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

学习资料:

  • Android开发群英传
  • 鸿洋大神的Android ViewDragHelper完全解析 自定义ViewGroup神器

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. 简单使用

简单需求:
在屏幕上,一个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;
    }
}

布局文件:



    


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

Android 自定义View学习(十五)——ViewDragHelper入门学习_第1张图片
简单使用

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);
}

Android 自定义View学习(十五)——ViewDragHelper入门学习_第2张图片
移动范围

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


3.恢复到默认位置

当在竖直方向上,拖动不超过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

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. 边缘拖动

在使用一些侧滑的控件时,有些可以从手机屏幕最左侧边缘滑出,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中的其他回调方法

方法 作用
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常用方法

方法 作用
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.最后

都是方法的简单调用

本人很菜,有错误请指出

共勉 : )

你可能感兴趣的:(Android 自定义View学习(十五)——ViewDragHelper入门学习)