ViewDragHelper 部分原理分析
2016-04-03 16:07
453 查看
前言
本周用 ViewDragHelper 实现几个自定义 ViewGroup ,如“QQ 5.0 侧滑菜单”、”仿 SlideMenu 的侧滑菜单”、”ListView 的左滑删除”等。发现,ViewDragHelper 确实是一个不错的工具类,相比自己手写 onTouchEvent 可以省很多代码。但是,在使用过程中也产生了不少的疑问。关于滑动的实现
在 Android 中,滑动有两种方式:显示位置的改变
ScrollTo、ScrollBy 只改变 View 的显示位置(内容),而不改变真实的位置。使用 ScrollTo 滑动后,会产生一个滑动值:view.getScrollX(),表示该 View,滑动了多少距离。而在此过程中,view.getLeft() 一直为 0。ScrollTo 将 View 滑动到绝对位置而ScrollBy 是对 ScrollTo 的简单封装,将 View 滑动到相对位置。
View.class:
public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { // scrollTo 不断刷新显示位置 postInvalidateOnAnimation(); } } }
可以看出,scrollTo 在滚动过程中,不断记录将当前位置记录在 mScrollX 和 mScrollX,然后通过 postInvalidateOnAnimation 刷新显示位置。在此过程中,会不断调用 onScrollChanged,产生回调。
/* x,y 是移动前坐标 */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
scrollBy 将本次滚动的相对值累加到 mScrollX 和 mScrollY
真实位置的改变
真实改变 View 位置的方法有这么几种:offsetLeftAndRight、offsetTopAndButtom
view.setLeft(left)、view.setRight(right)(属性动画同理)
LayoutParams
这种方式将改变 View 的真实位置,底层调用 invalidate(),让 onDraw 方法被调用,view 将在新的位置重绘。通过 getLeft() 获取左边界和父容器的距离。
而 ViewDragHelper 的移动,就是改变其真实位置:
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); ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft); } if (dy != 0) { clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy); ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop); } if (dx != 0 || dy != 0) { final int clampedDx = clampedX - oldLeft; final int clampedDy = clampedY - oldTop; mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy); } }
关于兼容低版本
经过测试,ViewDragHelper 无法 2.3 的模拟器生效,让我们从代码中找出原因。View.class(api 8)
public void offsetLeftAndRight(int offset) { mLeft += offset; mRight += offset; }
public void offsetTopAndBottom(int offset) { mTop += offset; mBottom += offset; }
低版本的 offsetLeftAndRight 不会主动重绘。所以,只需要手动刷新即可。
Callback:
/* ViewDragHelper 的拖拽监听回调 */ private class MyCallback extends ViewDragHelper.Callback{ //... /* 当位置发生改变时调用(此时,已经发生了view位置的改变,松开手之后的改变也可以监听) */ @Override public void onViewPositionChanged(View changedView, int left, int top,int dx, int dy) { // ... // 兼容低版本:低版本view的offsetLeftAndRight不会主动刷新界面,因此需要手动调用 invalidate(); } // ... }
smoothSlideView 原理
ViewDragHelper 不仅封装了事件监听,而且封装了动画和滑动逻辑。只需要 调用 helper.smoothSlideView () 即可实现滑动。从代码看出,其内部是 Scroller ,并在的 continueSettling 不断 offsetLeftAndRight、offsetTopAndBottom
public boolean continueSettling(boolean deferCallbacks) { // ... if (dx != 0) { ViewCompat.offsetLeftAndRight(mCapturedView, dx); } if (dy != 0) { ViewCompat.offsetTopAndBottom(mCapturedView, dy); } // 位置改变回调 if (dx != 0 || dy != 0) { mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy); } // ... }
而 offsetLeftAndRight、offsetTopAndBottom 默认刷新,所以这里不需要手动调用 invalidate(),做兼容处理。
ViewCompatBase.java
static void offsetTopAndBottom(View view, int offset) { final int currentTop = view.getTop(); view.offsetTopAndBottom(offset); if (offset != 0) { // We need to manually invalidate pre-honeycomb final ViewParent parent = view.getParent(); if (parent instanceof View) { final int absOffset = Math.abs(offset); ((View) parent).invalidate( view.getLeft(), currentTop - absOffset, view.getRight(), currentTop + view.getHeight() + absOffset); } else { view.invalidate(); } } }
关于 TouchSlop
TouchSlop 即触摸敏感度,是指能触发移动的最短手指滑动距离,数值越低越灵敏。ViewDragHelper 提供创建对象的 create 方法中,就有指定敏感度的。可以在代码中看到,这里的 sensitivity 经过处理,变成一个更容易理解的值,即 sensitivity 越大越灵敏ViewDragHelper.java
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; }
相关文章推荐
- AsyncTask详解
- 读写Word的组件DocX介绍与入门
- accept函数
- 编译安装 Centos 7 x64 + tengine.2.0.3 (实测+笔记)
- NYOJ 16 矩形嵌套
- 林语堂名著全集
- org.apache.jasper.JasperException: /mainpage.jsp (line: 25, column: 2) According to TLD or attribute
- [Java]不同包的类之间的继承
- 第五章 springboot + mybatis
- 模板方法模式和外观模式
- 《构建之法》第四章读后感
- windows下编译ZThread-2.3.2
- 5-26 单词长度 (15分)
- mac 升级vim
- BestCoder Round #78 (div.2)_A_ CA Loves Stick
- oracle ocp 学习day6总结(oracle 启动过程与参数文件)
- J2EE 日志错误
- 仿AcFun 投食香蕉界面
- 位图的实现
- LintCode 删除排序链表中的重复元素