天天看點

Android的滑動分析以及各種實作

【引自mrxi的部落格】一、滑動效果的産生

滑動一個view,本質差別就是移動一個view。改變目前view所在的坐标,原理和動畫相似不斷改變坐标位置實作。實作view的滑動就必須監聽滑動的事件,并且根據事件傳入的坐标,動态且不斷改變view的坐标,進而實作view跟随使用者觸摸的滑動而滑動。

(1)、android的坐标系

android中将螢幕最左上角的頂點作為android坐标系的原點,從這個點向右是x軸正方向,從這個點向下是y軸正方向,如下圖:

Android的滑動分析以及各種實作

系統提供了getlocationonscreen(int location[])這樣的方法來擷取android坐标系中點的位置,即該視圖左上角在android坐标系中的坐标。在觸控事件中使用getrawx()、getrawy()方法所獲得的坐标同樣是android坐标系中的坐标。

(2)、視圖坐标系

android中除了上面所說的這種坐标系之外,還有一個視圖坐标系,它描述了子視圖在父視圖中的位置關系。這兩種坐标系并不沖突也不複雜,他們的作用是互相相成的。與android坐标系類似,視圖坐标系同樣是以原點向右為x軸正方向,以原點向下為y軸正方向,隻不過在視圖坐标系中,原點不再是android坐标系中的螢幕最左上角,而是以父視圖左上角為坐标原點,如下圖:

Android的滑動分析以及各種實作

在觸控事件中,通過getx()、gety()所獲得的坐标系就是視圖坐标系中的坐标。

(3)、觸控事件——motionevent

觸控事件motionevent在使用者互動中,占着舉足輕重的地位。首先看看motionevent封裝的一些常用事件常量,定義了觸控事件的不同類型。

//單點觸摸按下動作 

public static final int action_down             = 0; 

//單點觸摸離開動作 

public static final int action_up               = 1; 

//觸摸點移動動作 

public static final int action_move             = 2; 

//觸摸動作取消 

public static final int action_cancel           = 3; 

//觸摸動作超出邊界 

public static final int action_outside          = 4; 

//多點觸摸按下動作 

public static final int action_pointer_down     = 5; 

//多點離開動作 

public static final int action_pointer_up       = 6;  

通常情況會在ontouchevent(motionevent event)方法中通過event.getaction()方法來擷取觸控事件的類型,并使用switch-case方法來進行篩選,這個代碼的模式基本固定:

@override 

public boolean ontouchevent(motionevent event) { 

    //擷取目前輸入點的x、y坐标(視圖坐标) 

    int x = (int) event.getx(); 

    int y = (int) event.gety(); 

    switch (event.getaction()) { 

        case motionevent.action_down: 

            //處理按下事件 

            break; 

        case motionevent.action_move: 

            //處理移動事件 

        case motionevent.action_up: 

            //處理離開事件 

    } 

    return true; 

}  

在不涉及多點操作的情況下,通常可以使用以上代碼來完成觸控事件的監聽。

在android中系統提供了非常多的方法來擷取坐标值、相對距離等。方法豐富固然好,下面對坐标系的api進行總結,如下圖:

Android的滑動分析以及各種實作

這些方法可以分為如下兩個類别:

view提供的擷取坐标方法

gettop():擷取到的是view自身的頂邊到其父布局頂邊的距離。

getleft():擷取到的是view自身的左邊到其父布局最左邊的距離。

getright():擷取到的是view自身的右邊到其父布局左邊的距離。

getbottom():擷取到的是view自身的底邊到其父布局頂邊的距離。

motionevent提供的方法

getx():擷取點選事件距離空間左邊的距離,即視圖坐标。

gety():擷取點選事件距離控件頂邊的距離,即視圖坐标。

getrawx():擷取點選事件距離整個螢幕左邊的距離,即絕對坐标。

getrawy():擷取點選事件距離整個螢幕頂邊的距離,即絕對坐标。

二、實作滑動的七種方式

當了解android坐标系和觸控事件後,我們再來看看如何使用系統提供的api來實作動态地修改一個view坐标,即實時滑動效果。而不管采用哪一種方式,其實作的思想基本是一緻的,當觸摸view時,系統記下目前觸摸點坐标,當手指移動時,系統記下移動後的觸摸點坐标,進而擷取到相對于前一次坐标點的偏移量,并通過偏移量來修改view的坐标,這樣不斷重複,實作滑動過程。

通過一個執行個體看看android中該如何實作滑動效果,定義一個view,處于linearlayout中,實作一個簡單布局:

<?xml version="1.0" encoding="utf-8"?> 

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" 

android:layout_width="match_parent" 

android:layout_height="match_parent" 

android:orientation="vertical"> 

<com.xjf.drawview.dragview1 

android:layout_width="100dp" 

android:layout_height="100dp" /> 

</linearlayout>  

我們的目的就是讓這個自定義的view随着手指在螢幕上的滑動而滑動。初始化時顯示效果:

Android的滑動分析以及各種實作

(1)、layout方法

在view繪制時,會調用onlayout()方法來設定顯示的位置。同樣,可以通過修改view的left,top,right,bottom四個屬性來控制view的坐标。與前面提供的模闆代碼一樣,在每次回調ontouchevent的時候,我們都來擷取一下觸摸點的坐标,代碼如下:

//擷取目前輸入點的x、y坐标(視圖坐标) 

int x = (int) event.getx(); 

int y = (int) event.gety();  

接着,在action_down事件中記錄觸摸點的坐标,如下:

case motionevent.action_down: 

// 記錄觸摸點坐标 

lastx = x; 

lasty = y; 

break;  

最後,可以在action_move事件中計算偏移量,并将偏移量作用到layout方法中,在目前layout的left,top,right,bottom基礎上,增加計算出來的偏移量,代碼如下所示:

case motionevent.action_move: 

// 計算偏移量 

int offsetx = x - lastx; 

int offsety = y - lasty; 

// 在目前left、top、right、bottom的基礎上加上偏移量 

layout(getleft() + offsetx, 

gettop() + offsety, 

getright() + offsetx, 

getbottom() + offsety); 

這樣沒錯移動後,view都會調用layout方法來對自己重新布局,進而達到移動view的效果。

上面的代碼中,使用的是getx()、gety()方法來擷取坐标值,即通過視圖坐标來擷取偏移量。當然,同樣可以使用getrawx()、getrawy()來擷取坐标,并使用絕對坐标來計算偏移量,代碼如下:

// 視圖坐标方式 

int x = (int) event.getrawx(); 

int y = (int) event.getrawy(); 

switch (event.getaction()) { 

break; 

//重新設定初始化坐标 

return true; 

使用絕對坐标系,有一點非常需要注意的地方,就是在每次執行完action_move的邏輯後,一定要重新設定初始化坐标,這樣才能準确地擷取偏移量。

(2)、offsetleftandright()與offsettopandbottom()

這個方法相當于系統提供的一個對左右、上下移動的api的封裝。當計算出偏移量後,隻需要使用如下代碼就可以完成view的重新布局,效果與使用layout方法一樣,代碼如下所示:

//同時對left和right進行偏移 

offsetleftandright(offsetx); 

//同時對top和bottom進行偏移 

offsettopandbottom(offsety);  

這裡的offsetx、offsety與在layout方法中計算offset方法一樣。

(3)、layoutparams

layoutparams儲存了一個view的布局參數。是以可以在程式中,通過改變layoutparams來動态地修改一個布局的位置參數,進而達到改變view位置的效果。我們可以很友善在程式中使用getlayoutparams()來擷取一個view的layoutparams。當然,計算偏移量的方法與在layout方法中計算offset也是一樣。當擷取到偏移量之後,就可以通過setlayoutparams來改變其layoutparams,代碼如下:

linearlayout.layoutparams layoutparams = (linearlayout.layoutparams) getlayoutparams(); 

layoutparams.leftmargin = getleft() + offsetx; 

layoutparams.topmargin = gettop() + offsety; 

setlayoutparams(layoutparams);  

這裡getlayoutparams()擷取layoutparams時,需要根據view所在view父布局的類型來設定不同的類型,比如這裡将view放在linearlayout中,那麼就可以使用linearlayout.layoutparams。如果在relativelayout中,就要使用relativelayout.layoutparams。這一切的前提是你必須要有一個父布局,不然系統無法擷取layoutparams。

在通過改變layoutparams來改變一個view的位置時,通常改變的是這個view的margin屬性,是以除了使用布局的layoutparams之外,還可以使用viewgroup.marginlayoutparams來實作這一一個功能,代碼:

viewgroup.marginlayoutparams layoutparams = (viewgroup.marginlayoutparams) getlayoutparams(); 

我們可以發現,使用viewgroup.marginlayoutparams更加的友善,不需要考慮父布局的類型,當然它們的本質都是一樣。

(4)、scrollto與scrollby

在一個view中,系統提供了scrollto、scrollby兩種方式來改變一個view的位置。這兩個方法的差別非常好了解,與英文中to與by的差別類似,scrollto(x,y)表示移動到一個具體的坐标點(x,y),而scrollby(dx,dy)表示移動的增量為dx,dy。

與前面幾種方式相同,在擷取偏移量後使用scrollby來移動view,代碼如下:

scrollby(offsetx, offsety);  

但是,當我們拖動view的時候,你會發現view并沒有移動,其實方法沒錯,view确實移動了,隻是移動的并不是我們想要的東西。scrollto、scrollby方法移動的是view的content,即讓view的内容移動,如果在viewgroup中使用scrollto、scrollby方法,那麼移動的将是所有子view,如果在view中使用,那麼移動的将是view的内容,例如textview,content就是它的文本,imageview,content就是它的drawable對象。

通過以上的分析,現在知道為什麼不能再view中使用這兩個方法來拖動這個view了。那麼我們就該view所在的viewgroup中來使用scrollby方法,移動它的子view,代碼如下:

((view) getparent()).scrollby(offsetx, offsety); 

但是再次拖動view的時候,你會發現view雖然移動了,但卻在亂動,并不是我們想要的跟随觸摸點的移動而移動。這裡先看一下視圖移動,不妨這樣想象一下手機螢幕是一個中空的蓋闆,蓋闆下面是一個巨大的畫布,也就是我們想要顯示的視圖。當把這個蓋闆蓋在畫布上的某一處時,透過中間空的矩形,我們看見了手機螢幕上顯示的視圖,而畫布上其他地方的視圖,則被蓋闆蓋住了無法看見。我們的視圖與這個例子非常類似,我們沒有看見視圖,并不代表它就不存在,有可能隻是在螢幕外面而已。當調用scrollby方法時,可以想象為外面的蓋闆在移動,這麼說比較抽象。

下圖一中間的矩形相當于螢幕,及可視區域。後面的content就相當于畫布,代表視圖。可以看到,隻有視圖的中間部分目前是可視的,其他部分都不可見。在可見區域中,我們設定了一個button,它的坐标為(20,10)。

下面使用scrollby方法,将蓋闆(螢幕、可視區域),在水準方向上向x軸正方向(向右)平移20,在豎直方向上向y軸正方向(下方)平移10,那麼平移之後的可視區域如圖二。

Android的滑動分析以及各種實作

圖一

Android的滑動分析以及各種實作

圖二、移動之後的可視區域

我們發現,雖然設定scrollby(20,10),偏移量均為x軸、y軸正方向上的正數,但是在螢幕的可視區域内,button卻向x軸、y軸負方向上移動了。這就是因為參考系選擇的不同,而産生的不同效果。

通過上面的分析可以發現,如果講scrollby中的參數dx和dy設定為正數,那麼content講向坐标軸負方向移動,如果将scrollby中的參數dx和dy設定為負數,那麼content将向坐标軸正方向移動,是以回到前面的例子,要實作跟随着手指移動而滑動的效果,就必須将偏移量改為負值,代碼如下:

((view) getparent()).scrollby(-offsetx, -offsety);  

現在在運作一次發現和前面幾種方式效果相同了,類似地使用絕對坐标時,也可以通過使用scrollto發方法來實作這一效果。

(5)、scroller

前面提到了scrollby、scrollto方法,就不得不再來說一說scroller類。scroller類與scrollby、scrollto方法十分相似。什麼差別?先看例子,如果要完成這樣一個效果;通過點選按鈕,讓一個viewgroup的子view向右移動100個像素。問題看起來很簡單,隻要在按鈕的點選事件中使用前面的scrollby方法設定下偏移量就可以了嗎?确實這樣可以讓一個子viewgroup中的子view平移,但是不管使用scrollby還是scrollto方法,子view的平移都是瞬間發生的,在事件執行的時候平移就已經完成了,這樣的效果會讓人感覺非常突然,google建議使用自然的過度動畫來實作移動效果。是以scroller類就這樣誕生了,通過scroller類可以實作平滑移動的效果,而不是瞬間就完成移動。

scroller類的實作原理,其實它與前面使用的scrollto和scrollby方法來實作子view跟随手指移動的原理基本類似,雖然scrollby芳芳法是讓子view瞬間從某點移動到另一個點,但是由于在action_move事件中不斷擷取手指移動的微小的偏移量,這樣就将一段距離劃分成了n個非常小的偏移量。雖然每個偏移量裡面,通過scrollby方法進行了瞬間移動,但是在整體上卻可以獲得一個平滑移動的效果。這個原理與動畫的實作原理也是基本類似的,它們都是利用了人眼的視覺暫留特性。

下面我們使用scroller類實作平滑移動,在這個執行個體中,同樣讓子view跟随手指的滑動而滑動,但是在手指離開屏蔽時,讓子view平滑的移動到初始化位置,即螢幕左上角。使用scroller類需要如下三個步驟:

初始化scroller

首先通過它的構造方法來建立一個scroller對象,代碼如下所示:

// 初始化scroller 

mscroller = new scroller(context);  

重寫computerscroller方法,實作模拟滑動

下面我們需要重寫computerscroller()芳芳法,它是使用scroller類的核心,系統在繪制view的時候會在draw()方法中調用該方法。這個方法實際就是使用的scrollto方法。再結合scroller對象,幫助擷取到目前滾動值。我們可以通過不斷地瞬間移動一個小的距離來實作整體上的平滑移動效果。代碼如下:

public void computescroll() { 

    super.computescroll(); 

    // 判斷scroller是否執行完畢 

    if (mscroller.computescrolloffset()) { 

        ((view) getparent()).scrollto( 

                mscroller.getcurrx(), 

                mscroller.getcurry()); 

        // 通過重繪來不斷調用computescroll 

        invalidate(); 

scroller類提供了computescrolloffset()方法來判斷是否完成了整個滑動,同時也提供了getcurrx()、getcurry()方法來獲得目前的滑動坐标。在上面的代碼中,唯一需要注意的是invalidate()方法,因為隻能在computescroller()方法中擷取模拟過程中的scrollx和scrolly坐标。但computescroll()方法是不會自動調用的,隻能通過invalidate()->draw()->computescroll()來間接調用compuetscroll()方法,是以需要在compuetscroll()方法中調用invalidate()方法,實作循環擷取scrollx和scrolly的目的。而當模拟過程結束後,scroller.compuetscrolloffset()方法會傳回false,而中斷循環,完成平滑移動過程。

startscroll開啟模拟過程

我們在需要使用平滑移動的事件中,使用scroller類的startscroll()方法來開啟平滑移動過程。startscroll()方法具有兩個重載方法。

public void startscroll(int startx, int starty, int dx, int dy)  

public void startscroll(int startx, int starty, int dx, int dy, int duration) 

可以看到它們的差別就是一個具有指定的支援時長,而另一個沒有。很好了解,與在動畫中設定duration和使用預設的顯示時長是一個道理。其他四個坐标,則與他們的命名含義相同,就是起始坐标與偏移量。在擷取坐标時,通常可以使用getscrollx()和getscrolly()方法來擷取父視圖中content所滑動到的點的坐标,需要注意的是這個值的正負,它與在scrollby、scrollto中講解的情況是一樣的。

根據以上三步,就可以使用scroller類實作平滑移動,在構造方法中初始化scroller對象,重寫view的computerscroll()方法,最後監聽手指離開屏蔽的事件,并在該事件中調用startscroll()方法完成平滑移動。監聽手指離開螢幕的事件,隻需要在ontouchevent中增加一個action_up監聽選項即可,代碼如下所示:

case motionevent.action_up: 

    // 手指離開時,執行滑動過程 

    view viewgroup = ((view) getparent()); 

    mscroller.startscroll( 

            viewgroup.getscrollx(), 

            viewgroup.getscrolly(), 

            -viewgroup.getscrollx(), 

            -viewgroup.getscrolly()); 

    invalidate(); 

    break;  

在startscroll()方法中我們擷取子view移動的距離-getscrollx()、getscrolly(),并将偏移量設定為其相反數,進而将子view滑動到原位置。這裡的invalidate()方法是用來通知view進行重繪,調用computescroll()的模拟過程。當然,也可以給startscroll()方法增加一個duration的參數來設定滑動的持續時長。

(6)、屬性動畫

屬性動畫請參見我的另一篇:android全套動畫使用技巧

(7)、viewdraghelper

google在其support庫中為我們提供了drawerlayout和slidingpanelayout兩個布局來幫助開發者實作側邊欄滑動的效果。這兩個新的布局友善我們建立自己的滑動布局界面,在這兩個強大布局背後有一個功能強大的類——viewdraghelper。通過viewdraghelper,基本可以實作各種不同的滑動、拖放需求,是以這個方法也是各種滑動解決方案中的終結絕招。

下面示範一個使用viewdraghelper建立一個qq側邊欄滑動的布局,如圖:

Android的滑動分析以及各種實作

圖三

Android的滑動分析以及各種實作

圖四

初始化viewdraghelper

首先需要初始化viewdraghelper,viewdraghelper通常定義在一個viewgroup的内部,通過靜态工廠方法進行初始化,代碼如下:

mviewdraghelper = viewdraghelper.create(this, callback); 

第一個參數監聽的view,通常需要一個viewgroup,即parentview;第二個參數是一個callback回調,這個回調就是整個viewdraghelper的邏輯核心。

攔截事件

重寫攔截事件,将事件傳遞給viewdraghelper進行處理;

public boolean onintercepttouchevent(motionevent ev) { 

return mviewdraghelper.shouldintercepttouchevent(ev); 

//将觸摸事件傳遞給viewdraghelper,此操作必不可少 

mviewdraghelper.processtouchevent(event); 

處理computescroll()

使用viewdraghelper同樣需要重寫computescroll()方法,因為viewdraghelper内部也是通過scroller來實作平滑移動的。

    if (mviewdraghelper.continuesettling(true)) { 

        viewcompat.postinvalidateonanimation(this); 

處理回調callback

建立一個viewdraghelper.callback

private viewdraghelper.callback getcallback = new viewdraghelper.callback() { 

    @override 

    public boolean trycaptureview(view child, int pointerid) { 

        return false; 

};  

as自動重寫trycaptureview()方法,通過這個方法可以指定在建立viewdraghelper時,參數parentview中的哪一個子vieww可以被移動,例如我們在這個執行個體中自定義一個viewgroup,裡面定義了兩個子view——menu view和mainview,如下代碼:

// 何時開始檢測觸摸事件 

public boolean trycaptureview(view child, int pointerid) { 

    //如果目前觸摸的child是mmainview時開始檢測 

    return mmainview == child; 

具體垂直滑動方法clampviewpositionvertical()和水準滑動方法clampviewpositionhorizontal()。實作滑動這個兩個方法必須寫,預設傳回值是0,即不發生滑動,當然如果隻重寫clampviewpositionvertical()或clampviewpositionhorizontal()中的一個,那麼就隻會實作該方向上的滑動效果。

// 處理垂直滑動 

public int clampviewpositionvertical(view child, int top, int dy) { 

    return top; 

// 處理水準滑動 

public int clampviewpositionhorizontal(view child, int left, int dx) { 

    return left; 

clampviewpositionvertical(view child, int top, int dy)中的參數top,代表在垂直方向上child移動的距離,dy則表示比較前一次的增量。clampviewpositionhorizontal(view child, int left, int dx)也是類似的含義,通常情況下隻需要傳回top和left即可,但需要更加精确地計算padding等屬性的時候,就需要對left進行一些處理,并傳回合适大小的值。

通過重寫上面的三個方法,就可以實作基本的滑動效果。當用手拖動mainview的時候,它就可有跟随手指的滑動而滑動了,代碼:

private viewdraghelper.callback callback =  new viewdraghelper.callback() { 

            // 何時開始檢測觸摸事件 

            @override 

            public boolean trycaptureview(view child, int pointerid) { 

                //如果目前觸摸的child是mmainview時開始檢測 

                return mmainview == child; 

            } 

            // 處理垂直滑動 

            public int clampviewpositionvertical(view child, int top, int dy) { 

                return 0; 

            // 處理水準滑動 

            public int clampviewpositionhorizontal(view child, int left, int dx) { 

                return left; 

        };  

在前面的scroller中講解時實作一個效果——手指離開螢幕後,view滑動回到初始位置。現在使用viewdraghelper實作,在viewdraghelper.callback中,系統提供了這樣的方法——onviewreleased(),通過重寫這個方法,可以非常簡單地實作當手指離開螢幕後實作的操作。這個方法内部是使用scroller類實作的,這也是前面重寫computescroll()方法的原因。

public void onviewreleased(view releasedchild, float xvel, float yvel) { 

    super.onviewreleased(releasedchild, xvel, yvel); 

    //手指擡起後緩慢移動到指定位置 

    if (mmainview.getleft() < 500) { 

        //關閉菜單 

        //等同于scroll的startscroll方法 

        mviewdraghelper.smoothslideviewto(mmainview, 0, 0); 

        viewcompat.postinvalidateonanimation(dragviewgroup.this); 

    } else { 

        //打開菜單 

        mviewdraghelper.smoothslideviewto(mmainview,300,0); 

設定讓mainview移動後左邊距小于500像素的時候,就使用smoothslideviewto()方法來講mainview還原到初始狀态,即坐标(0,0),左邊距大于500則将mainview移動到(300,0)坐标,即顯示mainview。

//viewdraghelper 

mviewdraghelper.smoothslideviewto(mmainview, 0, 0); 

viewcompat.postinvalidateonanimation(dragviewgroup.this);   

//scroller 

mscroller.startscroll(x,y,dx,dy); 

invalidate();  

滑動的時候,在自定義viewgroup的onfinishinflate()方法中,按照順序将子view分别定義成menuview和mainview,并在onsizechanged方法中獲得view的寬度。如果需要根據view的寬度來處理滑動後的效果,就可以使用這個值判斷。

/*** 

 * 加載完布局檔案後調用 

 */ 

protected void onfinishinflate() { 

    super.onfinishinflate(); 

    mmenuview = getchildat(0); 

    mmainview = getchildat(1); 

protected void onsizechanged(int w, int h, int oldw, int oldh) { 

    super.onsizechanged(w, h, oldw, oldh); 

    mwidth = mmenuview.getmeasuredwidth(); 

最後,整個通過viewdraghelper實作qq側滑功能代碼:

package com.xjf.drawview; 

import android.content.context; 

import android.support.v4.view.viewcompat; 

import android.support.v4.widget.viewdraghelper; 

import android.util.attributeset; 

import android.view.motionevent; 

import android.view.view; 

import android.widget.framelayout; 

public class dragviewgroup extends framelayout { 

    private viewdraghelper mviewdraghelper; 

    private view mmenuview, mmainview; 

    private int mwidth; 

    public dragviewgroup(context context) { 

        super(context); 

        initview(); 

    public dragviewgroup(context context, attributeset attrs) { 

        super(context, attrs); 

    public dragviewgroup(context context, 

                         attributeset attrs, int defstyleattr) { 

        super(context, attrs, defstyleattr); 

    /*** 

     * 加載完布局檔案後調用 

     */ 

    protected void onfinishinflate() { 

        super.onfinishinflate(); 

        mmenuview = getchildat(0); 

        mmainview = getchildat(1); 

    protected void onsizechanged(int w, int h, int oldw, int oldh) { 

        super.onsizechanged(w, h, oldw, oldh); 

        mwidth = mmenuview.getmeasuredwidth(); 

    public boolean onintercepttouchevent(motionevent ev) { 

        return mviewdraghelper.shouldintercepttouchevent(ev); 

    public boolean ontouchevent(motionevent event) { 

        //将觸摸事件傳遞給viewdraghelper,此操作必不可少 

        mviewdraghelper.processtouchevent(event); 

        return true; 

    private void initview() { 

        mviewdraghelper = viewdraghelper.create(this, callback); 

    private viewdraghelper.callback callback = 

            new viewdraghelper.callback() { 

                // 何時開始檢測觸摸事件 

                @override 

                public boolean trycaptureview(view child, int pointerid) { 

                    //如果目前觸摸的child是mmainview時開始檢測 

                    return mmainview == child; 

                } 

                // 觸摸到view後回調 

                public void onviewcaptured(view capturedchild, 

                                           int activepointerid) { 

                    super.onviewcaptured(capturedchild, activepointerid); 

                // 當拖拽狀态改變,比如idle,dragging 

                public void onviewdragstatechanged(int state) { 

                    super.onviewdragstatechanged(state); 

                // 當位置改變的時候調用,常用與滑動時更改scale等 

                public void onviewpositionchanged(view changedview, 

                                                  int left, int top, int dx, int dy) { 

                    super.onviewpositionchanged(changedview, left, top, dx, dy); 

                // 處理垂直滑動 

                public int clampviewpositionvertical(view child, int top, int dy) { 

                    return 0; 

                // 處理水準滑動 

                public int clampviewpositionhorizontal(view child, int left, int dx) { 

                    return left; 

                // 拖動結束後調用 

                public void onviewreleased(view releasedchild, float xvel, float yvel) { 

                    super.onviewreleased(releasedchild, xvel, yvel); 

                    //手指擡起後緩慢移動到指定位置 

                    if (mmainview.getleft() < 500) { 

                        //關閉菜單 

                        //相當于scroller的startscroll方法 

                        mviewdraghelper.smoothslideviewto(mmainview, 0, 0); 

                        viewcompat.postinvalidateonanimation(dragviewgroup.this); 

                    } else { 

                        //打開菜單 

                        mviewdraghelper.smoothslideviewto(mmainview, 300, 0); 

                    } 

            }; 

    public void computescroll() { 

        if (mviewdraghelper.continuesettling(true)) { 

            viewcompat.postinvalidateonanimation(this); 

        } 

除此之外,viewdraghelper很多強大的功能還沒得到展示,在viewdraghelper.callback中,系統定義了大量的監聽事件來幫助我們處理各種事件,如下:

onviewcaptured()這個事件在使用者觸摸到view後回調

onviewdragstatechanged()這個事件在拖拽狀态改變時回調,比如idle,dragging等狀态

state_idle:view目前沒有被拖拽也沒執行動畫,隻是安靜地待在原地

state_dragging:view目前正在被拖動,由于使用者輸入或模拟使用者輸入導緻view位置正在改變

state_settling:view目前正被安頓到指定位置,由fling手勢或預定義的非互動動作觸發

onviewpositionchanged()//view在拖動過程坐标發生變化時會調用此方法,包括兩個時間段:手動拖動和自動滾動。

三、開源代碼庫

最後再分享一個自己積攢很久的代碼庫,隻有你想不到,沒有用不到的,歡迎star

<a href="https://github.com/xijiufu">https://github.com/xijiufu</a>

由于github伺服器在美國,有時通路很慢,還提供了開源中國位址庫,2個倉庫代碼均同步更新:

<a href="http://git.oschina.net/xijiufu">http://git.oschina.net/xijiufu </a>

作者:mrxi

來源:51cto

繼續閱讀