View滚动帮助类ViewDragHelper解析
来源:互联网 发布:立式数控车床编程 编辑:程序博客网 时间:2024/06/06 00:58
ViewDraHelper解析
ViewDragHelper 是什么?
ViewDragHelper 是Google 编写的帮助我们处理拖拽的一个帮助类,如果我们自己去处理TouchEvent事件,判断处理TouchEvent的数据,达到我们拖拽View的一个目的也是可以的,但是过程肯定是有点繁琐的,ViewDragHelper 通过处理 MotionEvent 事件,判断是否是拖拽 以及拖拽方向、速率等然后回调给我们去实现我们自己真正想要实现的功能。
如何使用ViewDragHelper
ViewDragHelper 是通过工厂方法的方式创建的,下面是它的构造函数
/*** forParent 必须是ViewGroup,ViewDragHelper *并不是直接作用于要被拖动的View,而是使其控制的视图容器中的子View可以被拖动* sensitivity 敏感参数,一般都指定1.0f 表示正常速率* Callback 回调类,ViewDragHelper 在处理 touchevent 事件后,回调这个类的方法*/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; }
一般,我们在构造函数中,实例化ViewDragHelper
public MyDragLayout(Context context) { this(context, null);}public MyDragLayout(Context context, AttributeSet attrs) { super(context, attrs, defStyle); //DragHelperCallback 是我们继承抽象接口Callback的一个实现类 mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());}
ViewGroup区别于View的一个特点是,它有一个 onInterceptTouchEvent(MotionEvent ev) 方法,事件最先传递给这个方法,如果返回值返回 true,表示自己拦截 这一整个事件序列,不向子View进行分发,自己处理,即调用自己的onTouchEvent方法,我们需要再这个方法中,调用 ViewDragHelper 的shouldInterceptTouchEvent() 方法,让他处理是否拦截事件。
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mDragHelper.shouldInterceptTouchEvent(ev); }
同样,在 onTouchEvnet 中,也应该将事件交给 ViewDragHelper 处理
@Override public boolean onTouchEvent(MotionEvent event) { mDragHelper.processTouchEvent(event); return true; }
先不去了解 ViewDragHelpe 对 TouchEvent 的处理流程,先看一下,ViewDragHelper.Callback 为我们提供了哪些回调方法
public static abstract class Callback { /** * 在拖拽状态改变的时候调用. * @param state 新的拖拽状态 * * @see #STATE_IDLE 处于空闲状态 * @see #STATE_DRAGGING 拖拽中 * @see #STATE_SETTLING 自动沉落 */ public void onViewDragStateChanged(int state) {} /** * 在 一个子 View 的位置改变的时候调用 * * @param changedView View whose position changed * @param left New X coordinate of the left edge of the view * @param top New Y coordinate of the top edge of the view * @param dx Change in X position from the last call * @param dy Change in Y position from the last call */ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {} /** * 当 captured View 被捕获的时候调用 * Called when a child view is captured for dragging or settling. The ID of the pointer * currently dragging the captured view is supplied. If activePointerId is * identified as {@link #INVALID_POINTER} the capture is programmatic instead of * pointer-initiated. * * @param capturedChild Child view that was captured * @param activePointerId Pointer id tracking the child capture */ public void onViewCaptured(View capturedChild, int activePointerId) {} /** * 当View 被释放,其实也就是 手指抬起的时候调用 * xvel yvel 分别表示 x y方向上拖动的 px/每秒 * Called when the child view is no longer being actively dragged. * The fling velocity is also supplied, if relevant. The velocity values may * be clamped to system minimums or maximums. * * <p>Calling code may decide to fling or otherwise release the view to let it * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)} * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING} * and the view capture will not fully end until it comes to a complete stop. * If neither of these methods is invoked before <code>onViewReleased</code> returns, * the view will stop in place and the ViewDragHelper will return to * {@link #STATE_IDLE}.</p> * * @param releasedChild The captured child view now being released * @param xvel X velocity of the pointer as it left the screen in pixels per second. * @param yvel Y velocity of the pointer as it left the screen in pixels per second. */ public void onViewReleased(View releasedChild, float xvel, float yvel) {} /** * 当触摸到边界时回调 * Called when one of the subscribed edges in the parent view has been touched * by the user while no child view is currently captured. * * @param edgeFlags A combination of edge flags describing the edge(s) currently touched * @param pointerId ID of the pointer touching the described edge(s) * @see #EDGE_LEFT * @see #EDGE_TOP * @see #EDGE_RIGHT * @see #EDGE_BOTTOM */ public void onEdgeTouched(int edgeFlags, int pointerId) {} /** * 返回true的时候会锁住当前的边界,false则unLock。 * Called when the given edge may become locked. This can happen if an edge drag * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)} * was called. This method should return true to lock this edge or false to leave it * unlocked. The default behavior is to leave edges unlocked. * * @param edgeFlags A combination of edge flags describing the edge(s) locked * @return true to lock the edge, false to leave it unlocked */ public boolean onEdgeLock(int edgeFlags) { return false; } /** * 在边界拖动时回调 * Called when the user has started a deliberate drag away from one * of the subscribed edges in the parent view while no child view is currently captured. * * @param edgeFlags A combination of edge flags describing the edge(s) dragged * @param pointerId ID of the pointer touching the described edge(s) * @see #EDGE_LEFT * @see #EDGE_TOP * @see #EDGE_RIGHT * @see #EDGE_BOTTOM */ public void onEdgeDragStarted(int edgeFlags, int pointerId) {} /** * 改变同一个坐标(x,y)去寻找captureView位置的方法。 * Called to determine the Z-order of child views. * * @param index the ordered position to query for * @return index of the view that should be ordered at position <code>index</code> */ public int getOrderedChildIndex(int index) { return index; } /** * 返回值表示这个child View 竖向可以 拖拽的范围,0表示不能拖拽 * Return the magnitude of a draggable child view's horizontal range of motion in pixels. * This method should return 0 for views that cannot move horizontally. * * @param child Child view to check * @return range of horizontal motion in pixels */ public int getViewHorizontalDragRange(View child) { return 0; } /** * Return the magnitude of a draggable child view's vertical range of motion in pixels. * This method should return 0 for views that cannot move vertically. * * @param child Child view to check * @return range of vertical motion in pixels */ public int getViewVerticalDragRange(View child) { return 0; } /** * * @param 拖拽点击的child view * @param pointerId ID of the pointer attempting the capture * @return true 表示可以捕获这个子view;即可以被拖拽,false 不运行 */ public abstract boolean tryCaptureView(View child, int pointerId); /** * 返回值表示child View left的位置,一般直接返回left * 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 拖拽的子View * @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; } /** * Restrict the motion of the dragged child view along the vertical axis. * The default implementation does not allow vertical motion; the extending * class must override this method and provide the desired clamping. * * * @param child Child view being dragged * @param top Attempted motion along the Y axis * @param dy Proposed change in position for top * @return The new clamped position for top */ public int clampViewPositionVertical(View child, int top, int dy) { return 0; } }
那么如何让子view 可拖拽呢,简单的实现3个回调方法
class DragHelperCallback extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(View child, int pointerId) { return true; } //返回值表示view 新的left @Override public int clampViewPositionHorizontal(View child, int left, int dx) { return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return top; } }
这样实现的时候,我们发现了一个问题,view的拖拽可以超出父view的边界,这时候clampViewPostion.. 方法就不能简单的返回 left 了
class DragHelperCallback extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(View child, int pointerId) { return true; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { //可能我们给自定义的layout 设置了padding属性 final int leftBound = getPaddingLeft(); //left 的 最大界限 是布局的宽 - padding - 拖拽view的宽 final int rightBound = getWidth() - leftBound -child.getWidth(); //系统帮我们判断的left 和 我们的margin(margin可能为负) 取最大值,在和 最大界限取最小值 return Math.min(Math.max(leftBound,left),rightBound); } @Override public int clampViewPositionVertical(View child, int top, int dy) { final int topBound = getPaddingTop(); final int bottomBound = getHeight() - topBound -child.getHeight(); return Math.min(Math.max(topBound,top),bottomBound); } }
实现拖拽释放后的自动滚动(拖拽)功能
// 如果我们应该在这个方法中 // 调用settleCapturedViewAt(int, int) or flingCapturedView(int, int, int, int). // 如果调用了其中一个 ViewDragHelper会进入 STATE_SETTLING 状态 // 如果没有 会进入STATE_IDLE 状态 @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { mDragHelper.settleCapturedViewAt(200,200); //不要忘记调用invalidate(), invalidate(); }
拖拽后的移动用的是scroller 实现的 ,所以重写 layout 的 computeScroll 方法
@Override public void computeScroll() { if (mDragHelper.continueSettling(true)){ invalidate(); } }
ViewDragHelper还有一个移动View的方法是
smoothSlideViewTo(View child, int finalLeft, int finalTop)
可以在任何地方调用,效果和settleCapturedViewAt()类似,因为它们最终都调用了forceSettleCapturedViewAt()来启动自动滚动,区别在于settleCapturedViewAt()会以最后松手前的滑动速率为初速度将View滚动到最终位置,而smoothSlideViewTo()滚动的初速度是0。forceSettleCapturedViewAt()里有地方调用了Callback里的方法,所以再来看看这个方法:
/** * Settle the captured view at the given (left, top) position. * * @param finalLeft Target left position for the captured view * @param finalTop Target top position for the captured view * @param xvel Horizontal velocity * @param yvel Vertical velocity * @return true if animation should continue through {@link #continueSettling(boolean)} calls */private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) { final int startLeft = mCapturedView.getLeft(); final int startTop = mCapturedView.getTop(); final int dx = finalLeft - startLeft; final int dy = finalTop - startTop; if (dx == 0 && dy == 0) { // Nothing to do. Send callbacks, be done. mScroller.abortAnimation(); setDragState(STATE_IDLE); return false; } final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel); mScroller.startScroll(startLeft, startTop, dx, dy, duration); setDragState(STATE_SETTLING); return true;}
可以看到自动滑动是靠Scroll类完成,在这里生成了调用mScroller.startScroll()需要的参数。再来看看计算滚动时间的方法computeSettleDuration():
private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) { xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity); yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity); final int absDx = Math.abs(dx); final int absDy = Math.abs(dy); final int absXVel = Math.abs(xvel); final int absYVel = Math.abs(yvel); final int addedVel = absXVel + absYVel; final int addedDistance = absDx + absDy; final float xweight = xvel != 0 ? (float) absXVel / addedVel : (float) absDx / addedDistance; final float yweight = yvel != 0 ? (float) absYVel / addedVel : (float) absDy / addedDistance; int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child)); int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child)); return (int) (xduration * xweight + yduration * yweight);}
clampMag()方法确保参数中给定的速率在正常范围之内。最终的滚动时间还要经过computeAxisDuration()算出来,通过它的参数可以看到最终的滚动时间是由dx、xvel、mCallback.getViewHorizontalDragRange()共同影响的。看computeAxisDuration():
private int computeAxisDuration(int delta, int velocity, int motionRange) { if (delta == 0) { return 0; } final int width = mParentView.getWidth(); final int halfWidth = width / 2; final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width); final float distance = halfWidth + halfWidth * distanceInfluenceForSnapDuration(distanceRatio); int duration; velocity = Math.abs(velocity); if (velocity > 0) { duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); } else { final float range = (float) Math.abs(delta) / motionRange; duration = (int) ((range + 1) * BASE_SETTLE_DURATION); } return Math.min(duration, MAX_SETTLE_DURATION);}
直接看14~19行,如果给定的速率velocity不为0,就通过距离除以速率来算出时间;如果velocity为0,就通过要滑动的距离(delta)除以总的移动范围(motionRange,就是Callback里getViewHorizontalDragRange()、getViewVerticalDragRange()返回值)来算出时间。最后还会对计算出的时间做过滤,最终时间反正是不会超过MAX_SETTLE_DURATION的,源码里的取值是600毫秒,所以不用担心在Callback里getViewHorizontalDragRange()、getViewVerticalDragRange()返回错误的数而导致自动滚动时间过长了。
在调用settleCapturedViewAt()、flingCapturedView()和smoothSlideViewTo()时,还需要实现mParentView的computeScroll():
@Overridepublic void computeScroll() { if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); }}
- View滚动帮助类ViewDragHelper解析
- Viewdraghelper解析
- ViewDragHelper解析
- 自定义可拖拽View(工具类ViewDragHelper的用法)
- 神器ViewDragHelper完全解析
- ANDROID VIEWDRAGHELPER源码解析
- Android ViewDragHelper源码解析
- ViewDragHelper 深入解析
- Android ViewDragHelper源码解析
- Android自定义View(五)ViewDragHelper完全解析 自定义ViewGroup神器
- View的滚动原理简单解析
- ViewDragHelper一个帮助你轻松完成滑动操作的辅助类
- ViewDragHelper自定义view保存view的位置
- Android的ViewDragHelper源码解析
- Android的ViewDragHelper源码解析
- 史上最全的Android ViewDragHelper解析
- Android的ViewDragHelper源码解析
- 【FastDev4Android框架开发】神器ViewDragHelper完全解析,妈妈再也不担心我自定义ViewGroup滑动View操作啦~(三十三)
- 阿里云服务器的MySQL莫名其妙的挂了 之 优化篇
- 隐马尔可夫模型Hidden Markov Model
- 容易混淆的知识点
- poj1426 Find The Multiple
- 值得推荐的C/C++框架和库 (真的很强大)
- View滚动帮助类ViewDragHelper解析
- %-3d在C语言中的含义是什么?
- poj3260&&hdu3591(多重背包+完全背包)
- 2016寒假训练——字典树
- 安卓图看清安卓
- 【Data Algorithms_Recipes for Scaling up with Hadoop and Spark】Chapter1 Secondary Sort
- 更好的设计数据库还有就是数据库语句优化~~(主要是mysql)
- ProgressDialog的简单应用
- MySQL创建用户与授权方法