ViewDragHelper入门

概述

        它是为了自定义ViewGroup而添加的一个工具类。它内部有一系列方法,这些方法方便我们拖拽和重新定位子View的位置。在位于V4包中。

        ViewDragHelper的本质是分析onInterceptTouchEvent和onTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位置( 通过offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在触摸的时候判断当前拖动的是哪个子View。

常用方法

初始化

        该类的初始化是通过静态方法create()完成的。其中三个参数的源代码如下:
    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;
    }
        从中可以看出:最终调用了两个参数的create(),只是将mTouchSlop的值进行了改变。而两个参数的create()很简单,只是调用了下面的构造方法:
    private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
        if (forParent == null) {
            throw new IllegalArgumentException("Parent view may not be null");
        }
        if (cb == null) {
            throw new IllegalArgumentException("Callback may not be null");
        }

        mParentView = forParent;
        mCallback = cb;

        final ViewConfiguration vc = ViewConfiguration.get(context);
        final float density = context.getResources().getDisplayMetrics().density;//屏幕密度
        mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);

        mTouchSlop = vc.getScaledTouchSlop();//代码一
        mMaxVelocity = vc.getScaledMaximumFlingVelocity();
        mMinVelocity = vc.getScaledMinimumFlingVelocity();
        mScroller = ScrollerCompat.create(context, sInterpolator);
    }
        从代码一处可以看到mTouchSlop只是一个限制数字(手指按到屏幕上时会无意识的运动,该值就是为了屏蔽这种运动),当手指移动的距离大于该值时才可认为用户已经滑动,否则认为手指是静止的。因此,当该值越大时用户需要滑动的距离就越大,也就是对滑动越不敏感。这也是三个参数的create()中第二个参数的含义:当第二个参数越大,mTouchSlop越小,从而越敏感;反之同理。
        create()中的第三个参数是一个回调,该回调方法是与View打交道的接口。
        在构造方法中,还初始化了一些别的变量。特别是mScroller,它是Scroller的兼容类,经常用来计算移动的位置。并且初始化了mParentView。

常用方法

        captureChildView():强制地为本次滑动设置一个捕获到的view。该方法不会经过CallBack.tryCaptureView()的权限验证。也就是说:即使后者返回false,本次滑动也会滑动该方法指定的view。
        setEdgeTrackingEnabled():设置支持的边界滑动(DrawerLayout的效果就是边界滑动)。
        settleCapturedViewAt():以动画的方式将捕获到的view移动了(left,top)位置处,并且考虑该view被释放时的移动速度。如果返回false,表示这次不需要进行任何移动;如果返回true,需要继续调用continueSettling()来判断是否应该继续移动。
        continueSettling():返回true表明仍旧需要移动,否则不需要。如果在View.computeScroll()或者别的属于layout或者draw过程的方法中调用时,参数传true。示例如下:
	    //ViewDragHelper.Callback中的方法
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        super.onViewReleased(releasedChild, xvel, yvel);
        helper.settleCapturedViewAt(releasedChild.getLeft(), getWidth() - releasedChild.getWidth());
        invalidate();//为了启动移动动画
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if(helper.continueSettling(true)){//如果为true,表明移动未结束
            //需要继续进行移动
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
        shouldInterceptTouchEvent(MotionEvent):是否应该拦截本次MotionEvent的传递。该方法通常在onInterceptTouchEvent()中调用。如:
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = MotionEventCompat.getActionMasked(ev);
		if (action == MotionEvent.ACTION_CANCEL
				|| action == MotionEvent.ACTION_UP) {
			mDragHelper.cancel();
			return false;
		}
		return mDragHelper.shouldInterceptTouchEvent(ev);
	}
        processTouchEvent(MotionEvent):与GestureDetector类似ViewDragHelper也需要关联相应的MotionEvent,该方法就是关联MotionEvent的。它通常是在onTouchEvent()中调用。如:
	public boolean onTouchEvent(MotionEvent event) {
		mDragHelper.processTouchEvent(event);
		return true;
	}

Callback

        它是连接ViewDragHelper与View(创建ViewDragHelper实例时传递的ViewGroup)的桥梁,也是我们进行修改子View的地方。

常用方法

        tryCaptureView(View,int):是否可以拖动该组件。返回true:允许使用该手指(第二个参数指定)进行拖动,否则将不能进行拖动。其中View表示当前手机触摸到的子View,无论是当前ViewGroup的直接子View还是间接子View。
        getOrderedChildIndex():确定mParentView的各个子view的顺序。它的唯一调用地方为:
    public View findTopChildUnder(int x, int y) {
        final int childCount = mParentView.getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
            if (x >= child.getLeft() && x < child.getRight() &&
                    y >= child.getTop() && y < child.getBottom()) {
                return child;
            }
        }
        return null;
    }
        因此仅有一个view包含(x,y)时,该方法基本上无用;不止一个view包含时(x,y)时,该方法可以用来调整findTopChildUnder()的返回值。但是最好还是别重写。
        clampViewPositionHorizontal(View child, int left, int dx):处理控件的水平拖动。child指当前拖动的view;dx指本次移动在水平方向上移动的距离;left指child原来left+dx,也就是child将要达到的位置。源码如下:
dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
dragTo源码如下:
    private void dragTo(int left, int top, int dx, int dy) {
        int clampedX = left;
        int clampedY = top;
        final int oldLeft = mCapturedView.getLeft();
        final int oldTop = mCapturedView.getTop();
        if (dx != 0) {
            clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
            mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
        }
从上面可以看出,clampViewPositionHorizontal的返回值表示本次移动后view将要达到的位置
        clampViewPositionVertical(View child, int left, int dx):处理控件的垂直拖动。具体参数同上。
        onViewReleased(View,float,float):当 view被释放时。第二,三个参数分别表示释放时在x,y轴上的速度。一般在该回调中调用settleCapturedViewAt()。
        getViewHorizontalDragRange():返回一个可拖动子视图在水平方向上运动范围 大小。返回不大于0的数代表着当前view 不可 在水平方向上移动 。返回大于0的数则代表着可以。主要用在:
    private boolean checkTouchSlop(View child, float dx, float dy) {
        if (child == null) {
            return false;
        }
        final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
        final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;

        if (checkHorizontal && checkVertical) {
            return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
        } else if (checkHorizontal) {
            return Math.abs(dx) > mTouchSlop;
        } else if (checkVertical) {
            return Math.abs(dy) > mTouchSlop;
        }
        return false;
    }
当返回值不大于0时,该checkTouchSlop()返回false,也就是说当前的滑动距离不够大,ViewDragHelper便不会认为是滑动,所以没有不会有滑动效果。
        getViewVerticalDragRange():与 getViewHorizontalDragRange()类似。
        onEdgeDragStarted():当用户从边界开始滑动,并且当前没有捕获到的view时的回调。类似于DrawerLayout的效果。
        onViewDragStateChanged():滑动状态改变时的回调(IDLE,DRAGGING与SETTLING)。
        onViewPositionChanged():当captureView的位置发生变化时的回调,无论是因为拖动还是因为通过ViewDragerHelper#settlerCapturedViewAt()引起的。第二三个参数表示该view当前的left和top,最后两个表示本次移动的距离。

与点击事件的冲突

        如果内部有view添加了click事件,那么该view默认时是无法移动的。可以根据需要重写getView[Horizontal|Vertical]DragRange()。





你可能感兴趣的:(ViewDragHelper入门)