自定义ViewGroup不可或缺的ViewDragHelper工具类

ViewDragHelper概述

ViewDragHelper实质上是对父ViewGroup中的子view的滑动操作、重新定位视图以及状态跟踪等做了一系列的封装,即只需输入父ViewGroup的TouchEvent,则会通过Callback返回子View的相关操作。省去了程序员需要对ViewGroup中不同子View的各种TouchEvent进行非常复杂的逻辑处理。所以ViewDragHelper是用于编写自定义ViewGroup的实用程序类。例如DrawerLayout中就运用了ViewDragHelper来处理拖动。
官方文档:

https://developer.android.google.cn/reference/android/support/v4/widget/ViewDragHelper

https://developer.android.google.cn/reference/android/support/v4/widget/ViewDragHelper.Callback

ViewDragHelper 常用方法

ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb)

创建新ViewDragHelper的工厂方法
forParent:输入需要被控制的视图容器,必须为ViewGroup
sensitivity:灵敏度倍增器,用于帮助程序检测拖动开始时的灵敏度。数值越大越敏感。默认为1.0f。
cb: 触摸过程中会回调相关方法

boolean shouldInterceptTouchEvent(MotionEvent ev)

检查这个事件是否应该使父视图拦截该触摸事件。相当于父视图中的触摸事件拦截代理。该方法在父视图的onInterceptTouchEvent方法中调用。

ev:提供给onInterceptTouchEvent的MotionEvent

void processTouchEvent(MotionEvent ev)

将父视图的触摸事件传递给ViewDragHelper ,在父视图的onTouchEvent中调用,这里要注意一个问题,处理相应的TouchEvent的时候要将结果返回为true,消费本次事件!否则将无法使用ViewDragHelper处理相应的拖拽事件!

ev:提供给onTouchEvent的MotionEvent

void captureChildView(@NonNull View childView, int activePointerId)

捕获特定子View以在父容器中被拖动。注意:捕获特定子childView不会使mCallback.tryCaptureView()方法被调用,但其他回调不受影响。

boolean settleCapturedViewAt(int finalLeft, int finalTop)

以松手前的滑动速度为初速动,让捕获到的View自动滚动到指定位置。只能在Callback的onViewReleased()中调用。

void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)

以松手前的滑动速度为初速动,让捕获到的View在指定范围内fling。只能在Callback的onViewReleased()中调用。

boolean smoothSlideViewTo(View child, int finalLeft, int finalTop)

指定某个View自动滚动到指定的位置,初速度为0,可在任何地方调用。

ViewDragHelper.Callback 常用方法

public void onViewDragStateChanged(int state) 拖动状态改变时调用state有如下三种值STATE_IDLE:当前视图未被拖动或动画STATE_DRAGGING:当前正在拖动视图STATE_SETTLING:目前正在通过投掷或抛掷当前视图
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 当捕获视图的位置因拖动或沉降而改变时调用changedView:被改变的Viewleft:视图左边缘的新X坐标top:视图上边缘的新Y坐标dx:从上次调用开始改变X位置dy:从上次调用开始改变Y位置
public void onViewCaptured(View capturedChild, int activePointerId) 当捕捉到用于拖动或控制的子视图时调用capturedChild:捕获到的子ViewactivePointerId: 跟踪子捕获的指针id,可以用来区分是手动拖动,还是程序控制。
public void onViewReleased(View releasedChild, float xvel, float yvel) 当子视图不再被主动拖动时调用。releasedChild:被释放的子视图xvel:X指针离开屏幕时的速度,单位是像素/秒。yvel:Y指针离开屏幕时的速度,单位是像素/秒。
public void onEdgeTouched(int edgeFlags, int pointerId) 当用户在当前没有捕获子视图的情况下触摸了父视图中的边缘时调用edgeFlags:描述当前触摸的边缘的边缘标志的组合EDGE_LEFTEDGE_TOPEDGE_RIGHTEDGE_BOTTOMpointerId:触摸所述边缘的指针id
public boolean onEdgeLock(int edgeFlags) 返回true来锁定当前的边缘,返回false来保持它未锁定。默认的行为是不锁定边缘。 edgeFlags:描述被锁定边缘的边缘标志的组合return true锁定边缘,false不锁定边缘
public void onEdgeDragStarted(int edgeFlags, int pointerId) 在父视图中的边缘拖动,且当前没有捕获任何子视图时调用。edgeFlags:描述拖动的边缘的边缘标志的组合EDGE_LEFTEDGE_TOPEDGE_RIGHTEDGE_BOTTOMpointerId: 接触所述边缘的指针ID
public int getOrderedChildIndex(int index) 调用以确定子视图的z顺序,(用于改变同一位置有两个View重叠,如果想让下层的子View被选中,则重写此方法,返回下层子View的index)index:当前捕获的子View的Z轴索引
public int getViewHorizontalDragRange(View child) 返回可拖动子视图水平运动范围的大小(以像素为单位)。对于不能水平移动的视图,该方法应该返回0。child:选中的子Viewreturn:以像素为单位的水平运动范围
public int getViewVerticalDragRange(View child) 返回可拖动子视图垂直运动范围的大小(以像素为单位)。对于不能垂直移动的视图,该方法应该返回0。child:选中的子Viewreturn:以像素为单位的垂直运动范围
public abstract boolean tryCaptureView(View child, int pointerId) 检查是否是需要被捕获的子View,如果允许捕获,返回true;否则返回false.返回ture表示该子View可以被拖动。pointerId:试图捕获的指针id
public int clampViewPositionHorizontal(View child, int left, int dx) 处理被拖动的子view在水平方向上应该移动到的位置child:被滑动的子View实例left:期望移动后的子View左边缘位置dx:移动的距离return:子View在最终位置时的left值
public int clampViewPositionVertical(View child, int top, int dy) 处理被拖动的子view在垂直方向上应该移动到的位置child:被滑动的子View实例top:期望移动后的子View上边缘位置dy:移动的距离return:子View在最终位置时的top值


ViewDragHelper 使用步骤:

  1. 在自定义ViewGroup的构造方法中调用ViewDragHelper的静态工厂方法create()创建ViewDragHelper实例mViewDragHelper
  2. 创建ViewDragHelper.Callback实例,实现其中tryCaptureView、onViewCaptured、onViewReleased、clampViewPositionHorizontal、clampViewPositionVertical、onViewDragStateChanged等方法。
  3. 在自定义ViewGroup中的onInterceptTouchEvent(MotionEvent ev)方法中调用mViewDragHelper的shouldInterceptTouchEvent(ev)方法。
  4. 在自定义ViewGroup中的onTouchEvent(MotionEvent event)方法中调用mViewDragHelper的processTouchEvent(ev)方法。当ACTION_DOWN事件发生时,如果当前点击要拖动的子View没有消费事件,此时应该在OnTouchEvent返回true,否则将不会收到后续点击事件,造成子View无法拖动。为什么无法收到后续点击事件?
  5. 上面四个步骤已经可以实现子View拖动的效果,如果还需要实现松手后自动滑动到指定位置,或者类似于抛东西,使得子View在滑动松手后被抛出去,并逐渐减速直到停止的效果,那么还需要实现自定义ViewGroup中的computeScroll()方法:
    @Override
    public void computeScroll() {
   
        //判断滑动是否完成
        if (mViewDragHelper.continueSettling(true)) {
   
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

computeScroll()方法实现后,可以在ViewDragHelper.Callback的onViewReleased()方法里调用settleCapturedViewAt()、flingCapturedView(),或在任意地方调用smoothSlideViewTo()方法。

  1. 如果要实现边缘拖动的效果,需要调用ViewDragHelper的setEdgeTrackingEnabled()方法,注册想要监听的边缘。然后实现ViewDragHelper.Callback里的onEdgeDragStarted()方法,在此手动调用captureChildView()传递要拖动的子View。

ViewDragHelper 注意事项:

ViewDragHelper一般用于自定义ViewGroup中对子View拖拽功能的实现,但ViewDragHelper并不会保存拖拽子View的状态及位置信息,需要我们手动保存,我们知道ViewGroup从新建到显示会经历measure、layout、draw这三大流程。其中在layout阶段会在onLayout方法中遍历所有子元素并调用子元素的layout方法。 通常我们在自定义ViewGroup的onLayout方法中对子View的位置及大小进行初始化配置,如下:

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
   
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
   
                 child.layout(0, 0, child.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值