滑動一個View,本質區別就是移動一個View。改變當前View所在的座標,原理和動畫類似不斷改變座標位置實現。實現View的滑動就必須監聽滑動的事件,而且根據事件傳入的座標,動態且不斷改變View的座標,從而實現View跟隨用戶觸摸的滑動而滑動。android
Android中將屏幕最左上角的頂點做爲Android座標系的原點,從這個點向右是X軸正方向,從這個點向下是Y軸正方向,以下圖:bash
系統提供了getLocationOnScreen(int location[])這樣的方法來獲取Android座標系中點的位置,即該視圖左上角在Android座標系中的座標。在觸控事件中使用getRawX()、getRawY()方法所得到的座標一樣是Android座標系中的座標。ide
Android中除了上面所說的這種座標系以外,還有一個視圖座標系,它描述了子視圖在父視圖中的位置關係。這兩種座標系並不矛盾也不復雜,他們的做用是相互相成的。與Android座標系相似,視圖座標系一樣是以原點向右爲X軸正方向,以原點向下爲Y軸正方向,只不過在視圖座標系中,原點再也不是Android座標系中的屏幕最左上角,而是以父視圖左上角爲座標原點,以下圖:佈局
在觸控事件中,經過getX()、getY()所得到的座標系就是視圖座標系中的座標。post
(3)、觸控事件——MotionEvent動畫
觸控事件MotionEvent在用戶交互中,佔着舉足輕重的地位。首先看看MotionEvent封裝的一些經常使用事件常量,定義了觸控事件的不一樣類型。ui
//單點觸摸按下動做
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方法來進行篩選,這個代碼的模式基本固定:this
@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:
//處理移動事件
break;
case MotionEvent.ACTION_UP:
//處理離開事件
break;
}
return true;
}複製代碼
在不涉及多點操做的狀況下,一般可使用以上代碼來完成觸控事件的監聽。spa
在Android中系統提供了很是多的方法來獲取座標值、相對距離等。方法豐富當然好,下面對座標系的API進行總結,以下圖:.net
這些方法能夠分爲以下兩個類別:
當了解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隨着手指在屏幕上的滑動而滑動。初始化時顯示效果:
在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);
break;複製代碼
這樣沒錯移動後,View都會調用Layout方法來對本身從新佈局,從而達到移動View的效果。
上面的代碼中,使用的是getX()、getY()方法來獲取座標值,即經過視圖座標來獲取偏移量。固然,一樣可使用getRawX()、getRawY()來獲取座標,並使用絕對座標來計算偏移量,代碼以下:
// 視圖座標方式
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 記錄觸摸點座標
lastX = x;
lastY = y;
break;
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);
//從新設置初始化座標
lastX = x;
lastY = y;
break;
}
return true;
}複製代碼
使用絕對座標系,有一點很是須要注意的地方,就是在每次執行完ACTION_MOVE的邏輯後,必定要從新設置初始化座標,這樣才能準確地獲取偏移量。
這個方法至關於系統提供的一個對左右、上下移動的API的封裝。當計算出偏移量後,只須要使用以下代碼就能夠完成View的從新佈局,效果與使用Layout方法同樣,代碼以下所示:
//同時對left和right進行偏移
offsetLeftAndRight(offsetX);
//同時對top和bottom進行偏移
offsetTopAndBottom(offsetY);複製代碼
這裏的offsetX、offsetY與在layout方法中計算offset方法同樣。
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();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);複製代碼
咱們能夠發現,使用ViewGroup.MarginLayoutParams更加的方便,不須要考慮父佈局的類型,固然它們的本質都是同樣。
在一個View中,系統提供了scrollTo、scrollBy兩種方式來改變一個View的位置。這兩個方法的區別很是好理解,與英文中To與By的區別相似,scrollTo(x,y)表示移動到一個具體的座標點(x,y),而scrollBy(dx,dy)表示移動的增量爲dx,dy。
與前面幾種方式相同,在獲取偏移量後使用scrollBy來移動View,代碼以下:
int offsetX = x - lastX;
int offsetY = y - lastY;
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,那麼平移以後的可視區域如圖二。
圖一
圖2、移動以後的可視區域
咱們發現,雖然設置scrollBy(20,10),偏移量均爲X軸、Y軸正方向上的正數,可是在屏幕的可視區域內,Button卻向X軸、Y軸負方向上移動了。這就是由於參考系選擇的不一樣,而產生的不一樣效果。
經過上面的分析能夠發現,若是講scrollBy中的參數dx和dy設置爲正數,那麼content講向座標軸負方向移動,若是將scrollBy中的參數dx和dy設置爲負數,那麼content將向座標軸正方向移動,所以回到前面的例子,要實現跟隨着手指移動而滑動的效果,就必須將偏移量改成負值,代碼以下:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);複製代碼
如今在運行一次發現和前面幾種方式效果相同了,相似地使用絕對座標時,也能夠經過使用scrollTo發方法來實現這一效果。
前面提到了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
mScroller = new Scroller(context);複製代碼
下面咱們須要重寫computerScroller()芳芳法,它是使用Scroller類的核心,系統在繪製View的時候會在draw()方法中調用該方法。這個方法實際就是使用的scrollTo方法。再結合Scroller對象,幫助獲取到當前滾動值。咱們能夠經過不斷地瞬間移動一個小的距離來實現總體上的平滑移動效果。代碼以下:
@Override
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,而中斷循環,完成平滑移動過程。
咱們在須要使用平滑移動的事件中,使用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的參數來設置滑動的持續時長。
屬性動畫請參見個人另外一篇:Android全套動畫使用技巧
Google在其support庫中爲咱們提供了DrawerLayout和SlidingPaneLayout兩個佈局來幫助開發者實現側邊欄滑動的效果。這兩個新的佈局方便咱們建立本身的滑動佈局界面,在這兩個強大布局背後有一個功能強大的類——ViewDragHelper。經過ViewDragHelper,基本能夠實現各類不一樣的滑動、拖放需求,所以這個方法也是各類滑動解決方案中的終結絕招。
下面演示一個使用ViewDragHelper建立一個QQ側邊欄滑動的佈局,如圖:
圖三
圖四
首先須要初始化ViewDragHelper,ViewDragHelper一般定義在一個ViewGroup的內部,經過靜態工廠方法進行初始化,代碼以下:
mViewDragHelper = ViewDragHelper.create(this, callback);複製代碼
第一個參數監聽的View,一般須要一個ViewGroup,即parentView;第二個參數是一個Callback回調,這個回調就是整個ViewDragHelper的邏輯核心。
重寫攔截事件,將事件傳遞給ViewDragHelper進行處理;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//將觸摸事件傳遞給ViewDragHelper,此操做必不可少
mViewDragHelper.processTouchEvent(event);
return true;
}複製代碼
使用ViewDragHelper一樣須要重寫computeScroll()方法,由於ViewDragHelper內部也是經過Scroller來實現平滑移動的。
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}複製代碼
建立一個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,以下代碼:
// 什麼時候開始檢測觸摸事件
@Override
public boolean tryCaptureView(View child, int pointerId) {
//若是當前觸摸的child是mMainView時開始檢測
return mMainView == child;
}複製代碼
具體垂直滑動方法clampViewPositionVertical()和水平滑動方法clampViewPositionHorizontal()。實現滑動這個兩個方法必須寫,默認返回值是0,即不發生滑動,固然若是隻重寫clampViewPositionVertical()或clampViewPositionHorizontal()中的一個,那麼就只會實現該方向上的滑動效果。
// 處理垂直滑動
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
// 處理水平滑動
@Override
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;
}
// 處理垂直滑動
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
// 處理水平滑動
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
};複製代碼
在前面的Scroller中講解時實現一個效果——手指離開屏幕後,View滑動回到初始位置。如今使用ViewDragHelper實現,在ViewDragHelper.Callback中,系統提供了這樣的方法——onViewReleased(),經過重寫這個方法,能夠很是簡單地實現當手指離開屏幕後實現的操做。這個方法內部是使用Scroller類實現的,這也是前面重寫computeScroll()方法的緣由。
@Override
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);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}複製代碼
設置讓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的寬度來處理滑動後的效果,就可使用這個值判斷。
/***
* 加載完佈局文件後調用
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
@Override
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);
initView();
}
public DragViewGroup(Context context,
AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
/***
* 加載完佈局文件後調用
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = mMenuView.getMeasuredWidth();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
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後回調
@Override
public void onViewCaptured(View capturedChild,
int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
// 當拖拽狀態改變,好比idle,dragging
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
// 當位置改變的時候調用,經常使用與滑動時更改scale等
@Override
public void onViewPositionChanged(View changedView,
int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
// 處理垂直滑動
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
// 處理水平滑動
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
// 拖動結束後調用
@Override
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);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}複製代碼
除此以外,ViewDragHelper不少強大的功能還沒獲得展現,在ViewDragHelper.Callback中,系統定義了大量的監聽事件來幫助咱們處理各類事件,以下:
STATE_IDLE:View當前沒有被拖拽也沒執行動畫,只是安靜地待在原地
STATE_DRAGGING:View當前正在被拖動,因爲用戶輸入或模擬用戶輸入致使View位置正在改變
STATE_SETTLING:View當前正被安頓到指定位置,由fling手勢或預約義的非交互動做觸發