Android View滑动处理大法

迭代薄雾
• 阅读 836

原文链接 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到上一页,但如果离左边较远时滑动,就会是页面内部的滑动事件(假如它有可滑动的组件的话,事件手势会被其滑消耗掉)。

参考资料

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

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
迭代薄雾
迭代薄雾
Lv1
天子呼来不上船,自称臣是酒中仙。
文章
3
粉丝
0
获赞
0