美文网首页Android
Android中神奇的ViewDragHelper

Android中神奇的ViewDragHelper

作者: 笔墨Android | 来源:发表于2018-03-30 12:07 被阅读798次

    XSize的主页

    参考文献:
    https://blog.csdn.net/briblue/article/details/73730386
    https://www.jianshu.com/p/111a7bc76a0e

    ViewDragHelper是针对 ViewGroup 中的拖拽和重新定位 views 操作时提供了一系列非常有用的方法和状态追踪。基本上使用在自定义ViewGroup处理拖拽中!

    所谓无图无真相。。。


    终极目标回弹效果

    本文知识点:

    • 常用API的说明
    • ViewDragHelper的使用案例
    • 常见的使用案例

    1.常用API说明

    这里为什么先介绍API呢?因为上来我贴出来一堆代码你都不知道每个方法什么意思,会很痛苦的,而且还要跳着看。所以这里决定先介绍一下API大家先有个印象,后面说到的时候也不会那么陌生,好了废话不多说了!

    ViewDragHelper的API

    • ViewDragHelper create(ViewGroup forParent, Callback cb);一个静态的创建方法,
      • 参数1:出入的是相应的ViewGroup
      • 参数2:是一个回掉(其实这个回掉你可以自己在外面实现,后面在细说)
    • shouldInterceptTouchEvent(MotionEvent ev) 处理事件分发的(怎么说这个方法呢?主要是将ViewGroup的事件分发,委托给ViewDragHelper进行处理)
      • 参数1:MotionEvent ev 主要是ViewGroup的事件
    • processTouchEvent(MotionEvent event) 处理相应TouchEvent的方法,这里要注意一个问题,处理相应的TouchEvent的时候要将结果返回为true,消费本次事件!否则将无法使用ViewDragHelper处理相应的拖拽事件!

    基本上使用到的就这三个API就能实现相应的拖拽效果了!(有的API我没有查看,好像还有很多API但是有的真的不怎么理解!原谅我的放荡不羁。。。)

    ViewDragHelper.Callback的API(也就是创建ViewDragHelper传入的回调方法)

    其实这个回掉主要是用来监听一些内容的,其实你可以这样,自己实现一个类,继承这个类,然后在里面写相应的逻辑,这样代码能比较整洁!也便于其他人的观看

    • tryCaptureView(View child, int pointerId) 这是一个抽象类,必须去实现,也只有在这个方法返回true的时候下面的方法才会生效;

      • 参数1:捕获的View(也就是你拖动的这个View)
      • 参数2:这个参数我也不知道什么意思API中写的一个什么指针,这里没有到也没有注意
    • onViewDragStateChanged(int state) 当状态改变的时候回调,返回相应的状态(这里有三种状态)

      • STATE_IDLE 闲置状态
      • STATE_DRAGGING 正在拖动
      • STATE_SETTLING 放置到某个位置
    • onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 当你拖动的View位置发生改变的时候回调

      • 参数1:你当前拖动的这个View
      • 参数2:距离左边的距离
      • 参数3:距离右边的距离
      • 参数4:x轴的变化量
      • 参数5:y轴的变化量
    • onViewCaptured(View capturedChild, int activePointerId)捕获View的时候调用的方法

      • 参数1:捕获的View(也就是你拖动的这个View)
      • 参数2:这个参数我也不知道什么意思API中写的一个什么指针,这里没有到也没有注意
    • onViewReleased(View releasedChild, float xvel, float yvel) 当View停止拖拽的时候调用的方法,一般在这个方法中重置一些参数,比如回弹什么的。。。

      • 参数1:你拖拽的这个View
      • 参数2:x轴的速率
      • 参数3:y轴的速率
    • clampViewPositionVertical(View child, int top, int dy) 竖直拖拽的时候回调的方法

      • 参数1:拖拽的View
      • 参数2:距离顶部的距离
      • 参数3:变化量
    • clampViewPositionHorizontal(View child, int left, int dx) 水平拖拽的时候回调的方法

      • 参数1:拖拽的View
      • 参数2:距离左边的距离
      • 参数3:变化量

    基本上常用的API就这么多,也都解释了相应参数代表的意思,可能有些不是那么准确,还请指正。。。

    2. ViewDragHelper的使用案例

    2.1案例一:

    自定义一个LinearLayout使其内部的View可以进行相应的拖动

    2.1.1 创建相应的回调CallBack(其实就是创建一个相应的ViewDragHelper.Callback对象)这个主要是用于把代码拆分到外面去。。。代码如下

    public class MyDragHelper extends ViewDragHelper.Callback {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            //注意这里一定要返回true,否则后续的拖拽回调是不会生效的
            return true;
        }
    
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }
    
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return top;
        }
    }
    

    这里主要处理了内部控件的拖拽问题;

    2.1.2自定义一个LinearLayout,授权相应的拖拽。代码如下:

    public class MyDragLinearLayout extends LinearLayout {
    
    
        private ViewDragHelper mViewDragHelper;
    
        public MyDragLinearLayout(Context context) {
            this(context, null);
        }
    
        public MyDragLinearLayout(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public MyDragLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            MyDragHelper dragHelper = new MyDragHelper();
            mViewDragHelper = ViewDragHelper.create(this, dragHelper);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return mViewDragHelper.shouldInterceptTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            mViewDragHelper.processTouchEvent(event);
            return true;
        }
    }
    

    上面这段代码就是在View初始话的时候,创建了一个回调和一个ViewDragHelper对象,在onInterceptTouchEvent和onTouchEvent处理一下相应的逻辑(这里有一点千万要注意就是在onTouchEvent的时候一定要返回true,切记!!!

    然后直接在布局文件中使用这个自定义的ViewGroup就可以了。

    3.常见的使用案例

    1.回弹效果

    回弹效果是最常见的了,也是最主要的内容就是回弹的效果!!!其实使用方法和上面是类似的!

    1.1委托相应的服务是一样的,这里就直接贴上相应的代码了。

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return mViewDragHelper.shouldInterceptTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            mViewDragHelper.processTouchEvent(event);
            return true;
        }
    

    1.2获取捕获的View,并获取捕获时候的位置信息

       @Override
       public void onViewCaptured(View capturedChild, int activePointerId) {
             super.onViewCaptured(capturedChild, activePointerId);
             mLeft = capturedChild.getLeft();
             mTop = capturedChild.getTop();
       }
    

    这里主要是通过相应的捕获的回调获取相应的位置信息,主要是作用是为了之后回弹到原始位置提供相应的位置信息;

    1.3在释放的时候重置这个位置信息

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            mViewDragHelper.settleCapturedViewAt(mLeft, mTop);
            invalidate();
        }
    

    这里主要是通过ViewDragHelper的settleCapturedViewAt(int finalLeft, int finalTop)方法使你拖拽的View回到上面获取的原始位置了。。。但是其实这样是不行的,为什么呢???上面这个确实是设置拖拽View位置的,但是查看API的时候有这样一段描述信息

    If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
    on each subsequent frame to continue the motion until it returns false. If this method
    returns false there is no further work to do to complete the movement.
    

    简单的翻译一下:
    如果这个方法返回true,调用者应该调用{ @link #continueSettling(boolean)}在每个后续帧继续运动,直到返回false。如果这个方法返回false,没有进一步的工作来完成运动。

    也就是说这里你要通过这个方法进行相应的处理,但是怎么处理呢?请看下面这段代码:

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

    这个是重写的ViewGroup的方法,主要是用于ViewGroup中更新相应View的(通过ScrollX
    和ScrollY)通过上面的代码就能实现回弹了。整体代码如下:

    public class MyDragLinearLayout extends LinearLayout {
    
    
        private ViewDragHelper mViewDragHelper;
    
        public MyDragLinearLayout(Context context) {
            this(context, null);
        }
    
        public MyDragLinearLayout(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public MyDragLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            mViewDragHelper = ViewDragHelper.create(this, new ViewDragHelper.Callback() {
    
                private int mLeft;
                private int mTop;
    
                @Override
                public boolean tryCaptureView(View child, int pointerId) {
                    return true;
                }
    
                @Override
                public void onViewCaptured(View capturedChild, int activePointerId) {
                    super.onViewCaptured(capturedChild, activePointerId);
                    mLeft = capturedChild.getLeft();
                    mTop = capturedChild.getTop();
                }
    
                @Override
                public int clampViewPositionVertical(View child, int top, int dy) {
                    return top;
                }
    
                @Override
                public int clampViewPositionHorizontal(View child, int left, int dx) {
                    return left;
    
                }
    
                @Override
                public void onViewReleased(View releasedChild, float xvel, float yvel) {
                    super.onViewReleased(releasedChild, xvel, yvel);
                    mViewDragHelper.settleCapturedViewAt(mLeft, mTop);
                    invalidate();
                }
            });
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return mViewDragHelper.shouldInterceptTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            mViewDragHelper.processTouchEvent(event);
            return true;
        }
    
    
        @Override
        public void computeScroll() {
            if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
                invalidate();
            }
        }
    }
    

    其实对这个API也只是表层的理解,如有什么不对的地方请指出。。。

    相关文章

      网友评论

        本文标题:Android中神奇的ViewDragHelper

        本文链接:https://www.haomeiwen.com/subject/isbmcftx.html