Android ViewDragHelper使用介紹
ViewDragHelper是support.v4下提供的用于處理拖拽滑動的輔助類,查看Android的DrawerLayout源碼,可以發(fā)現(xiàn),它內(nèi)部就是使用了該輔助類來處理滑動事件的.
public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
final float density = getResources().getDisplayMetrics().density;
mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
final float minVel = MIN_FLING_VELOCITY * density;
mLeftCallback = new ViewDragCallback(Gravity.LEFT);
mRightCallback = new ViewDragCallback(Gravity.RIGHT);
mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
mLeftDragger.setMinVelocity(minVel);
mLeftCallback.setDragger(mLeftDragger);
mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
mRightDragger.setMinVelocity(minVel);
mRightCallback.setDragger(mRightDragger);
//省略
}
有了ViewDragHelper,我們在寫與拖拽滑動相關(guān)的自定義控件的時候就變得非常簡單了,例如我們可以用來實現(xiàn)自定義側(cè)滑菜單,再也不需要在onTouchEvent方法里計算滑動距離來改變布局邊框的位置了.
使用ViewDragHelper類的大體步驟分為3步:
步驟1.在自定義的ViewGroup子類下通過ViewDragHelper的靜態(tài)方法獲取到ViewDragHelper的實例引用,注意它是一個單例的.
查看源碼:
/**
* Factory method to create a new ViewDragHelper.
*
* @param forParent Parent view to monitor
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
return new ViewDragHelper(forParent.getContext(), forParent, cb);
}
可以發(fā)現(xiàn)它需要接收2個參數(shù),參數(shù)1就是當(dāng)前要使用ViewDragHelper的自定義控件的引用,Callback是一個回調(diào)抽象類,該回調(diào)接口是用于建立當(dāng)前自定義控件與ViewDragHelper溝通的橋梁,Callback內(nèi)定義了多個回調(diào)函數(shù),這些回調(diào)函數(shù)涵蓋了與當(dāng)前自定義控件相關(guān)的是否允許拖拽,當(dāng)前拖拽的View是哪一個view,拖拽的view的位置如何變化,釋放的時候那個view被釋放了,釋放時的速度是怎么樣的等等.稍后會詳細(xì)介紹.
步驟2.有了ViewDragHelper的引用后,我們就需要傳遞相關(guān)的觸摸事件給ViewDragHelper來幫我們處理,那么怎么傳遞呢?
可以通過重寫onInterceptTouchEvent和onTouchEvent這2個函數(shù)來傳遞,前者是用于決定是否要攔截中斷事件的,后者是用于消費觸摸事件的,如果前者return true則表示事件需要被攔截,那么事件就會直接回調(diào)給onTouchEvent去處理,如果onTouchEvent返回true,則事件被消費,返回false則向上返回它的父類調(diào)用處,如果事件在向上層層返回的過程中沒有被處理的話,那么事件最終將會消失;當(dāng)然,如果onInterceptTouchEvent返回false的話,那么事件就會繼續(xù)向下傳遞個它的直接子View去分發(fā)處理,關(guān)于事件分發(fā)的更多理論知識,大家可以看這篇文章事件分發(fā)機制的原理總結(jié).
實例代碼:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//由ViewDragHelper類來決定是否要攔截事件
return dragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
//由ViewDragHelper類來決定是否要處理觸摸事件,這里可能有異常
dragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
//返回true,可以持續(xù)接收到后續(xù)事件
return true;
}
步驟3:重寫ViewDragHelper.Callback()的相關(guān)回調(diào)方法,處理事件,大體有如下方法:
下面將通過demo來分別介紹幾個常用的方法,先來看看demo的整體代碼,這是一個自定義的ViewGroup的子類,里面有2個子控件,分別是側(cè)邊欄和主體布局
/**
* Created by mChenys on 2015/12/16.
*/
public class DragLayout extends FrameLayout {
private String TAG = "DragLayout";
private ViewDragHelper dragHelper;
private LinearLayout mLeftContent; //左側(cè)面板
private LinearLayout mMainContent;//主體面板
public DragLayout(Context context) {
this(context, null);
}
public DragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
//重寫此方法,可以獲取該容器下的所有的直接子View
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLeftContent = (LinearLayout) getChildAt(0);
mMainContent = (LinearLayout) getChildAt(1);
}
private void init() {
//step1 通過ViewDragHelper的單例方法獲取ViewDragHelper的實例
dragHelper = ViewDragHelper.create(this, mCallback);
}
//step2 傳遞觸摸事件,需要重寫onInterceptTouchEvent和onTouchEvent
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//由ViewDragHelper類來決定是否要攔截事件
return dragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
//由ViewDragHelper類來決定是否要處理觸摸事件
dragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
//返回true,可以持續(xù)接收到后續(xù)事件
return true;
}
//step3 重寫ViewDragHelper.Callback()的相關(guān)回調(diào)方法,處理事件
private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
/**
* 1.改方法是abstract的方法,必須要實現(xiàn),其返回結(jié)果決定當(dāng)前child是否可以拖拽
* @param child 當(dāng)前被拖拽的view
* @param pointerId pointerId 區(qū)分多點觸摸的id
* @return true表示允許拖拽, false則不允許拖拽 ,默認(rèn)返回false
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
Log.d(TAG, "tryCaptureView:當(dāng)前被拖拽的view:" + child);
return false;
}
};
}
布局文件:
<?xml version="1.0" encoding="utf-8"?> <mchenys.net.csdn.blog.mytencentqq.view.DragLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/dragLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <!--左側(cè)--> <LinearLayout android:id="@+id/layout_left" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_red_light" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="左側(cè)" android:textSize="30sp" /> </LinearLayout> <!--主體布局--> <LinearLayout android:id="@+id/layout_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_blue_light" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="主體" android:textSize="30sp" /> </LinearLayout> </mchenys.net.csdn.blog.mytencentqq.view.DragLayout>
運行效果:

打印的log
D/DragLayout: tryCaptureView:當(dāng)前被拖拽的view:android.widget.LinearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
D/DragLayout: tryCaptureView:當(dāng)前被拖拽的view:android.widget.LinearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
D/DragLayout: tryCaptureView:當(dāng)前被拖拽的view:android.widget.LinearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
D/DragLayout: tryCaptureView:當(dāng)前被拖拽的view:android.widget.LinearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
由上面的log可以知道tryCaptureView方法被執(zhí)行了,但是mMainContent卻沒有被拖動出來,只是為什么呢,因為tryCaptureView返回了false.默認(rèn)是返回false的,下面修改為mMainContent可以拖動,mLeftContent不可以拖動:
@Override
public boolean tryCaptureView(View child, int pointerId) {
Log.d(TAG, "tryCaptureView:當(dāng)前被拖拽的view:" + child);
if (child == mMainContent) {
return true; //只有主題布局可以被拖動
}
return false;
}
運行效果還是移動不了,這是為什么呢?
這是以因為我們還沒有重寫clampViewPositionHorizontal方法,下面將介紹該方法的使用
/**
* 根據(jù)建議值修正將要移動到的橫向位置,此時沒有發(fā)生真正的移動
* @param child 當(dāng)前被拖拽的View
* @param left 新的建議值
* @param dx 水平位置的變化量
* @return
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
Log.d(TAG, "clampViewPositionHorizontal:" + "舊的left坐標(biāo)oldLeft:" + child.getLeft()
+ " 水平位置的變化量dx:" + dx + " 新的建議值left:" + left);
return super.clampViewPositionHorizontal(child, left, dx); //父類默認(rèn)返回0
}
該方法返回的是水平方向的移動建議值,該建議值等于當(dāng)前的X坐標(biāo)+水平方向的變化量,向右移動,偏移量為正值,向左移動則為負(fù)數(shù).默認(rèn)返回的是調(diào)用父類的重寫的方法,查看源碼可以發(fā)現(xiàn)默認(rèn)返回的是0,如果建議值等于0的話,就表示水平方向不會移動.如果想要移動,我們需要返回它提供的建議值left,我們來看看運行的log:
tryCaptureView:當(dāng)前被拖拽的view:android.widget.LinearLayout{23a3c537 V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
clampViewPositionHorizontal:舊的left坐標(biāo)oldLeft:0 水平位置的變化量dx:1 新的建議值left:1
clampViewPositionHorizontal:舊的left坐標(biāo)oldLeft:0 水平位置的變化量dx:12 新的建議值left:12
clampViewPositionHorizontal:舊的left坐標(biāo)oldLeft:0 水平位置的變化量dx:63 新的建議值left:63
clampViewPositionHorizontal:舊的left坐標(biāo)oldLeft:0 水平位置的變化量dx:53 新的建議值left:53
tryCaptureView:當(dāng)前被拖拽的view:android.widget.LinearLayout{23a3c537 V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
clampViewPositionHorizontal:舊的left坐標(biāo)oldLeft:0 水平位置的變化量dx:-5 新的建議值left:-5
clampViewPositionHorizontal:舊的left坐標(biāo)oldLeft:0 水平位置的變化量dx:-2 新的建議值left:-2
clampViewPositionHorizontal:舊的left坐標(biāo)oldLeft:0 水平位置的變化量dx:-6 新的建議值left:-6
clampViewPositionHorizontal:舊的left坐標(biāo)oldLeft:0 水平位置的變化量dx:-11 新的建議值left:-11
由上面的log可以看出,分別是向右拖拽和向左拖拽的結(jié)果,如果我們返回了它的建議值,就可以實現(xiàn)水平方向的拖動了.
將clampViewPositionHorizontal的返回值修改成return left;看看運行效果:

跟我們預(yù)想的結(jié)果一樣,只有主體布局可以滑動,左側(cè)的布局不能滑動,如果想要左側(cè)布局也可以滑動,那么只需要在tryCaptureView直接返回true即可.效果如下:

同樣的,如果要實現(xiàn)垂直方向的拖拽滾動,就需要重新下面這個方法了.
/**
* 根據(jù)建議值修正將要移動到的縱向位置,此時沒有發(fā)生真正的移動
* @param child 當(dāng)前被拖拽的View
* @param top 新的建議值
* @param dy 垂直位置的變化量
* @return
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
Log.d(TAG, "clampViewPositionHorizontal:" + "舊的top坐標(biāo)oldTop:" + child.getTop()
+ " 垂直位置的變化量dy:" + dy + " 新的建議值top:" + top);
return top;
}
效果如下,可以隨意的滑動,實現(xiàn)起來是不是很簡單啊

繼續(xù)介紹剩下的回調(diào)方法
/**
* 當(dāng)capturedChild被捕獲時調(diào)用
* @param capturedChild 當(dāng)前被拖拽的view
* @param activePointerId
*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
Log.d(TAG, "onViewCaptured:當(dāng)前被拖拽的view:" + capturedChild);
super.onViewCaptured(capturedChild, activePointerId);
}
該回調(diào)方法和tryCaptureView一樣都可以獲取到當(dāng)前被拖拽的View,不同點在于tryCaptureView是可以決定哪個View是可以被拖拽滑動的,而onViewCaptured只是用來獲取當(dāng)前到底哪個View被正在拖拽而已.
/**
* 返回拖拽的范圍,不對拖拽進(jìn)行真正的限制,僅僅決定了動畫執(zhí)行的速度
* @param child
* @return 返回一個固定值
*/
@Override
public int getViewHorizontalDragRange(View child) {
int range = super.getViewHorizontalDragRange(child);
Log.d(TAG, "被拖拽的范圍getViewHorizontalDragRange:" + range);
return range;
}
該回調(diào)方法是用于決定水平方向的動畫執(zhí)行速度,相對的垂直方向肯定也會有相應(yīng)的方法,沒錯,就是下面這個:
@Override
public int getViewVerticalDragRange(View child) {
return super.getViewVerticalDragRange(child);
}
那么話又說回來,我們有什么辦法可以限制子View的滑動范圍呢,如果范圍不能很好的控制的話,那滑動也沒有什么意義了.還記得上面介紹的clampViewPositionHorizontal和clampViewPositionVertical嗎,分別用于設(shè)置水平方向和垂直方向的移動建議值,假設(shè)我們要限制該自定義控件的子View在水平方向移動的范圍為0-屏幕寬度的0.6,那么如何控制呢.要實現(xiàn)這個限制,我們現(xiàn)在獲取屏幕的寬度,由于當(dāng)前的自定義控件是全屏顯示的,所以直接就可以那控件的寬度來作為屏幕的寬度,那么如何獲取呢?有2種方式,分別是在onMeasure和onSizeChange方法里面調(diào)用getMeasuredWidth()方法獲取.前者是測量完之后獲取,后者是當(dāng)控件的寬高發(fā)生變化后獲取,例如:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 當(dāng)尺寸有變化的時候調(diào)用
mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth();
// 移動的范圍
mRange = (int) (mWidth * 0.6f);
}
接下來,在clampViewPositionHorizontal方法內(nèi)部做判斷,如果當(dāng)前的建議值left超過了mRange,那么返回mRange,如果小于了0,則返回0,這樣一來子View的滑動范圍就限定在0-mRange之間了,修改代碼如下:
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
Log.d(TAG, "clampViewPositionHorizontal 建議值left:" + left + " mRange:" + mRange);
if (left > mRange) {
left = mRange;
} else if (left < 0) {
left = 0;
}
return left;
}
如果垂直方向也想限定的話,那就修改clampViewPositionVertical返回的建議值
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
Log.d(TAG, "clampViewPositionVertical 建議值top:" + top + " mRange:" + mRange);
if (top > mRange) {
top = mRange;
} else if (top < 0) {
top = 0;
}
return top;
}
來看看運行的效果:

如此一來,我們就可以隨意的控制子View的拖拽滑動的范圍了.那么新的問題又來的,如果現(xiàn)在的需求是只能mMainContent被拖拽,mLeftContent不能被拖拽,也許你會說,這還不簡單嗎,直接在tryCaptureView判斷當(dāng)前拖拽的子View是mLeftContent的話就返回false不就得了,沒錯,如果需求只是這樣的話確實可以滿足了,但是如果在加上一個條件,那就是拖拽mLeftContent的時候的效果相當(dāng)于把mMainContent拖拽了,什么意思呢,也就說現(xiàn)在mMainContent已經(jīng)是打開的狀態(tài)了,我想通過滑動mLeftContent就能把mMainContent滑動了.而mLeftContent還是原來的位置不動.這個要怎么實現(xiàn)呢?
首先可以肯定的是,tryCaptureView方法必須返回true,表示mMainContent和mLeftContent都可以被滑動,接下來要處理的就是如何在mLeftContent滑動的時候是滑動mMainContent的.那么現(xiàn)在就要介紹另一個回調(diào)方法了,如下所示:
/**
* 當(dāng)View的位置發(fā)生變化的時候,處理要做的事情(更新狀態(tài),伴隨動畫,重繪界面)
* 此時,View已經(jīng)發(fā)生了位置的改變
*
* @param changedView 改變位置的View
* @param left 新的左邊值
* @param top 新的上邊值
* @param dx 水平方向的變化量
* @param dy 垂直方向的變化量
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
Log.d(TAG, "位置發(fā)生變化onViewPositionChanged:" + "新的左邊值left: " + left + " 水平方向的變化量dx:" + dx
+ " 新的上邊值top:" + top + " 垂直方向的變化量dy:" + dy);
super.onViewPositionChanged(changedView, left, top, dx, dy);
// 為了兼容低版本, 每次修改值之后, 進(jìn)行重繪
invalidate();
}
該方法是隨著View的位置發(fā)生變化而不斷回調(diào)的.四個參數(shù)如上面的注釋所述,通過該方法可以拿到當(dāng)前正在拖拽滑動的View是哪個View,有了這依據(jù)之后,我們就將在mLeftContent上的滑動的水平方向和垂直方向的變化量傳遞給mMainContent,這樣一來,滑動mLeftContent的效果不就等于滑動mMainContent了嗎.需要注意的是,該回調(diào)方法在低版本上為了兼容還需要加上invalidate();這句代碼,invalidate是用來刷新界面的,他會導(dǎo)致界面的重繪.
滑動mMainContent來看看log
D/DragLayout: 位置發(fā)生變化onViewPositionChanged:新的左邊值left: 15 水平方向的變化量dx:15 新的上邊值top:8 垂直方向的變化量dy:8
D/DragLayout: 位置發(fā)生變化onViewPositionChanged:新的左邊值left: 32 水平方向的變化量dx:17 新的上邊值top:16 垂直方向的變化量dy:8
D/DragLayout: 位置發(fā)生變化onViewPositionChanged:新的左邊值left: 121 水平方向的變化量dx:89 新的上邊值top:46 垂直方向的變化量dy:30
D/DragLayout: 位置發(fā)生變化onViewPositionChanged:新的左邊值left: 145 水平方向的變化量dx:24 新的上邊值t
由log可以看出,最新的left和top值是等于上一次的位置+水平/垂直方向的變化量.這個特點有點類似建議值了.不同的是建議值發(fā)生了改變不代表View就一定已經(jīng)處于滑動,除非返回的值也是建議值,但是onViewPositionChanged方法就不同了,這個方法只要一執(zhí)行,就說明目標(biāo)View是處于滑動狀態(tài)的.
以水平方向滑動為例,垂直方向不移動,接下來就可以在onViewPositionChanged方法內(nèi)做判斷了,如下所示:
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
//獲取最新的left坐標(biāo)
int newLeft = left;
if (changedView == mLeftContent) {
//如果滑動的是mLeftContent,則將其水平變化量dx傳遞給mMainContent,記錄在newLeft中
newLeft = mMainContent.getLeft() + dx;
}
//矯正范圍
if (newLeft > mRange) {
newLeft = mRange;
} else if (newLeft < 0) {
newLeft = 0;
}
//再次判斷,限制mLeftContent的滑動
if (changedView == mLeftContent) {
//強制將mLeftContent的位置擺會原來的位置,這里通過layout方法傳入左,上,右,下坐標(biāo)來實現(xiàn)
//當(dāng)然方法不限于這一種,例如還可以通過scrollTo(x,y)方法
mLeftContent.layout(0, 0, mWidth, mHeight);
//改變mMainContent的位置
mMainContent.layout(newLeft, 0, newLeft + mWidth, mHeight);
}
// 為了兼容低版本, 每次修改值之后, 進(jìn)行重繪
invalidate();
}
效果圖:

由上面的效果圖可以發(fā)現(xiàn)已經(jīng)可以實現(xiàn)當(dāng)手指向右滑動mLeftContent時,滑動的效果等于向右滑動mMainContent,當(dāng)同時也會發(fā)現(xiàn)一個問題,那就是手指在mLeftContent向左滑動的時候并沒有效果,這是因為我們限制了子View的滑動范圍就是0-mRange,所以,如果滑動時小于0是沒有效果的.那如果我們想要實現(xiàn)在mLeftContent當(dāng)手指有向左滑動的趨勢,或者手指在mMainContent有向左滑動的趨勢時,就關(guān)閉mLeftContent,讓mMainContent自動向左滑動到x=0的位置,反之就是打開mLeftContent,讓mMainContent滑動到x=mRange的位置,這個要怎么實現(xiàn)呢?首先我們要能夠想到的時,這個向左滑動的趨勢肯定是與手指松手后相關(guān)的,那有沒有一個回調(diào)方法是與手指觸摸松開相關(guān)的呢?下面將介紹另一個回調(diào)方法,如下所示:
/**
* 當(dāng)被拖拽的View釋放的時候回調(diào),通常用于執(zhí)行收尾的操作(例如執(zhí)行動畫)
* @param releasedChild 被釋放的View
* @param xvel 水平方向的速度,向右釋放為正值,向左為負(fù)值
* @param yvel 垂直方向的速度,向下釋放為正值,向上為負(fù)值
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
Log.d(TAG, "View被釋放onViewReleased:" + "釋放時水平速度xvel:" + xvel + " 釋放時垂直速度yvel:" + yvel);
super.onViewReleased(releasedChild, xvel, yvel);
}
有了這個方法,我們就可以實現(xiàn)我們剛剛說到的效果了,首先我們來考慮下那些情況是和打開mLeftContent相關(guān)的,有2種情況:
1.當(dāng)前水平方向的速度xvel=0,同時mMainContent的x位置是大于mRange/2.0f的.
2.當(dāng)前水平方向的速度xvel>0
考慮了所有打開的情況,那么剩下的情況就是關(guān)閉mLeftContent.
具體邏輯如下:
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
Log.d(TAG, "View被釋放onViewReleased:" + "釋放時水平速度xvel:" + xvel + " 釋放時垂直速度yvel:" + yvel);
super.onViewReleased(releasedChild, xvel, yvel);
if (xvel > 0 || (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f)) {
//打開mLeftContent,即mMainContent的x=mRange
mMainContent.layout(mRange, 0, mRange + mWidth, mHeight);
} else {
//關(guān)閉mLeftContent,即mMainContent的x=0
mMainContent.layout(0, 0, mWidth, mHeight);
}
}
效果圖:

細(xì)心的話,可以發(fā)現(xiàn)上面的打開和關(guān)閉動畫都是瞬間完成的,看起來效果不怎么好,如何實現(xiàn)平滑的打開和關(guān)閉呢?
通過ViewDragHelper的smoothSlideViewTo(View child, int finalLeft, int finalTop)方法可以實現(xiàn)平滑的滾動目標(biāo)View到指定的left或者top坐標(biāo)位置,接收3個參數(shù),參數(shù)child表示要滑動的目標(biāo)View,finalLeft和finalTop表示目標(biāo)view最終平滑滑動到的位置.翻看源碼,發(fā)現(xiàn)其實現(xiàn)原理是通過Scroller對象來實現(xiàn)的,也就說我們還需要重寫computeScroll方法,不斷的刷新當(dāng)前界面,具體設(shè)置如下:
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
Log.d(TAG, "View被釋放onViewReleased:" + "釋放時水平速度xvel:" + xvel + " 釋放時垂直速度yvel:" + yvel);
super.onViewReleased(releasedChild, xvel, yvel);
if (xvel > 0 || (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f)) {
//打開mLeftContent,即mMainContent的x=mRange
if (dragHelper.smoothSlideViewTo(mMainContent, mRange, 0)) {
// 返回true代表還沒有移動到指定位置, 需要刷新界面.
// 參數(shù)傳this(child所在的ViewGroup)
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
} else {
//關(guān)閉mLeftContent,即mMainContent的x=0
if (dragHelper.smoothSlideViewTo(mMainContent, 0, 0)) {
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
}
@Override
public void computeScroll() {
super.computeScroll();
// 2. 持續(xù)平滑動畫 (高頻率調(diào)用)
if(dragHelper.continueSettling(true)){
// 如果返回true, 動畫還需要繼續(xù)執(zhí)行
ViewCompat.postInvalidateOnAnimation(this);
}
}
效果如下:

總結(jié)
以上所述是小編給大家介紹的Android ViewDragHelper使用介紹,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- Android利用ViewDragHelper輕松實現(xiàn)拼圖游戲的示例
- Android ViewDragHelper仿淘寶拖動加載效果
- Android 中通過ViewDragHelper實現(xiàn)ListView的Item的側(cè)拉劃出效果
- Android ViewDragHelper完全解析 自定義ViewGroup神器
- Android基于ViewDragHelper仿QQ5.0側(cè)滑界面效果
- Android使用ViewDragHelper實現(xiàn)仿QQ6.0側(cè)滑界面(一)
- Android使用ViewDragHelper實現(xiàn)QQ6.X最新版本側(cè)滑界面效果實例代碼
- Android ViewDragHelper使用方法詳解
相關(guān)文章
Android編程實現(xiàn)google消息通知功能示例
這篇文章主要介紹了Android編程實現(xiàn)google消息通知功能,結(jié)合具體實例形式分析了Android消息處理及C#服務(wù)器端與google交互的相關(guān)操作技巧,需要的朋友可以參考下2017-06-06
android中Fragment+RadioButton實現(xiàn)底部導(dǎo)航欄
本篇文章主要介紹了android中Fragment+RadioButton實現(xiàn)底部導(dǎo)航欄,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03
android編程開發(fā)之全屏和退出全屏的實現(xiàn)方法
這篇文章主要介紹了android編程開發(fā)之全屏和退出全屏的實現(xiàn)方法,以實例形式較為詳細(xì)的分析了Android全屏及退出全屏的頁面布局與功能實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
android異步任務(wù)設(shè)計思詳解(AsyncTask)
AsyncTask在Android十分常用,那為什么如此常用呢,不用行不行呢,內(nèi)部又是怎么實現(xiàn)的呢,為什么Java的API中沒有這個類呢,看完本文后,你將會知道答案2014-02-02
Android使用ViewPager快速切換Fragment時卡頓的優(yōu)化方案
今天小編就為大家分享一篇關(guān)于Android使用ViewPager快速切換Fragment時卡頓的優(yōu)化方案,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12
Android應(yīng)用圖標(biāo)上的小紅點Badge實踐代碼
本篇文章主要介紹了Android應(yīng)用圖標(biāo)上的小紅點Badge實踐代碼,具有一定的參考價值,有興趣的可以了解一下2017-07-07
Android編程開發(fā)之打開文件的Intent及使用方法
這篇文章主要介紹了Android編程開發(fā)之打開文件的Intent及使用方法,已實例形式分析了Android打開文件Intent的相關(guān)布局及功能實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10

