Android中神奇的ViewDragHelper

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 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,805评论 25 709
  • 内容是博主照着书敲出来的,博主码字挺辛苦的,转载请注明出处,后序内容陆续会码出。 当了解了Android坐标系和触...
    Blankj阅读 6,698评论 3 61
  • 过程不重要,结局最终零点零…………………………………………
    与遇阅读 127评论 0 0
  • 这本超级长的小说看到一半,所以只能说是半解。从作者:灰熊猫的笔名来看,应该是定期更新的网络小说。这一类新兴的作品倒...
    geoeee阅读 332评论 0 2