自定义控件辅助神器ViewDragHelper-LMLPHP

ViewDragHelper作为官方推出的手势滑动辅助工具,极大的简化了我们对手势滑动的处理逻辑,v4包中的SlidingPaneLayout和DrawerLayout内部都有ViewDragHelper的身影,这里对这个强大的辅助工具类使用以及相关方法做个系统性的总结。

全文思路:

一、用ViewDragHelper实现一个简单的效果,对其有个初步的认识

用个在项目中实现的简单效果来看下吧:

自定义控件辅助神器ViewDragHelper-LMLPHP

这个实现思路也很简单,我们看下代码:

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-LMLPHP

ViewDragHelper的滑动中共有三个方法可以调用,smoothSlideViewTosettleCapturedViewAtflingCapturedView,动画移动会回调continueSettling(boolean)方法,在内部是用的ScrollerCompat来实现滑动的。

在computeScroll方法中判断continueSettling(boolean)的返回值,来动态刷新界面:

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

2、ViewDragHelper.CallBack

自定义控件辅助神器ViewDragHelper-LMLPHP

05-11 13:26