共计 4141 个字符,预计需要花费 11 分钟才能阅读完成。
原文链接 Android View 滑动解决大法
对于触控式操作来说,滑动是一个特地重要的手势操作,如何做到让应用程序的页面滑动起来如丝般顺滑,让用户感觉到手起刀落的晦涩感,是开发人猿须要重点解决的问题,这对晋升用户体验是最为重要的事件。本文就将探讨一下,Android 中 View 的滑动相干常识,以及如何做到丝般顺滑。
如何让 View 滑动起来
View 的滑动是 GUI 反对的一项根本个性,就像触摸事件一件,这是废话,平台如果不反对,你还搞个毛线。
View 滑动的基本原理
咱们先来看一下 Android 中实现 View 的滑动的基本原理。其实屏幕并没有动啊,一个 View 的可绘制区域,对于屏幕来说,对于 view tree 来说都是没有变动 的。父布局给某一个 View 的绘制区域是在 layout 之后就确定好了的,当 View 的实在高度或者宽度超过了这块可绘制区域,那么就须要滑动才能够把整个 View 做到用户可见。View 外部通过两个要害成员变量 mScrollX 和 mScrollY 来记录滑动之后的坐标,View 自身有 mLeft 和 mTop 来标识本人绝对于父布局的坐标地位,那么当有滑动的时候,在此 View 当中具体要绘制的区域就变成了以 mLeft+mScrollX 和 mTop+mScrollY 为终点的区域了。由此 View 便滚动起来了。
如何实现 View 的滑动
对于开发人猿来说,实现 View 的滑动,须要关注三个重要的办法,也即是 View#scrollBy),View#scrollTo)以及 View#onScrollChanged),这是实现滑动的三个最为外围的办法。
scrollBy 提供的参数是须要滑动的间隔,而 scrollTo 则是须要传入要滑动到的指标坐标值,这两个办法都是要批改 mScrollX 和 mScrollY 的值,实质上是一样的。而 onScrollChanged 则是一个回调,用以告诉更新了的滑动地位。
Scroll 手势
要想让 View 滑动起来,离不开事件手势的反对。最简略也是最间接的手势就是 onScroll 手势,这个在 GestureDetecor 中能够辨认出此手势,或者本人去间接解决 touch event 也能够得出此手势。这个并不简单,就是间接通过 touch 事件来计算滑动多少间隔就好了,依照 View 预设计的能够滑动的方向,比方横向就计算不同工夫点 MotionEvent 的坐标值,失去一个程度间隔 deltaX,而后调用 scrollBy 即可。垂直方向依此类推。
Scroll 手势简略是因为它是间接来源于事件,且速度较慢,并不需要额定解决,所以整体逻辑解决流程并不简单。
在 GestureDetector 中的辨认就是在 ACTION_MOVE 时,查看滑动过的间隔,这个间隔(由 sqrt(dx x dx, dy x dy)如果大于 touch slop,就会触发 onScroll 手势回调。
Fling 手势
Fling 也即是疾速滑动,就是手指在屏幕上使劲的『挠』一下,手势的要点是手指在屏幕疾速滑过一小段短距离,就像把一个小球弹出去的感觉一样。对于 Fling 手势来说,最重要的是速度,程度方向的速度和垂直方向的速度,能够了解为高中物理常讲到的平抛静止一样。
GestureDetector 辨认 Fling 的逻辑是,在 ACTION_UP 时,查看此次事件的速度,如果程度方向速度或者垂直方向速度超过了阈值,便会触发 Fling 手势回调。
留神:注意 Scroll 与 Fling 的区别,Scroll 是慢的,不关怀工夫与速度,只关怀滑动的间隔,是在 ACTION_MOVE 时,手指并未有来到屏幕时就触发了,只有是 ACTION_MOVE 还在持续,就会持续触发 onScroll,并且 ACTION_UP 时终止整个 Scroll,而 Fling 只关怀速度,不关怀间隔,是在 ACTION_UP 时,手指来到了屏幕了(此次事件流解决结了)才会触发。
VelocityTracker
Fling 事件速度是决定性的,认真看 GestureDetector 的处理过程会发现它应用了一个叫做 VelocityTracker 的对象,来帮忙解决一些对于速度的具体逻辑,那么有必要深刻理解一下这个对象。
VelocityTracker 应用起来并不简单,获取它的一个对象后,只须要一直的把 MotionEvent 塞给它就能够了,而后在须要的时候让其计算两个方向上的速度,而后就没有而后了:
velocityTracker = VelocityTracker.obtain();
onTouchEvent(MotionEvent ev) {velocityTracker.addMovement(ev);
if (want to know velocities) {velocityTracker.computeCurrentVelocity(100);
vx = velocityTracker.getXVelocity();
vy = veolocityTracker.getYVelocity();
be happy with vx and vy.
}
}
这个类的实现,值得认真看一下,它次要的实现都是用 JNI 去实现,可能是因为计算形式较简单,所以 computeCurrentVelocity)办法也阐明了,让你真用的时候再调,这个不必去管细节实现。重点看一下这个类,外面有一个对象池,用以缓存对象,并且创建对象的形式并不是间接 new,而是用其 obtain)办法。这里用的是叫享元(Flyweight Pattern)的设计模式,也就是说 VelocityTracker 对象其实是共享的。
顺滑如丝
后面提到了,让 View 滑动,只须要调用 scrollBy 或者 scrollTo 即可,但这个吧,是间接批改了 mScrollX,mScrollY,而后 invalidate,View 下次 draw 时就间接在把指标区域内容绘制进去了,换句话说这两个办法滑动是霎时跳格局的。
一般来说,这也没有问题,就像 onScroll 手势,ACTION_MOVE 时,一直的 scrollBy 刚刚滑过的间隔,都还 okay,没有什么问题。
然而对于 Fling 事件就不行了,Fling 事件,也即疾速滑动,要求短时间内进行大间隔滑动,或者像有跳转的需要时,也是短时间内要滑动大间隔。如果间接 scrollBy 或者 scrollTo 一步到位了,会显得 相当的突兀,体验相当不好,卡顿感特地强。如果能像做动画那样,在肯定工夫内,让其平滑的滑动,就会如丝般顺滑,体验好很多。Scroller 就是专门用来解决此问题的。
Scroller
[Scroller]()是对滑动的封装,并不是 View 的子类,其实它跟 View 一点关系也没有,也不能操作 View,实际上它与属性动画相似,它仅是一个滚动地位的计算器,通知它起始地位和要滚动的间隔,而后它就会通知你地位随工夫变动的值。其实这是一个中学物理题,也即给定初始地位,给定要滚动的间隔,以肯定的形式来计算每个工夫点的地位。具体的计算形式由 mInterpolater 成员来管制,默认是 ViscousFluid,是按天然指数为减速度来计算的,具体的能够查看 Scroller 的源码。如果不喜爱默认的计算形式,能够本人实现个 Interpolator,而后在结构时传进去。
Scroller 的作用在于实现安稳滑动,不让 View 的滚动呈现跳跃,比方滑动一下 ListView,开始滑动时的地位是 x0,y0(ActionDown 的地位),要向下滑动比方 500 个像素,不安稳的意思是,从 x0,一下跳到 x0+500 的地位。要安稳,就要一直的一点点的扭转 x 的值而后 invalidate,这也就是 Scroller 的典型应用场景:
Scroller scroller = new Scroller(getContext());
scroller.startScroll(x0, y0, 500, 0);
而后在 computeScroll 时:
if (scroller.computeScrollOffset()) {int currX = scroller.getCurrX();
int currY = scroller.getCurrY();
invalidate(); // with currX and currY}
computeScrollOffset 在滚动没完结时返回 true,也就是说你须要持续刷新 view。返回 false 时表明滚动完结了,当然也就没有必要再刷新 view(当然如果你乐意也能够持续刷,然而地位啥的都不变了,所以刷了也白刷)。
滑动抵触解决
对于 View 的滑动,最难搞的问题便是手势抵触解决,特地是当页面的构造变得复杂了当前。一般来讲,滑动手势,是让某一个 View 沿着某一个方向『平移』一段距离,如果某一个页面中只有一个 View 是能够滑动的,或者页面中不同的 View 的可滑动方向是垂直正交的,那么就不会有抵触的问题。
所谓滑动抵触,是指父 View 和子 View 都承受滑动手势,并且方向又是一样的,这时就产生了滑动抵触,常见就是 ScrollView 中套着 ListView(这个通常是垂直 Y 方向下面有滑动抵触),或者 ViewPager 中套着 ScrollView(这个是程度 X 方向上有滑动抵触)。
要想解决好滑动抵触问题,须要先的确好整体的设计方案,有了大的准则后,就容易用技术计划找到解法。最现实的计划,也是目前用的最多的计划就是在子 View 的边界设定一个 margin 区域,当 ACTION_DOWN 在 margin 区域以外,认定滑动手势归父 View 解决,否则交由子 View 解决。像一些全局手势也是要用如此的计划,当点击间隔屏幕肯定范畴内 (margin 区域) 认定此事件归以后页面解决,否则就认定为全局手势,就好比从屏幕右边向右滑动,很多应该将此辨认为 BACK 到上一页,但如果离右边较远时滑动,就会是页面外部的滑动事件(如果它有可滑动的组件的话,事件手势会被其滑消耗掉)。
参考资料
- Detect common gestures
- Flyweight pattern
- Design Patterns – Flyweight Pattern
- Animate a scroll gesture
- Android Scroller simple example
原创不易,打赏 , 点赞 , 在看 , 珍藏 , 分享 总要有一个吧