Android 自定义View学习(十五)——ViewDragHelper入门学习

学习资料:


ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.

ViewDragHelper是一个自定义ViewGroup的工具类。内部提供了一系列属性和用户拖动状态,并且支持恢复

ViewDragHelper是解决各种滑动的终极绝招,几乎可以实现各种不同的的滑动、拖动


1. 简单使用 <p>

简单需求:
在屏幕上,一个TextView可以拖动,并且当拖动的距离位于屏幕上半部分1/2区域内,可以自己恢复原始位置

ViewDragHelper使用也有一个固定的模式

  1. 初始化ViewDragHer实例,并创建所需要的回调接口
  2. 处理事件拦截和事件的消费

代码:

public class DragView extends LinearLayout {
    private ViewDragHelper mViewDragHelper;

    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initDragHelper();
    }

    private void initDragHelper() {
        mViewDragHelper = ViewDragHelper.create(DragView.this, 1.0f, mDragCallback);
    }

    /**
     *  ViewDragHelper回调接口
     */
    private ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {//可以用来指定哪一个childView可以拖动
            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;
        }
    };

    @Override
    public boolean onInterceptHoverEvent(MotionEvent event) {//拦截事件
        return mViewDragHelper.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {//消费事件
        //将触摸事件传递给`ViewDragHelper`,必不可少
        mViewDragHelper.processTouchEvent(event);
        return true;
    }
}

布局文件:

<com.szlk.customview.custom.DragView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:padding="20dp"
        android:text="@string/drag__name"
        android:textColor="@android:color/white"
        android:textSize="30sp" />

</com.szlk.customview.custom.DragView>

布局文件很简单,就是包含一个TextView

简单使用

DragView内,TextView就可以任意拖动


在创建ViewDragHelper对象时,create()方法有两种形式

//方式 1
public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
    return new ViewDragHelper(forParent.getContext(), forParent, cb);
}

//方式2
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
    final ViewDragHelper helper = create(forParent, cb);
    helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
    return helper;
}

方式2多了第二个参数值,代表了灵敏度。sensitivity越大,helper.mTouchSlop越小,一般写为1.0f


ViewDragHelper.Callback mDragCallback内,重写了3个方法

@Override
public boolean tryCaptureView(View child, int pointerId) {
    return true;
}

这个方法可以用来指定哪一个childView可以进行拖动,通过重写onFinishInflate()来获取childView子控件,然后进行childView判断

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动
    return left;
}

@Override
public int clampViewPositionVertical(View child, int top, int dy) {//竖直拖动
    return top;
}

这两个方法默认返回值都为0
left代表在水平方向上,childView即将在x轴移动到目标坐标位置,dx代表较前一次的增量,left = child.getLeft()+dxtop同理,代表垂直方向上childView即将在y轴移动到目标坐标位置

虽然上面的代码实现了childView的拖动,但有些问题需要考虑优化


2.简单进行Padding优化

简单使用使用图中,首先一个问题便是TextView超出了屏幕范围,导致内容都无法显示完全。需要对lefttop进行修改

修改代码

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动
    final int leftPadding = getPaddingLeft();
    final int rightPadding = getWidth() - child.getWidth() - getPaddingRight();
    return Math.min(Math.max(left, leftPadding), rightPadding);
}

@Override
public int clampViewPositionVertical(View child, int top, int dy) {//竖直拖动
    final int topPadding = getPaddingTop();
    final int bottomPadding = getHeight() - child.getHeight() - getPaddingBottom();
    return Math.min(Math.max(top, topPadding), bottomPadding);
}

移动范围

可以移动的区域就是灰色区域,水平范围便是paddingLeft <= target <= getWidth()-child.getWidth()-getPaddingRight(),垂直方向同理。padding四边的值不一定相同。


3.恢复到默认位置 <p>

当在竖直方向上,拖动不超过DragView高度的一半,就会回弹到默认位置

完整代码

public class DragView extends LinearLayout {
    private ViewDragHelper mViewDragHelper;
    private Point initPoint;
    private View autoTextView;

    public DragView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initDragHelper();
    }

    private void initDragHelper() {
        mViewDragHelper = ViewDragHelper.create(DragView.this, 1.0f, mDragCallback);
        initPoint = new Point();
    }

    /**
     * ViewDragHelper回调接口
     */
    private ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动
            final int leftPadding = getPaddingLeft();
            final int rightPadding = getWidth() - child.getWidth() - leftPadding;
            final int newLeft = Math.min(Math.max(left, leftPadding), rightPadding);
            return newLeft;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {//竖直拖动
            final int topPadding = getPaddingTop();
            final int bottomPadding = getHeight() - child.getHeight() - topPadding;
            final int newTop = Math.min(Math.max(top, topPadding), bottomPadding);
            return newTop;
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {//拖动结束后
            super.onViewReleased(releasedChild, xvel, yvel);
            if (releasedChild == autoTextView && releasedChild.getTop()  < (getHeight()/2)){
                mViewDragHelper.smoothSlideViewTo(releasedChild,initPoint.x,initPoint.y);//平滑移动
                ViewCompat.postInvalidateOnAnimation(DragView.this);
            }
        }
    };

    @Override
    public boolean onInterceptHoverEvent(MotionEvent event) {//拦截事件
        return mViewDragHelper.shouldInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {//消费事件
        mViewDragHelper.processTouchEvent(event);
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mViewDragHelper.continueSettling(true)) {//不停计算位置后,自动移动
            ViewCompat.postInvalidateOnAnimation(DragView.this);//重新绘制
        }
    }
    /**
     * 完成解析布局xml文件
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        autoTextView =  getChildAt(0);
    }
    /**
     * 布局
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        initPoint.x = autoTextView.getLeft();
        initPoint.y = autoTextView.getTop();
    }
}

利用onLayout()方法,拿到TextView起始点一开始的初始化坐标( initPoint.x,initPoint.y),在onViewReleased()方法中,进行结束拖动后的处理


4.处理Button <p>

DragView中,添加一个Button或者给TextView添加android:clickable="true",android:longClickable="true",便不能进行拖动处理

原因根据前面学过的,Buttonclickable默认为ture,事件被消费了,DragView便不会再处理ACTION_MOVE,ACTION_UP 事件,Buttonclickable设置为false后,便可以拖动,但此时Button却不在可以点击

想要实现Button既可以点击又又可以拖动,需要在ViewDragHelper.Callback mDragCallback重写两个方法

@Override
public int getViewHorizontalDragRange(View child) {
    return getMeasuredWidth() - child.getMeasuredWidth();
}

@Override
public int getViewVerticalDragRange(View child) {
    return getMeasuredHeight() - child.getMeasuredHeight();
}

注意:
因为Button是可以点击的,当ACTION_DOWN事件发生时(也就是手指落在按钮上),之后的ACTION_MOVE,ACTION_UP便会由Button处理,需要从Button外的区域滑到Button内后,Button再才会跟随手指动作被拖动


5. 边缘拖动 <p>

在使用一些侧滑的控件时,有些可以从手机屏幕最左侧边缘滑出,ViewDragHelper.Callback mDragCallback提供了回调方法,使用有两个步骤

  • 第1步:指定边缘拖动目标控件

边缘拖动代码:

@Override
public void onEdgeDragStarted(int edgeFlags, int pointerId) {
    super.onEdgeDragStarted(edgeFlags, pointerId);
    mViewDragHelper.captureChildView(autoTextView,pointerId);
}

使用captureChildView()方法来指定childView主动进行边缘拖动回调方法操作


  • 第2步:在初始化ViewDragHelper时,指定边缘方向
mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);

方向共有5个值:

ViewDragHelper.EDGE_ALL 四周都可以
ViewDragHelper.EDGE_LEFT  左边缘
ViewDragHelper.EDGE_RIGHT 右边缘
ViewDragHelper.EDGE_TOP  顶部
ViewDragHelper.EDGE_BOTTOM 底部

6.Callback中的其他回调方法 <p>

方法 作用
onViewDragStateChanged(int state) ViewDragHelper拖动状态发生改变,STATE_IDLESTATE_DRAGGINGSTATE_SETTLING[自动滚动],分别对应0,1,2
onViewPositionChanged() 拖动目标childView位置发生改变
onViewCaptured(View capturedChild, int activePointerId) 调用captureChildView确定拖动目标时,回调此方法
onEdgeTouched(int edgeFlags, int pointerId) 触摸ViewGroup边缘
onEdgeLock(int edgeFlags) true的时候会锁住当前的边界,false则unLock
getOrderedChildIndex(int index) 默认返回传入的index,可以重写将控件重新排序

加上上面用过的方法,Callback的回调方法就这些


7. ViewDragHelper常用方法 <p>

方法 作用
cancel() 取消拖动
abort() 取消拖动的过程,直接将控件移动了指定位置
captureChildView(View childView, int activePointerId) 将指定的子控件移动到指定位置
continueSettling(boolean deferCallbacks) 自动不断计算位置后移动控件
smoothSlideViewTo(View child, int finalLeft, int finalTop) child平滑移动到指定的位置
settleCapturedViewAt(int finalLeft, int finalTop) 以手指离开时的速度为初速度,将控件移动到指定的位置
shouldInterceptTouchEvent(MotionEvent ev) 判断父容器是否应该拦截事件
processTouchEvent(MotionEvent ev) 处理触摸事件由父视图接收

其他的以后用到了再学习补充


8.最后

都是方法的简单调用

本人很菜,有错误请指出

共勉 : )

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

推荐阅读更多精彩内容