自定義控件輔助神器ViewDragHelper

ViewDragHelper

ViewDragHelper作為官方推出的手勢滑動輔助工具,極大的簡化了我們對手勢滑動的處理邏輯,v4包中的SlidingPaneLayout和DrawerLayout內部都有ViewDragHelper的身影,這里對這個強大的輔助工具類使用以及相關方法做個系統性的總結。
全文思路:

一、用ViewDragHelper實現一個簡單效果,并對ViewDragHelper使用的常見思路進行總結
二、對ViewDragHelper相關API進行歸納分析
1、ViewDragHelper
** 2、ViewDragHelper.CallBack**

一、用ViewDragHelper實現一個簡單的效果,對其有個初步的認識

用個在項目中實現的簡單效果來看下吧:

ViewDragHelper.demo

這個實現思路也很簡單,我們看下代碼:

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靜態方法傳入ViewDragHelperCallBack創建
            mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelperCallBack());
//          mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP);
        }
      //實現ViewDragHelper.Callback相關方法
        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設置的邊界滑動時觸發
                //通過captureChildView對其進行捕獲,該方法可以繞過tryCaptureView

                //mViewDragHelper.captureChildView(mContentView, pointerId);
            }

            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                //手指觸摸移動時回調, left表示要到的x坐標
                return super.clampViewPositionHorizontal(child, left, dx);//
            }

            @Override
            public int clampViewPositionVertical(View child, int top, int dy) {
                //手指觸摸移動時回調 top表示要到達的y坐標
                return Math.max(Math.min(top, mMenuView.getHeight()), 0);
            }

            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                //手指抬起釋放時回調
                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完全覆蓋屏幕則防止過度繪制
                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方法調用ViewDragHelper.shouldInterceptTouchEvent
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return mViewDragHelper.shouldInterceptTouchEvent(ev);
        }

        //onTouchEvent方法中調用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的靜態方法實例化ViewDragHelper對象,其中第一個參數指的當前的ViewGroup,第二個sensitivity參數指的是對滑動檢測的敏感度,越大越敏感,默認傳1即可。第三個參數為靜態回調對象CallBack,我們實現相關CallBack方法來操作拖拽的View。
第二步:實現ViewDragHelper.Callback的相關方法。
第三步:在onInterceptTouchEvent方法中調用mViewDragHelper.shouldInterceptTouchEvent(ev)將事件傳給ViewDragHelper。
第四步:在onTouchEvent方法中調用ViewDragHelper.processTouchEvent方法并返回true。

二、對ViewDragHelper相關API進行歸納分析

1、ViewDragHelper

ViewDragHelper方法

public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)
sensitivity越大,對滑動的檢測就越敏感,默認傳1即可

public void setEdgeTrackingEnabled(int edgeFlags)
設置允許父View的某個邊緣可以用來響應托拽事件,

public boolean shouldInterceptTouchEvent(MotionEvent ev)
在父view onInterceptTouchEvent方法中調用

public void processTouchEvent(MotionEvent ev)
在父view onTouchEvent方法中調用

public void captureChildView(View childView, int activePointerId)
在父View內捕獲指定的子view用于拖曳,會回調tryCaptureView()

public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop)
某個View自動滾動到指定的位置,初速度為0,可在任何地方調用,動畫移動會回調continueSettling(boolean)方法,直到結束

public boolean settleCapturedViewAt(int finalLeft, int finalTop)
以松手前的滑動速度為初值,讓捕獲到的子View自動滾動到指定位置,只能在Callback的onViewReleased()中使用,其余同上

public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)
以松手前的滑動速度為初值,讓捕獲到的子View在指定范圍內fling慣性運動,只能在Callback的onViewReleased()中使用,其余同上

public boolean continueSettling(boolean deferCallbacks)
在調用settleCapturedViewAt()、flingCapturedView()和smoothSlideViewTo()時,該方法返回true,一般重寫父view的computeScroll方法,進行該方法判斷

public void abort()
中斷動畫

ViewDragHelper的滑動中共有三個方法可以調用,smoothSlideViewTo、settleCapturedViewAtflingCapturedView,動畫移動會回調continueSettling(boolean)方法,在內部是用的ScrollerCompat來實現滑動的。
在computeScroll方法中判斷continueSettling(boolean)的返回值,來動態刷新界面:

 @Override
        public void computeScroll() {
            if (mViewDragHelper.continueSettling(true)) {
                invalidate();
            }
        }

2、ViewDragHelper.CallBack

CallBack方法

**public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) **
被拖拽的View位置變化時回調,changedView為位置變化的view,left、top變化后的x、y坐標,dx、dy為新位置與舊位置的偏移量

public void onViewDragStateChanged(int state)
當ViewDragHelper狀態發生變化時回調(STATE_IDLE,STATE_DRAGGING,STATE_SETTLING)

public void onViewCaptured(View capturedChild, int activePointerId)
成功捕獲到子View時或者手動調用captureChildView()時回調

public void onViewReleased(View releasedChild, float xvel, float yvel)
當前拖拽的view松手或者ACTION_CANCEL時調用,xvel、yvel為離開屏幕時的速率

public void onEdgeTouched(int edgeFlags, int pointerId)
當觸摸到邊界時回調

public boolean onEdgeLock(int edgeFlags)
true的時候會鎖住當前的邊界,false則unLock。鎖定后的邊緣就不會回調onEdgeDragStarted()

public void onEdgeDragStarted(int edgeFlags, int pointerId)
ACTION_MOVE且沒有鎖定邊緣時觸發,在此可手動調用captureChildView()觸發從邊緣拖動子View

public int getOrderedChildIndex(int index)
尋找當前觸摸點View時回調此方法,如需改變遍歷子view順序可重寫此方法

public int getViewHorizontalDragRange(View child)
返回拖拽子View在相應方向上可以被拖動的最遠距離,默認為0

public int getViewVerticalDragRange(View child)
返回拖拽子View在相應方向上可以被拖動的最遠距離,默認為0

public abstract boolean tryCaptureView(View child, int pointerId)
對觸摸view判斷,如果需要當前觸摸的子View進行拖拽移動就返回true,否則返回false

public int clampViewPositionHorizontal(View child, int left, int dx)
拖拽的子View在所屬方向上移動的位置,child為拖拽的子View,left為子view應該到達的x坐標,dx為挪動差值

public int clampViewPositionVertical(View child, int top, int dy)
同上,top為子view應該到達的y坐標

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容