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

全文思路:

一、用ViewDragHelper实现一个简单效果,并对ViewDragHelper使用的常见思路进行总结

二、对ViewDragHelper相关API进行归纳分析

1、ViewDragHelper

2、ViewDragHelper.CallBack

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

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

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

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

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的滑动中共有三个方法可以调用,smoothSlideViewTosettleCapturedViewAtflingCapturedView,动画移动会回调continueSettling(boolean)方法,在内部是用的ScrollerCompat来实现滑动的。

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

 @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位置变化时回调,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坐标

自定义控件辅助神器ViewDragHelper的更多相关文章

  1. Windows下的开发辅助神器——Chocolate Package Manager

    Windows下的开发辅助神器——Chocolate Package Manager:https://juejin.im/post/5c6cb3acf265da2dc4537235 Windows上的 ...

  2. Mybatis辅助神器-MyBatis Log Plugin

    1. 问题描述 Java操作数据库的两台流行天王-mybatis和hibernate,mytabis和hibernate的区别不想废话了,以前用hibernate,最近几年一直用的mybatis,目前 ...

  3. Ocr答题辅助神器 OcrAnswerer4.x,通过百度OCR识别手机文字,支持屏幕窗口截图和ADB安卓截图,支持四十个直播App,可保存题库

    http://www.cnblogs.com/Charltsing/p/OcrAnswerer.html 联系qq:564955427 最新版为v4.1版,开放一定概率的八窗口体验功能,请截图体验(多 ...

  4. 渗透辅助神器 - DZGEN

    项目地址:https://github.com/joker25000/DZGEN git clone ┌─[root@sch01ar]─[/sch01ar] └──╼ #git clone https ...

  5. [转]Android自定义控件三部曲系列完全解析(动画, 绘图, 自定义View)

    来源:http://blog.csdn.net/harvic880925/article/details/50995268 一.自定义控件三部曲之动画篇 1.<自定义控件三部曲之动画篇(一)—— ...

  6. 自定义View其实很简单系列1-12

    作者: AigeStudio  http://blog.csdn.net/aigestudio 说明:文中的1/12表示12篇中的第1篇, 1/6=2/12表示12篇中的第2篇,其它类似. 自定义控件 ...

  7. 怎样做出优秀的扁平化设计风格 PPT 或 Keynote 幻灯片演示文稿?(装)

    不知道你有没有想过,为什么很人多的扁平化 PPT 是这个样子: 或者是这样: 然而,还有一小撮人的扁平化 PPT 却拥有那么高颜值: 为什么会产生这么大的差距呢?丑逼 PPT 应该如何逆袭成为帅逼呢? ...

  8. idea-plugin

    codehelper.generator https://github.com/zhengjunbase/codehelper.generator?id=5f5b0005-11fb-48e4-bdb7 ...

  9. 最全android Demo

    1.BeautifulRefreshLayout-漂亮的美食下拉刷新 https://github.com/android-cjj/BeautifulRefreshLayout/tree/Beauti ...

随机推荐

  1. [NOI 2016]区间

    Description 在数轴上有 $n$ 个闭区间 $[l_1,r_1],[l_2,r_2],...,[l_n,r_n]$.现在要从中选出 $m$ 个区间,使得这 $m$ 个区间共同包含至少一个位置 ...

  2. [WC2013]糖果公园

    Description 题库链接 给你一棵 $n$ 个节点,有 $m$种颜色的树.每个节点上有一个颜色.定义一条树上路径的价值为 $sum_c V_c(\sum_{i=1}^{tim_c}W_i)$ ...

  3. bzoj2149拆迁队 斜率优化dp+分治

    2149: 拆迁队 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 397  Solved: 177[Submit][Status][Discuss] ...

  4. 【集训第三天·疯狂训练】哦,顺带学习了manacher

    虽然说是疯狂训练吧,但是也没写多少题,就把伸展树的操作熟悉了一下,ac了5个题目. 一整天没啥可吐槽的,除了昨天在机房打游戏的某位朋友翻车后和教练谈了谈心2333 说题吧.. 1.BZOJ1208 H ...

  5. bzoj2006 NOI2010 数据结构+堆维护区间和最大

    2006: [NOI2010]超级钢琴 Time Limit: 20 Sec  Memory Limit: 552 MBSubmit: 3431  Solved: 1686[Submit][Statu ...

  6. 有源汇上下界可行流(POJ2396)

    题意:给出一个n*m的矩阵的每行和及每列和,还有一些格子的限制,求一组合法方案. 源点向行,汇点向列,连一条上下界均为和的边. 对于某格的限制,从它所在行向所在列连其上下界的边. 求有源汇上下界可行流 ...

  7. Python paramik

    本节内容 paramiko模块 1.paramiko模块 Python的paramiko模块,该模块机遇SSH用于连接远程服务器并执行相关操作 Python的paramiko模块,该模块机遇SSH用于 ...

  8. 垃圾回收机制(GC)

    垃圾收集器(GC)与内存分配策略 GC需要完成的三件事: 判断哪些内存需要回收 什么时候回收 如何回收 在java内存运行时区域的各个部分中,程序计数器.虚拟机栈.本地方法栈3个区域随线程而生,随线程 ...

  9. sql server 表分区

    背景: 一般情况下,我们建立数据库表时,表数据都存放在一个文件里. 但是如果是分区表的话,表数据就会按照你指定的规则分放到不同的文件里,把一个大的数据文件拆分为多个小文件,还可以把这些小文件放在不同的 ...

  10. form submit提交

    form内控件参数自动添加到url后,而自定义的url参数则不能添加到url后 $('#fm').form('submit', { url: 'Data/Diary.ashx?dt=' + new D ...