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也只是表层的理解,如有什么不对的地方请指出。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

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