自定義控件輔助神器ViewDragHelper

ViewDragHelper

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)看下吧:

ViewDragHelper.demo

這個(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

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)用,smoothSlideViewTosettleCapturedViewAt、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

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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容