转载

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.

也就是用来简化自定义ViewGroup拖拽操作的工具类,一些系统提供的Widget如SlidingPaneLayout、DrawerLayout都用到了ViewDragHelper处理拖动。

使用

大致步骤:

  1. 在自定义ViewGroup的构造方法里初始化:ViewDragHelper.create(this, 1.0f, callback)
  2. 将ViewGroup的Touch事件处理交给ViewDragHelper处理
  3. 重写callback对应方法。拖拽的子view,滑动范围等操作的具体控制均在此CallBack实现

    基本使用

    下面来看一个具体的实现:
    ViewDragHelper使用

三个View均在屏幕范围内滑动,第一个是常规拖动,第二个只有在拖动左侧屏幕边缘时才滑动,第三个展示的功能是拖动停止后的滑动。

自定义的DragLinearLayout代码如下:

public classDragLinearLayoutextendsLinearLayout{
    private ViewDragHelper mDragHelper;
    private View mDrawerNormalView;
    private View mDrawerEdgeView;
    private View mDrawerReleaseView;
    private Point mOriginPoint;
 
    publicDragLinearLayout(Context context, AttributeSet attrs,intdefStyle){
        super(context, attrs, defStyle);
        //实例化ViewDragHelper:1.0f为拖拽敏感系数,值越大拖拽灵敏度越高
        mDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelperCallBack());
        //设置屏幕左侧边缘支持拖拽
        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
        mOriginPoint = new Point();
    }
 
    public classViewDragHelperCallBackextendsViewDragHelper.Callback{
        @Override
        publicbooleantryCaptureView(View child,intpointerId){
            //如果想让某个view支持拖拽,在此处对应的child处返回true
            return mDrawerNormalView == child || child == mDrawerReleaseView;
        }
 
        @Override
        publicintclampViewPositionVertical(View child,inttop,intdy){
            //子View在y轴方向上应该移动到的位置,三个参数:要拖拽的子View实例、期望的移动后位置子View的top值、移动的距离。
            //返回值为子View在最终位置时的top值
            int topLimit = Math.max(0, top);
            return Math.min(getHeight() - child.getHeight(), topLimit);
        }
 
        @Override
        publicintclampViewPositionHorizontal(View child,intleft,intdx){
            int leftLimit = Math.max(0, left);
            return Math.min(getWidth() - child.getWidth(), leftLimit);
        }
 
        @Override
        publicvoidonEdgeTouched(intedgeFlags,intpointerId){
            //滑动左侧边缘时,主动通过captureChildView进行捕获,虽然tryCaptureView并未对mDrawerEdgeView返回true,但依旧能捕获
            if (edgeFlags == ViewDragHelper.EDGE_LEFT) {
                mDragHelper.captureChildView(mDrawerEdgeView, pointerId);
            }
        }
 
        @Override
        publicvoidonViewReleased(View releasedChild,floatxvel,floatyvel){
            //拖动mDrawerReleaseView手指释放时,让其回到原来位置
            // 注意:settleCapturedViewAt内部是Scroller机制,所以要通过invalidate() + computeScroll方法才能实现释放后的滑动。
            // 在computeScroll方法中判断滑动是否结束调用mDragHelper.continueSettling(true),此方法内部调用了mScroller.computeScrollOffset()
            if (releasedChild == mDrawerReleaseView) {
                mDragHelper.settleCapturedViewAt(mOriginPoint.x, mOriginPoint.y);
                invalidate();
            }
        }
    }
 
    @Override
    publicvoidcomputeScroll(){
        //响应settleCapturedViewAt()
        if (mDragHelper.continueSettling(true)) {
            invalidate();
        }
    }
 
    @Override
    publicbooleanonInterceptTouchEvent(MotionEvent ev){
        //交给DragHelper处理
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }
 
    @Override
    publicbooleanonTouchEvent(MotionEvent event){
        //交给DragHelper处理
        mDragHelper.processTouchEvent(event);
        return true;
    }
 
    @Override
    protectedvoidonFinishInflate(){
        super.onFinishInflate();
        mDrawerNormalView = findViewById(R.id.drawer_normal);
        mDrawerEdgeView = findViewById(R.id.drawer_edge);
        mDrawerReleaseView = findViewById(R.id.drawer_release);
    }
 
    @Override
    protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
        super.onLayout(changed, l, t, r, b);
        mOriginPoint.x = mDrawerReleaseView.getLeft();
        mOriginPoint.y = mDrawerReleaseView.getTop();
    }
 
    publicDragLinearLayout(Context context){
        this(context, null);
    }
 
    publicDragLinearLayout(Context context, AttributeSet attrs){
        this(context, attrs, 0);
    }
}

代码中注释比较详细,就不再解释了。另外,DragHelper还有对应fling的方法flingCapturedView(),其它smoothSlideViewTo()方法等。

Clickable

如果子view是Button或者具有Clickable的其它widget,就无法再次拖动了,因为此时子view抢去了事件的处理。简单的处理可以在覆盖CallBack中的如下方法:

@Override
publicintgetViewHorizontalDragRange(View child){
    return 1;
}
 
@Override
publicintgetViewVerticalDragRange(View child){
    return 1;
}

返回大于0即可,当子view抢去事件处理后,ViewDragHelper会进一步通过这两个方法的返回值来capture view。

ScrollView

如果子view是类似ScrollView的控件,可以通过重写ViewGroup的onInterceptTouchEvent来处理抢事件问题。

@Override
publicbooleanonInterceptTouchEvent(MotionEvent ev){
    //交给DragHelper处理
    boolean helper = mDragHelper.shouldInterceptTouchEvent(ev);
    boolean result = false;
 
    switch (MotionEventCompat.getActionMasked(ev)) {
        case MotionEvent.ACTION_DOWN:
            mInitXpos = ev.getX();
            mInitYpos = ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            if (Math.abs(ev.getX() - mInitXpos) - Math.abs(ev.getY() - mInitYpos) > ViewConfiguration.getTouchSlop()) {
                result = true;
                mDragHelper.captureChildView(mDrawerScrollView, ev.getPointerId(0));
            }
            break;
    }
    return helper || result;
}

这里只是做了简单的使用说明,原理还需要查看源码的实现~

详细代码: https://github.com/edreamoon/FLibrary/blob/develop/edreamoon/src/main/java/com/edreamoon/customview/DragLinearLayout.java

原文  http://blog.edreamoon.com/2016/12/25/ViewDragHelper使用/
正文到此结束
Loading...