ViewDragHelper学习

来源:互联网 发布:java logger加日志 编辑:程序博客网 时间:2024/05/02 04:55

本博文得益于点击打开链接,说是也是出自张鸿洋大神的。

ViewDragHelper是一个官方封装好的用于处理各种拖动事件的工具类,避免了自己处理各种Touch事件和判断的繁琐,DrawerLayout就是用这个类去实现的,附官方注释:

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类型的,像LinearLayout什么的,新建ViewDragHelper对象,

private ViewDragHelper mDragger ;

在构造方法中实例化它(采用静态工厂方法实例化)

mDragger = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {    @Override    public boolean tryCaptureView(View child, int pointerId) {        return true;    }    @Override    public int clampViewPositionHorizontal(View child, int left, int dx) {        //参数left是水平位置上移动的view的左边缘要到达的地方(把left作为坐标基准线)        //得到左内边距(即View水平移动的最左边的极限位置)        int leftPadding = getPaddingLeft();        //得到View水平移动的最右边的极限位置(注意都是以view的左边缘为准,所以是viewGroup的宽度减去右内边距再减去移动的view的宽度)        int rightBound = getWidth() - getPaddingRight() - child.getWidth();        //此行代码好厉害,仔细琢磨,先拿左内边距(所能滑到的最左边位置)和目标位置作比较,取最大的(即如果目标位置小于左内边距,        // 则说明要移出ViewGroup了,这时就取左内边距作为目标位置这样就固定了所能滑到的最左边位置),然后再拿比较后的位置(也即是初步决定的目标位置)        // 来和左边缘所能滑到的最右边位置作比较,取最小的(即如果目标位置大于左边缘所能滑到的最右边位置,则说明要移出ViewGroup了,这时        // 就取左边缘所能滑到的最右边位置作为目标位置,这样就固定了所能滑到的最右边位置)        int newLeft = Math.min(Math.max(left, leftPadding), rightBound);        return newLeft;    }    @Override    public int clampViewPositionVertical(View child, int top, int dy) {        //参数top是垂直位置上移动的view的上边缘要到达的地方(把top作为坐标基准线)        //得到上内边距(即View水平移动的最上边的极限位置)        int topPadding = getPaddingTop();        //得到View垂直移动的最下边的极限位置(注意都是以view的上边缘为准,所以是viewGroup的宽度减去下内边距再减去移动的view的宽度)        int bottomBound = getHeight() - getPaddingBottom() - child.getHeight();        //此行代码好厉害,仔细琢磨,先拿上内边距(所能滑到的最上边位置)和目标位置作比较,取最大的(即如果目标位置小于上内边距,        // 则说明要移出ViewGroup了,这时就取上内边距作为目标位置这样就固定了所能滑到的最上边位置),然后再拿比较后的位置(也即是初步决定的目标位置)        // 来和上边缘所能滑到的最下边位置作比较,取最小的(即如果目标位置大于上边缘所能滑到的最下边位置,则说明要移出ViewGroup了,这时        // 就取上边缘所能滑到的最下边位置作为目标位置,这样就固定了所能滑到的最下边位置)        int newTop = Math.min(Math.max(top, topPadding), bottomBound);        return newTop;    }}
create方法第一个参数是要绑定的ViewGroup对象,第二个是灵敏度(越大越灵敏helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));),sensitivity就是设置的灵敏度,第三个参数是ViewDragHelper的回调方法,里面设置一系列处理拖动事件的自定义业务。

@Overridepublic boolean tryCaptureView(View child, int pointerId) {    return true;}
上面的这个方法重写后默认是返回false,这时不会响应任何拖动事件,这个方法可以设置这个ViewGroup里的哪些View可以拖动的,你可以根据传入的第一个view参数决定哪些可以捕获,如果只是返回true则所有的View都可以拖动。

这个方法在新建CallBack实例时会自动创建的,是必须的,但是!即使设置了这个方法返回true,你会发现还是不能拖动,这是因为你还有两个方法没有重写(clampViewPositionHorizontal(View child, int left, int dx)和clampViewPositionVertical(View child, int top, int dy)),前者负责水平方向的拖动,后者负责竖直方向上的滑动,所以我们要重写这两个方法才能正常拖动,我们去上面的构造方法里重写了,里面还设置了能够拖动的范围,这可以使我们的view不会滑出他们的父容器,仔细阅读注释就会明白,如果不想设置边界,只想能拖动,则只需要返回参数里面的left或者top即可,他们代表着拖动到的目标位置。


然后重写他的onInterceptTouchEvent(MotionEvent event)方法,注意不要重写成onInterceptHoverEvent(),他俩很像,我就写错了...把是否拦截触摸事件交给ViewDragHelper对象去决定

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    return mDragger.shouldInterceptTouchEvent(ev);}

然后重新onTouchEvent(MotionEvent event)方法,如果拦截了说明是拖动事件,所以把触摸事件交给ViewDragHelper对象处理

@Overridepublic boolean onTouchEvent(MotionEvent event) {    mDragger.processTouchEvent(event);    return true;    }

到这,你就可以在xml中添加它了,然后run一下拖动就可以实现了。

下面来一些更高级的:

1.像DrawerLayout一样可以从边缘触发拖动事件:

重写onEdgeDragStarted(int edgeFlags, int pointerId)方法,第一个参数是决定从此ViewGroup的哪个边缘触发(特别注意不是screen的边缘),第二个参数是响应的view的点的id

@Overridepublic void onEdgeDragStarted(int edgeFlags, int pointerId) {    mDragger.captureChildView(mEdgeTrackerView, pointerId);}
captureChildView()方法的第一个参数是决定哪个view响应边缘触发事件,第二个参数是响应的view的点的id。

此外还要设置从哪个边缘触发,有上下左右四个边缘可以设置

mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
到此就可以触发边缘触发了!

2.在拖动后(手指松开后)也可以添加一些业务:现在来实现一个松开后自动回到原始位置的效果

首先,定义一个Point对象来记录要回到的原始位置

private Point mAutoBackOriginPos = new Point();
在onLayout()方法里保存一开始view的起始位置(view的左上角坐标)

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {    super.onLayout(changed, l, t, r, b);    mAutoBackOriginPos.x = mAutoBackView.getLeft();    mAutoBackOriginPos.y = mAutoBackView.getTop();}

mAutoBackView是xml中添加的要回到初始位置的view,可能你会问,怎么得到这个view呢?往下看

@Override protected void onFinishInflate() {    super.onFinishInflate();    mDragView = getChildAt(0);    mAutoBackView = getChildAt(1);    mEdgeTrackerView = getChildAt(2);}
就是这个方法,在inflate完布局后就可以得到里面的view了!

保存完初始点了,然后要做的就是恢复,CallBack对象里重写下面这个方法

@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {    //mAutoBackView手指释放时可以自动回去    if (releasedChild == mAutoBackView) {        mDragger.settleCapturedViewAt(mAutoBackOriginPos.x, mAutoBackOriginPos.y);        invalidate();    }}
这个方法就是在手指松开后调用的,第一个参数是代表着拖动了哪个view,后面两个参数分别代表着松开时最后一个点的x方向和y方向上的加速度。我们在这里面判断是否是我们想让它回到初始位置的view,如果是,调用settleCapturedViewAt()方法来设置某个view的坐标(这里设成保存的起点位置),然后进行重绘,就会把这个releaseView绘制到初始位置了,而且恢复时的速度取决于你拖过来时的速度(很人性化啊!)。

如果你以为这样就成了,就错了,我们还需要在这个ViewGroup层重写一个方法

@Override public void computeScroll() {    if (mDragger.continueSettling(true)) {        invalidate();    }}
一开始我也不知道这个方法是干什么的,查了资料后得知:

computeScroll()主要功能是计算拖动的位移量、更新背景、设置要显示的屏幕(setCurrentScreen(mCurrentScreen);)。
重写computeScroll()的原因:
调用startScroll()是不会有滚动效果的,只有在computeScroll()获取滚动情况,做出滚动的响应,computeScroll在父控件执行drawChild时,会调用这个方法

所以应该是我们在松开手之后执行的invalidate()方法去重绘时ViewDragHelper已经不负责了,这算新的一次绘制,所以我们要显示地再去通知ViewDragHelper:“还有一点没处理完呢”,通知的这部分就是

 if (mDragger.continueSettling(true)) {        invalidate();    }
continueSettling(true)传入true让绘制延续从而完整吧,这一部分不熟悉,反正肯定要这么写才行!!

如果拖动的是Button或者TextView指定了android:clickable="true",则拖动会失效,需要重写下面这两个方法

//如果拖动的是button或者TextView设置了android:clickable="true"消耗了事件,需要重写下面的这两个方法//Down的时候被设置了clickable属性的TextView或者Button先捕获了(因为此时不是拖动状态所以没被ViewGroup截获),所以此时不会执行// clampViewPositionHorizontalclampViewPositionVertical方法,所以他们的目标位置是初始位置// 处理方法是重写下面这两个方法,返回范围@Overridepublic int getViewHorizontalDragRange(View child) {    return getMeasuredWidth() - child.getMeasuredWidth();}@Overridepublic int getViewVerticalDragRange(View child) {    return getMeasuredHeight() - child.getMeasuredHeight();}
至于为什么没有截获而这两个CallBack里面的方法还能执行,我没有搞懂...

剩下的一些CallBack里的方法:

onViewDragStateChanged:
当ViewDragHelper状态发生变化时回调(IDLE,DRAGGING,SETTING[自动滚动时])


onViewPositionChanged:
当captureview的位置发生改变时回调


onViewCaptured:
当captureview被捕获时回调


onEdgeTouched:
当触摸到边界时回调。


onEdgeLock:
true的时候会锁住当前的边界,false则unLock。


getOrderedChildIndex:
改变同一个坐标(x,y)去寻找captureView位置的方法。(具体在:findTopChildUnder方法中)

还剩下一个问题就是setEdgeTrackingEnabled()方法只有LEFT和TOP可用,RIGHT和BOTTOM边缘都无效...(我测试的ViewGroup是继承自LinearLayout的)













0 0
原创粉丝点击