强大的ViewDragHelper和ViewDragHelper的妙用 一
来源:互联网 发布:重庆优化公司哪家好 编辑:程序博客网 时间:2024/06/05 22:50
文如其名,本篇博文我们将详细介绍强大的ViewDragHelper,但是这次我们将他们分开,本篇我们将完全解析 ViewDragHelper,下一篇我们我们将系统的说明ViewDragHelper的妙用
一般情况下,当我们希望我们的UI动起来(变得灵活的)的时候我们一般会首先想到在onInterceptTouchEvent 和OnTouchEvent做出配合处理,这样的话,我们就可以灵活的控制我们的UI,做出拖拽效果等等,当然onInterceptTouchEvent 和OnTouchEvent做出来的效果好不好呢,答案是肯定的,所有的逻辑都按照你的设定去走,那么便不回有什么大的偏差,但是如果你想偷懒的话呢,不要着急神奇的ViewDragHelper就应用而生了。举个最基本的例子Android中的SlidingPaneLayout和DrawerLayout就是用的ViewDragHelper来实现的。所以说ViewDragHelper是便捷且强大的。
那么到底什么是ViewDragHelper,我们来一起看看ViewDragHelper的神奇和强大。
我觉得我们首先需要明确一个概念ViewDragHelper虽然神奇和强大,但是如果你希望你的UI灵动起来,从根本上来讲都需要onInterceptTouchEvent 和OnTouchEvent来处理,那么既然躲不掉,那么为什么我们还要用ViewDragHelper呢,理由很简单Google用ViewDragHelper 封装了对onInterceptTouchEvent 和OnTouchEvent的处理,也就是说Google已经替我们写好了逻辑,我们只需要设定我们的UI动起来的轨迹就好了
我们先来看一下 Google对于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(用来实现灵活的UI)的话,ViewDragHelper 将会是一个非常有用的内部对象,他提供了一系列操作来让你拖拽和重定位 你的 UI 子View。
根据目前我们所知道,我们知道ViewDragHelper 将会是自定义VieGroup的内部对象,并且他封装了对onInterceptTouchEvent 和OnTouchEvent的处理。
那么,我们应该怎么使用它呢。
如果你熟悉Android Gesture,我们这里可以回想一下Gesture的用法,其实ViewDragHelper和Gesture一样,都是对于我们onTouchEvent的封装 比如如果我们希望我们的Gesture来处理我们的逻辑,我们一般会这么写
@Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub return mGestureDetector.onTouchEvent(event); }
这样一来,会不会稍微好理解一下ViewDragHelper的设计原理呢,其实在Android中,我们所有的屏幕交互事件都只有MotionEvent一种(按键的逻辑除外),包括OnClickListener也只是onTouchEvenet 的处理结果接口,如果对于这一块不是很理解的话,可以返回去读一读我的其他博文。这里不赘述。
那么到了这里,我们就知道如果我们希望使用到ViewDragHelper,那么我们首先第一步需要把MotionEvent处理逻辑交给我们的ViewDragHelper。例如我们需要在自定义的ViewGroup中这样写:
@Overridepublic 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);}
@Overridepublic boolean onTouchEvent(MotionEvent ev) { mDragHelper.processTouchEvent(ev); return true;}
好,现在我们走完了第一步,我们把我们的MotionEvent交给我们的ViewDragHelper,ViewDragHelper帮我们处理了所有的逻辑,那么现在问题来了,ViewDragHelper怎么知道我们希望怎么移动或者定位VIew呢,依照Android的尿性或者对照一下Gesture的实现,我们知道必定会有一个回调接口来处理我们的移动逻辑。
果不其然,我们在源码中发现
public static abstract class Callback { public void onViewDragStateChanged(int state) {} public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {} public void onViewCaptured(View capturedChild, int activePointerId) {} public void onViewReleased(View releasedChild, float xvel, float yvel) {} public void onEdgeTouched(int edgeFlags, int pointerId) {} public void onEdgeDragStarted(int edgeFlags, int pointerId) {} public int getOrderedChildIndex(int index) { return index; } public int getViewHorizontalDragRange(View child) { return 0; } public int getViewVerticalDragRange(View child) { return 0; } public abstract boolean tryCaptureView(View child, int pointerId); public int clampViewPositionHorizontal(View child, int left, int dx) { return 0; } public int clampViewPositionVertical(View child, int top, int dy) { return 0; } }
很明显,这一系列回调函数可以很好的完成我们所需有的拖拽逻辑。关于每一个函数的具体含义稍后再解释。
现在,我们继续我们的逻辑,完事具备,唯一差的就是我们的ViewDragHelper对象了,那么我们应该创建我们的
sensitivity用我的理解是用来设置触摸Move灵敏度的。我们在源码中可以看到
那么什么叫mTouchSlop呢Google给出的解释是
☞ public int clampViewPositionHorizontal(View child, int left, int dx) ;从函数名我们也可以知道他框定的是我们的Child水平方向上移动的位置,我们细看一下光方给出的注释
ViewDragHelper对象呢,Android 提供一种工厂模式来产生我们的ViewDragHelper对象,比如我们可以
mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);或者
mDragHelper = ViewDragHelper.create(this, 1.0f,mDragHelperCallBack);其实都一样,创建对象所需要的3个参数分别为ViewGroup、sensitivity、Callback。第一个和第三个没什么说的
sensitivity用我的理解是用来设置触摸Move灵敏度的。我们在源码中可以看到
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
那么什么叫mTouchSlop呢Google给出的解释是
Distance in pixels a touch can wander before we think the user is scrolling
我的理解是我们认为ACTION_MOVE的像素距离,貌似默认值为7
这里就很明显我们的 sensitivity越大,mTouchSlop就越小,那么我们的灵敏度就会更高。
经过了这三步之后,很明显我们已经设好了ViewDragHelper,接下来就需要在CallBack回调去规划了。
这里 我首先来介绍最后三个函数也是最常用的三个函数
☞ boolean tryCaptureView(View child, int pointerId); 这个函数从返回值我们都可以看出来,他是用来判断我们的哪一个Child可以用来做拖拽处理,简而言之,我们的ViewGroup有多个ChildView,是否每一个都可以拖动呢,很显然,要想动,先过tryCaptureView这一关。
public boolean tryCaptureView(View child, int pointerId) { return true; //所有的子元素都可以移动 } public boolean tryCaptureView(View child, int pointerId) { return child1 == child || child2 ==child;//制定子元素 }
☞ public int clampViewPositionHorizontal(View child, int left, int dx) ;从函数名我们也可以知道他框定的是我们的Child水平方向上移动的位置,我们细看一下光方给出的注释
/** * Restrict the motion of the dragged child view along the horizontal axis. * The default implementation does not allow horizontal motion; the extending * class must override this method and provide the desired clamping. * * * @param child Child view being dragged * @param left Attempted motion along the X axis * @param dx Proposed change in position for left * @return The new clamped position for left */ public int clampViewPositionHorizontal(View child, int left, int dx) { return 0; }
那么很明显,我们需要重载他来得到我们移动的距离,这里我就不一一翻译了,我们重点关注param left,官方给的说明是尝试在X轴移动的位置,怎么来理解呢,比如当你拖动一个View,你拖动到的位置就是我们的left,这样是不是就很好理解了呢,所以呢我们可以这样调用
@Override public int clampViewPositionHorizontal(View child, int left, int dx) { final int leftBound = getPaddingLeft(); final int rightBound = getWidth() - mChildView.getWidth() - leftBound; final int newLeft = Math.min(Math.max(left, leftBound), rightBound); return newLeft; //限定在ViewGroup内部移动 }
@Override public int clampViewPositionHorizontal(View child, int left, int dx) { return left; //随着你的拖动移动,没有限制 }
☞public int clampViewPositionVertical(View child, int top, int dy) 同理与clampViewPositionHorizontal
好了,有了这三个方法,对于简单的拖动处理应该就不是问题了。
关于其他函数的使用 请阅读:
Android ViewDragHelper完全解析 自定义ViewGroup神器
这里,我们继续,在使用的过程中,我们发现我们的ChildView一旦可以消费到我们的MotionEvent(OnTouch/onClick/Clickable等)时,我们的ViewDragHelper 将对该ChildView无效,为什么呢。
这里,我们还是用源码来说话
/** * Check if this event as provided to the parent view's onInterceptTouchEvent should * cause the parent to intercept the touch event stream. * * @param ev MotionEvent provided to onInterceptTouchEvent * @return true if the parent view should return true from onInterceptTouchEvent */ public boolean shouldInterceptTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); final int actionIndex = MotionEventCompat.getActionIndex(ev); if (action == MotionEvent.ACTION_DOWN) { // Reset things for a new event stream, just in case we didn't get // the whole previous stream. cancel(); } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); switch (action) { case MotionEvent.ACTION_DOWN: { final float x = ev.getX(); final float y = ev.getY(); final int pointerId = MotionEventCompat.getPointerId(ev, 0); saveInitialMotion(x, y, pointerId); final View toCapture = findTopChildUnder((int) x, (int) y); // Catch a settling view if possible. if (toCapture == mCapturedView && mDragState == STATE_SETTLING) { tryCaptureViewForDrag(toCapture, pointerId); } final int edgesTouched = mInitialEdgesTouched[pointerId]; if ((edgesTouched & mTrackingEdges) != 0) { mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); } break; } case MotionEventCompat.ACTION_POINTER_DOWN: { final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); final float x = MotionEventCompat.getX(ev, actionIndex); final float y = MotionEventCompat.getY(ev, actionIndex); saveInitialMotion(x, y, pointerId); // A ViewDragHelper can only manipulate one view at a time. if (mDragState == STATE_IDLE) { final int edgesTouched = mInitialEdgesTouched[pointerId]; if ((edgesTouched & mTrackingEdges) != 0) { mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); } } else if (mDragState == STATE_SETTLING) { // Catch a settling view if possible. final View toCapture = findTopChildUnder((int) x, (int) y); if (toCapture == mCapturedView) { tryCaptureViewForDrag(toCapture, pointerId); } } break; } case MotionEvent.ACTION_MOVE: { // First to cross a touch slop over a draggable view wins. Also report edge drags. final int pointerCount = MotionEventCompat.getPointerCount(ev); for (int i = 0; i < pointerCount; i++) { final int pointerId = MotionEventCompat.getPointerId(ev, i); final float x = MotionEventCompat.getX(ev, i); final float y = MotionEventCompat.getY(ev, i); final float dx = x - mInitialMotionX[pointerId]; final float dy = y - mInitialMotionY[pointerId]; reportNewEdgeDrags(dx, dy, pointerId); if (mDragState == STATE_DRAGGING) { // Callback might have started an edge drag break; } final View toCapture = findTopChildUnder((int) x, (int) y); if (toCapture != null && checkTouchSlop(toCapture, dx, dy) && tryCaptureViewForDrag(toCapture, pointerId)) { break; //这里的条件判断很重要,要注意 } } saveLastMotion(ev); break; } case MotionEventCompat.ACTION_POINTER_UP: { final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); clearMotionHistory(pointerId); break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { cancel(); break; } } return mDragState == STATE_DRAGGING; //很明显true,我们才能走到procrssTouchEvent } /** * Process a touch event received by the parent view. This method will dispatch callback events * as needed before returning. The parent view's onTouchEvent implementation should call this. * * @param ev The touch event received by the parent view */ public void processTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); final int actionIndex = MotionEventCompat.getActionIndex(ev); if (action == MotionEvent.ACTION_DOWN) { // Reset things for a new event stream, just in case we didn't get // the whole previous stream. cancel(); } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); switch (action) { case MotionEvent.ACTION_DOWN: { final float x = ev.getX(); final float y = ev.getY(); final int pointerId = MotionEventCompat.getPointerId(ev, 0); final View toCapture = findTopChildUnder((int) x, (int) y); saveInitialMotion(x, y, pointerId); // Since the parent is already directly processing this touch event, // there is no reason to delay for a slop before dragging. // Start immediately if possible. tryCaptureViewForDrag(toCapture, pointerId); final int edgesTouched = mInitialEdgesTouched[pointerId]; if ((edgesTouched & mTrackingEdges) != 0) { mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); } break; } case MotionEventCompat.ACTION_POINTER_DOWN: { final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); final float x = MotionEventCompat.getX(ev, actionIndex); final float y = MotionEventCompat.getY(ev, actionIndex); saveInitialMotion(x, y, pointerId); // A ViewDragHelper can only manipulate one view at a time. if (mDragState == STATE_IDLE) { // If we're idle we can do anything! Treat it like a normal down event. final View toCapture = findTopChildUnder((int) x, (int) y); tryCaptureViewForDrag(toCapture, pointerId); final int edgesTouched = mInitialEdgesTouched[pointerId]; if ((edgesTouched & mTrackingEdges) != 0) { mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); } } else if (isCapturedViewUnder((int) x, (int) y)) { // We're still tracking a captured view. If the same view is under this // point, we'll swap to controlling it with this pointer instead. // (This will still work if we're "catching" a settling view.) tryCaptureViewForDrag(mCapturedView, pointerId); } break; } case MotionEvent.ACTION_MOVE: { if (mDragState == STATE_DRAGGING) { final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, index); final float y = MotionEventCompat.getY(ev, index); final int idx = (int) (x - mLastMotionX[mActivePointerId]); final int idy = (int) (y - mLastMotionY[mActivePointerId]); dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy); // 这里,请注意我们的拖动逻辑最终在这里完成 saveLastMotion(ev); } else { // Check to see if any pointer is now over a draggable view. final int pointerCount = MotionEventCompat.getPointerCount(ev); for (int i = 0; i < pointerCount; i++) { final int pointerId = MotionEventCompat.getPointerId(ev, i); final float x = MotionEventCompat.getX(ev, i); final float y = MotionEventCompat.getY(ev, i); final float dx = x - mInitialMotionX[pointerId]; final float dy = y - mInitialMotionY[pointerId]; reportNewEdgeDrags(dx, dy, pointerId); if (mDragState == STATE_DRAGGING) { // Callback might have started an edge drag. break; } final View toCapture = findTopChildUnder((int) x, (int) y); if (checkTouchSlop(toCapture, dx, dy) && tryCaptureViewForDrag(toCapture, pointerId)) { break; } } saveLastMotion(ev); } break; } case MotionEventCompat.ACTION_POINTER_UP: { final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) { // Try to find another pointer that's still holding on to the captured view. int newActivePointer = INVALID_POINTER; final int pointerCount = MotionEventCompat.getPointerCount(ev); for (int i = 0; i < pointerCount; i++) { final int id = MotionEventCompat.getPointerId(ev, i); if (id == mActivePointerId) { // This one's going away, skip. continue; } final float x = MotionEventCompat.getX(ev, i); final float y = MotionEventCompat.getY(ev, i); if (findTopChildUnder((int) x, (int) y) == mCapturedView && tryCaptureViewForDrag(mCapturedView, id)) { newActivePointer = mActivePointerId; break; } } if (newActivePointer == INVALID_POINTER) { // We didn't find another pointer still touching the view, release it. releaseViewForPointerUp(); } } clearMotionHistory(pointerId); break; } case MotionEvent.ACTION_UP: { if (mDragState == STATE_DRAGGING) { releaseViewForPointerUp(); } cancel(); break; } case MotionEvent.ACTION_CANCEL: { if (mDragState == STATE_DRAGGING) { dispatchViewReleased(0, 0); } cancel(); break; } } }
如果你读到了这里,并且还有兴趣继续读下去,首先你需要了解MotionEvent的处理机制。如果说你对onInterceptTouchEvent 和onTouchEvent还不够了解的话,可以阅读
onInterceptTouchEvent 与 onTouchEvent 分析与MotionEvent在ViewGroup与View中的分发
那么,我们继续往下走。
根据前面的分析,现在我们假定我们的ChildView 是可以消费MotionEvent的,那么,依据我MotionEvent处理博文中的说明,我们首先会在shouldInterceptTouchEvent处理ActionDown 很明显这里返回的false,那么我们的ChildView便得到了ActionDown。
得到ActionDown 是毋庸置疑的,这里我们分两种情况来考虑,
一、我们只是点击了我们的ChildView并没有移动
很显然我们在shouldInterceptTouchEvent 得到ActionMove并走过
if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&
这里有一个函数很重要checkTouchSlop,现在从字面上来看就是判断是否是移动,稍后我会解释这个函数,现在我们先默认没有移动(我们这里是点击事件),那么同理是返回了了false让我们的ChildView得到了ActionMove,然后就顺理成章的ActionUp,完成点击事件了。
二、如果我们移动我们的ChildView,同理我们走到了
if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&
这里我们来看
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; }
首先我们知道
public int getViewHorizontalDragRange(View child) { return 0; } public int getViewVerticalDragRange(View child) { return 0; }那么,很明显我们的ActionMove还是会返回false,我们的ChildView依然不会被移动,所以说这里我们鞋网一个具有Clickable属性的ChildView被移动,我们需要重写getViewVerticalDragRange这两个函数k框定拖动范围,让他可以移动
1 0
- 强大的ViewDragHelper和ViewDragHelper的妙用 一
- 强大的ViewDragHelper和ViewDragHelper的妙用 一
- ViewDragHelper 的学习一
- ViewDragHelper的简单分析(一)
- 强大的视图拖拽助手--ViewDragHelper
- ViewDragHelper的妙用二 --QQ侧滑菜单的实现
- ViewDragHelper的一些研究
- ViewDragHelper的使用
- viewdraghelper的使用
- ViewDragHelper的用法
- ViewDragHelper API 的使用
- ViewDragHelper的使用介绍
- ViewDragHelper的使用
- ViewDragHelper的使用
- ViewDragHelper的使用
- ViewDragHelper的使用
- ViewDragHelper的使用
- ViewDragHelper的初体验
- 关于Git的入门理解以及第二天实习收获
- cocos2d-x游戏开发(七)对象释放时机
- 实现相册的缩放图片功能
- URAL 1996 Cipher Message 3 FFT + KMP
- oracle 定时任务
- 强大的ViewDragHelper和ViewDragHelper的妙用 一
- merge javascript Object
- synergy 简易教程——多台主机共用一套鼠标键盘
- 修改全局状态栏颜色
- [腾讯]sizeof()和strlen()的区别
- cocos2d-x游戏开发(八)各类构造器
- Android 编译系统(NDK)
- 首段首段
- Ubuntu中的vim一些简单配置