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

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

简单使用

DragView内,TextView就可以任意拖动


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

//方式 1public static ViewDragHelper create(ViewGroup forParent, Callback cb) {    return new ViewDragHelper(forParent.getContext(), forParent, cb);}//方式2public 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个方法

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

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

@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {// 水平拖动    return left;}@Overridepublic 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进行修改

修改代码

@Overridepublic 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);}@Overridepublic 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.恢复到默认位置

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

@Overridepublic int getViewHorizontalDragRange(View child) {    return getMeasuredWidth() - child.getMeasuredWidth();}@Overridepublic int getViewVerticalDragRange(View child) {    return getMeasuredHeight() - child.getMeasuredHeight();}

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


5. 边缘拖动

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

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

边缘拖动代码:

@Overridepublic 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.最后

都是方法的简单调用

本人很菜,有错误请指出

共勉 : )

文/英勇青铜5

关键字:android, 产品经理


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部