
ViewDragHelper作為官方推出的手勢(shì)滑動(dòng)輔助工具,極大的簡(jiǎn)化了我們對(duì)手勢(shì)滑動(dòng)的處理邏輯,v4包中的SlidingPaneLayout和DrawerLayout內(nèi)部都有ViewDragHelper的身影,這里對(duì)這個(gè)強(qiáng)大的輔助工具類使用以及相關(guān)方法做個(gè)系統(tǒng)性的總結(jié)。
全文思路:
一、用ViewDragHelper實(shí)現(xiàn)一個(gè)簡(jiǎn)單效果,并對(duì)ViewDragHelper使用的常見(jiàn)思路進(jìn)行總結(jié)
二、對(duì)ViewDragHelper相關(guān)API進(jìn)行歸納分析
1、ViewDragHelper
** 2、ViewDragHelper.CallBack**
一、用ViewDragHelper實(shí)現(xiàn)一個(gè)簡(jiǎn)單的效果,對(duì)其有個(gè)初步的認(rèn)識(shí)
用個(gè)在項(xiàng)目中實(shí)現(xiàn)的簡(jiǎn)單效果來(lái)看下吧:

這個(gè)實(shí)現(xiàn)思路也很簡(jiǎn)單,我們看下代碼:
public class MyDragViewLayout extends ViewGroup{
public ViewDragHelper mViewDragHelper;
private boolean isOpen = true;
private View mMenuView;
private View mContentView;
private int mCurrentTop = 0;
public MyDragViewLayout(Context context) {
super(context);
init();
}
public MyDragViewLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyDragViewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//ViewDragHelper靜態(tài)方法傳入ViewDragHelperCallBack創(chuàng)建
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelperCallBack());
// mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP);
}
//實(shí)現(xiàn)ViewDragHelper.Callback相關(guān)方法
private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
//返回ture則表示可以捕獲該view
return child == mContentView;
}
@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
//setEdgeTrackingEnabled設(shè)置的邊界滑動(dòng)時(shí)觸發(fā)
//通過(guò)captureChildView對(duì)其進(jìn)行捕獲,該方法可以繞過(guò)tryCaptureView
//mViewDragHelper.captureChildView(mContentView, pointerId);
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
//手指觸摸移動(dòng)時(shí)回調(diào), left表示要到的x坐標(biāo)
return super.clampViewPositionHorizontal(child, left, dx);//
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
//手指觸摸移動(dòng)時(shí)回調(diào) top表示要到達(dá)的y坐標(biāo)
return Math.max(Math.min(top, mMenuView.getHeight()), 0);
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//手指抬起釋放時(shí)回調(diào)
int finalTop = mMenuView.getHeight();
if(yvel <= 0){
if(releasedChild.getTop()< mMenuView.getHeight()/2){
finalTop = 0;
}else{
finalTop = mMenuView.getHeight();
}
}else{
if(releasedChild.getTop() > mMenuView.getHeight()/2){
finalTop = mMenuView.getHeight();
}else{
finalTop = 0;
}
}
mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), finalTop);
invalidate();
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
//mDrawerView完全覆蓋屏幕則防止過(guò)度繪制
mMenuView.setVisibility((changedView.getHeight() - top == getHeight()) ? View.GONE : View.VISIBLE);
mCurrentTop +=dy;
requestLayout();
}
@Override
public int getViewVerticalDragRange(View child) {
if (mMenuView == null) return 0;
return (mContentView == child) ? mMenuView.getHeight() : 0;
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
if (state == ViewDragHelper.STATE_IDLE) {
isOpen = (mContentView.getTop() == mMenuView.getHeight());
}
}
}
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
public boolean isDrawerOpened() {
return isOpen;
}
//onInterceptTouchEvent方法調(diào)用ViewDragHelper.shouldInterceptTouchEvent
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
//onTouchEvent方法中調(diào)用ViewDragHelper.processTouchEvent方法并返回true
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(measureWidth, measureHeight);
measureChildren(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mContentView = getChildAt(1);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mMenuView.layout(0, 0,
mMenuView.getMeasuredWidth(),
mMenuView.getMeasuredHeight());
mContentView.layout(0, mCurrentTop + mMenuView.getHeight(),
mContentView.getMeasuredWidth(),
mCurrentTop + mContentView.getMeasuredHeight() + mMenuView.getHeight());
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<com.mrzk.myapplication.MyDragViewLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="?attr/colorAccent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Menu"
android:layout_centerInParent="true"
android:textSize="22sp"
android:textColor="#fff"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Content"
android:textSize="22sp"
android:textColor="#fff"/>
</RelativeLayout>
</com.mrzk.myapplication.MyDragViewLayout>
我們縷一下思路,
第一步:在init方法中用ViewDragHelper的靜態(tài)方法實(shí)例化ViewDragHelper對(duì)象,其中第一個(gè)參數(shù)指的當(dāng)前的ViewGroup,第二個(gè)sensitivity參數(shù)指的是對(duì)滑動(dòng)檢測(cè)的敏感度,越大越敏感,默認(rèn)傳1即可。第三個(gè)參數(shù)為靜態(tài)回調(diào)對(duì)象CallBack,我們實(shí)現(xiàn)相關(guān)CallBack方法來(lái)操作拖拽的View。
第二步:實(shí)現(xiàn)ViewDragHelper.Callback的相關(guān)方法。
第三步:在onInterceptTouchEvent方法中調(diào)用mViewDragHelper.shouldInterceptTouchEvent(ev)將事件傳給ViewDragHelper。
第四步:在onTouchEvent方法中調(diào)用ViewDragHelper.processTouchEvent方法并返回true。
二、對(duì)ViewDragHelper相關(guān)API進(jìn)行歸納分析
1、ViewDragHelper

public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)
sensitivity越大,對(duì)滑動(dòng)的檢測(cè)就越敏感,默認(rèn)傳1即可
public void setEdgeTrackingEnabled(int edgeFlags)
設(shè)置允許父View的某個(gè)邊緣可以用來(lái)響應(yīng)托拽事件,
public boolean shouldInterceptTouchEvent(MotionEvent ev)
在父view onInterceptTouchEvent方法中調(diào)用
public void processTouchEvent(MotionEvent ev)
在父view onTouchEvent方法中調(diào)用
public void captureChildView(View childView, int activePointerId)
在父View內(nèi)捕獲指定的子view用于拖曳,會(huì)回調(diào)tryCaptureView()
public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop)
某個(gè)View自動(dòng)滾動(dòng)到指定的位置,初速度為0,可在任何地方調(diào)用,動(dòng)畫移動(dòng)會(huì)回調(diào)continueSettling(boolean)方法,直到結(jié)束
public boolean settleCapturedViewAt(int finalLeft, int finalTop)
以松手前的滑動(dòng)速度為初值,讓捕獲到的子View自動(dòng)滾動(dòng)到指定位置,只能在Callback的onViewReleased()中使用,其余同上
public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)
以松手前的滑動(dòng)速度為初值,讓捕獲到的子View在指定范圍內(nèi)fling慣性運(yùn)動(dòng),只能在Callback的onViewReleased()中使用,其余同上
public boolean continueSettling(boolean deferCallbacks)
在調(diào)用settleCapturedViewAt()、flingCapturedView()和smoothSlideViewTo()時(shí),該方法返回true,一般重寫父view的computeScroll方法,進(jìn)行該方法判斷
public void abort()
中斷動(dòng)畫
在ViewDragHelper的滑動(dòng)中共有三個(gè)方法可以調(diào)用,smoothSlideViewTo、settleCapturedViewAt、flingCapturedView,動(dòng)畫移動(dòng)會(huì)回調(diào)continueSettling(boolean)方法,在內(nèi)部是用的ScrollerCompat來(lái)實(shí)現(xiàn)滑動(dòng)的。
在computeScroll方法中判斷continueSettling(boolean)的返回值,來(lái)動(dòng)態(tài)刷新界面:
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
2、ViewDragHelper.CallBack

**public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) **
被拖拽的View位置變化時(shí)回調(diào),changedView為位置變化的view,left、top變化后的x、y坐標(biāo),dx、dy為新位置與舊位置的偏移量
public void onViewDragStateChanged(int state)
當(dāng)ViewDragHelper狀態(tài)發(fā)生變化時(shí)回調(diào)(STATE_IDLE,STATE_DRAGGING,STATE_SETTLING)
public void onViewCaptured(View capturedChild, int activePointerId)
成功捕獲到子View時(shí)或者手動(dòng)調(diào)用captureChildView()時(shí)回調(diào)
public void onViewReleased(View releasedChild, float xvel, float yvel)
當(dāng)前拖拽的view松手或者ACTION_CANCEL時(shí)調(diào)用,xvel、yvel為離開屏幕時(shí)的速率
public void onEdgeTouched(int edgeFlags, int pointerId)
當(dāng)觸摸到邊界時(shí)回調(diào)
public boolean onEdgeLock(int edgeFlags)
true的時(shí)候會(huì)鎖住當(dāng)前的邊界,false則unLock。鎖定后的邊緣就不會(huì)回調(diào)onEdgeDragStarted()
public void onEdgeDragStarted(int edgeFlags, int pointerId)
ACTION_MOVE且沒(méi)有鎖定邊緣時(shí)觸發(fā),在此可手動(dòng)調(diào)用captureChildView()觸發(fā)從邊緣拖動(dòng)子View
public int getOrderedChildIndex(int index)
尋找當(dāng)前觸摸點(diǎn)View時(shí)回調(diào)此方法,如需改變遍歷子view順序可重寫此方法
public int getViewHorizontalDragRange(View child)
返回拖拽子View在相應(yīng)方向上可以被拖動(dòng)的最遠(yuǎn)距離,默認(rèn)為0
public int getViewVerticalDragRange(View child)
返回拖拽子View在相應(yīng)方向上可以被拖動(dòng)的最遠(yuǎn)距離,默認(rèn)為0
public abstract boolean tryCaptureView(View child, int pointerId)
對(duì)觸摸view判斷,如果需要當(dāng)前觸摸的子View進(jìn)行拖拽移動(dòng)就返回true,否則返回false
public int clampViewPositionHorizontal(View child, int left, int dx)
拖拽的子View在所屬方向上移動(dòng)的位置,child為拖拽的子View,left為子view應(yīng)該到達(dá)的x坐標(biāo),dx為挪動(dòng)差值
public int clampViewPositionVertical(View child, int top, int dy)
同上,top為子view應(yīng)該到達(dá)的y坐標(biāo)