原文链接 Android View滑动处理大法

关于触控式操作来说,滑动是一个特别重要的手势操作,如何做到让应用程序的页面滑动起来如丝般顺滑,让用户感觉到手起刀落的流通感,是开发人猿需求重点解决的问题,这对提升用户体会是最为重要的工作。本文就将探讨一下,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即可。笔直方向依此类推。

Android View滑动处理大法

Scroll手势简略是因为它是直接来源于事情,且速度较慢,并不需求额定处理,所以全体逻辑处理流程并不杂乱。

在GestureDetector中的辨认便是在ACTION_MOVE时,检查滑动过的间隔,这个间隔(由sqrt(dx x dx, dy x dy)假设大于touch slop,就会触发onScroll手势回调。

Fling手势

Fling也便是快速滑动,便是手指在屏幕上使劲的『挠』一下,手势的关键是手指在屏幕快速滑过一小段短间隔,就像把一个小球弹出去的感觉相同。关于Fling手势来说,最重要的是速度,水平方向的速度和笔直方向的速度,能够理解为高中物理常讲到的平抛运动相同。

Android View滑动处理大法

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的可滑动方向是笔直正交的,那么就不会有抵触的问题。

Android 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

原创不易,打赏点赞在看收藏共享 总要有一个吧