往期回顾,专栏目录,更新动态,优惠政策,版权须知
温馨提示:如这是第一次接触《重学安卓》,可通过上述链接快速了解《重学安卓》专栏,获取 专栏目录、试读内容、更新动态 和 发展状况。
截至目前,专栏已对 体系化文章 实施 3310 余次修订,数十位群友告诉我,受专栏启发 亦开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创开源库 和 提供内推机会,订阅后可随时进群交流。
·
重要提示:
阅读本文最佳时机是,您已吃过 阅读 “源码” 或 “源码分析文” 时 找不到头绪的苦。
您还没吃过苦,那您先不要着急阅读本文。您得吃过苦,才会有体会。
在您吃够这方面苦后,您才有机会发现,本文正是专用于解决 “如何找到正确打开方式” 的困扰。
我们绝不通篇贴源码,而是基于广泛实践和反思,在累积过大量样本 乃至足以排除掉所有干扰信息后,点到为止揭露 View 坐标体系 等机制最核心本质,方便您理解其真实的设计缘由,乃至笃信使用项目中。
前言
相信多数小伙伴都是通过《Android 开发艺术探索》作为自定义 View 进阶的启蒙书,
该书完整论述了 “客户端 View 开发” 最少必要的基础,包括 View 坐标系,测量、布局、绘制流程,触控和滑动冲突处理等等,
不过受限于篇幅等因素,该书对一些现象仅仅是做了忠实记录,没有进一步追溯和确立造成该现象的缘由,这也导致部分读者在阅读后感到云里雾里,难以理解记忆和灵活应用,
比如关于 ScrollView 惯性滑动 ScrollX、ScrollY 取值的计算,书中的描述是:
当 “内容左边缘” 在 “视图左边缘” 左边时,mScrollX 为正值,反之为负值;
当 “内容上边缘” 在 “视图上边缘” 上边时,mScrollY 为正值,反之为负值,
那你可知为何是这样,10 分钟后还能记得这规律么,
为此,本期我们扮演一回源码设计师,从源头出发推导和铺垫 View 系统设计的来龙去脉,相信阅读后你会 “知其然” 亦 “知其所以然”。
文章目录一览
- 前言
- 开天辟地的交互方式
- First of all
- Secondly
- And then
- However
- So
- View 系统的坐标系 为何如此设计?
- View 坐标 为何如此设计?
- 为何能做到 惯性滑动?
- 惯性滑动的测量值 为何如此设计?
- 为何能做到 位移?
- 事件分发 为何如此设计?
- 综上
开天辟地的交互方式
世界上第一台 全触屏智能手机,是 2007 年苹果发布的 iPhone。在那之前,人类社会根本没有这样的移动设备。
换言之,缔造者 重新定义 并引领了后来十几年里移动设备的 用户体验习惯:
上一期《滑动冲突本质》篇 我们介绍过,由于人们使用 “智能手机” 多是为了打发无聊或查阅信息,因而 最普遍的操作意图即是浏览,而最普遍操作方式之一即是 单手握持 并通过大拇指划动屏幕。
当然,缔造者们早人们一步想象到未来这情况,
所以不妨继续发挥想象力,想象一下,当时的缔造者们,对这样一种 “全触屏设备” 有着怎样规划,以及为实现这些规划,需要存在哪些 “最少必要成分”,
First of all
我们需确保这台 “全触屏设备” 是个如假包换 交互式计算机。无论 2007 年移动蜂窝数据网络处于 2G 情况,还是未来 5 年内处于 5G 情况,
因为就算云手机,作为全触屏移动设备,也需能在本地 “独立处理” 最少必要计算 —— 世界上绝不存在完全剥离算力的智能手机。
云计算并不代表将计算任务全外包给云端,而是将 特定计算任务 外包给云端。移动设备必然需要本地算力,以便至少能支撑 “操作系统” 对硬件资源的调度和对上层软件的支持。
此外,正因为是交互式计算机,这台独立全触屏设备,务必包含 “输入设备” 和 “输出设备”。
输出设备很好理解,例如 “屏幕、外放” 等,当然我们今天谈的是 “View 系统”,所以这里我们只关注屏幕。
与此同时,与传统台式机不同的是,全触屏设备的 “输入设备” 也是屏幕,用户对图形化元素 发起指令,即是靠这块屏幕。
至此,我们可做个小结:
1.我们构想的 “全触屏移动设备”,务必是一台 “具备独立算力” 的交互式计算机。
2.从而它的背景状况是,通过屏幕负责 “图形化元素展示” 和 “接收用户指令”。
光是确立这两点,就不难望见 “全触屏设备” 操控 “图形化元素” 的整个逻辑 —— 分两步:
一步是 接收指令,
另一步是 基于指令 “实时渲染” 屏幕内容。
于是,我迫不及待要进入下一环节,使该逻辑完整落地。
Secondly
通过上一节追溯和分析,我们确知所处背景环境是,全触屏可移动设备务必是一台交互式计算机,并且全触屏同时兼任 “输入设备” 和 “输出设备” 两大职能,
因而,为在这背景下完成 “符合用户直觉、所见即所得” 的操控体验,View 系统会分别准备两套组件,基于这两种设备对用户体验负责,
其中,“触摸事件组件” 对应 “输入设备”,而 “排版渲染组件” 对应 “输出设备”。
于是,对输入设备来说,我们需要一个或多个坐标系,为 “触控操作” 提供 “实时坐标反馈”,从而能 基于 “实时坐标” 和 “预设指标” 辨别我们在触屏上操作,以便输送 “符合预期” 指令,通知 “排版渲染组件” 完成相应工作。
同时,对输出设备来说,我们同需要一个或多个坐标系,以便根据 “触控指令或布局” 实时排版和渲染图形化元素。
换言之,如一个 “图形化元素” 跟随你手指移动,该过程必然是分两步走:先是根据触控获取 “坐标轨迹和指令”,然后是根据 “坐标、指令、相对差” 等信息计算出 “加速度” 等数值,并根据这些数值通过某些组件,来实现元素内容惯性滑动,或元素本身位移。
之所以分两步走却能符合用户直觉,又是因为这些触控信号被设计为 “实时发送”,例如刷新率 60hz 的设备每隔 16ms 截取一次(当前的坐标),也即 每一段从直觉上 “看似轻松连贯” 的划动操作,实际都包含无数次 “输入指令” 和 “输出渲染” 交替执行。
那么至此,我们可做个小结:
1.为在 交互式计算机 背景下实现 “符合用户直觉、所见即所得” 操作,我们需分别为 “输入设备” 和 “输出设备” 准备 触摸事件组件 和 排版渲染组件,分别用于 实时输送指令 和 实时展示结果。
2.触摸事件组件 和 排版渲染组件 分别需要一个或多个坐标系,来为触控和排版提供坐标依据,以及基于 “坐标轨迹” 和 “预设的指标” 来辨别输入指令,以期通知对应渲染工作执行。
确立这两点后,我们终于可开始对 “View 系统” 做具体构思~
And then
作为缔造者,我们首先从 “输入系统” 开始构思,
—— 请对着手中四角圆润、黑乎乎的 “板砖” 发呆一下:通常我们触控操作都会有哪些,
1.点击(Click),点击按钮,即按下(Down)、松开(Up),
2.划动(Fling),划动内容,即按下(Down)、快速滑动(Move)、松开(Up),且让内容 “保持惯性” 滑动一小段距离。
3.拖拽(Drag),拖拽元素,即按下(Down)、缓慢移动(Move)、松开(Up)。
由此可见,划动和拖拽在使用过程中,需要一些指标来做出区分:
滑动容器中的 “内容” 要能惯性滑动,非滑动容器中的 “元素” 要能拖拽 —— 要是反其道而行:
“内容” 划动松开后立刻停止滑动,就会感觉很突然;
“元素” 拖拽松开后还在漂移,就会感觉很不安,不知它要去哪,
而假如所处场景既是(列表)内容,又要能拖拽(列表)item,则需进一步提供指标来区分,例如长按(LongClick)或其他方式,让场景默认处于划动模式、被动处于拖拽模式。
至此,我们可做个小结:
1.常见触控操作包括 点击、划动、拖拽,而它们又可被分解为 按下、移动、松开 等 原子操作。
2.不同场景下,需恰当处理后续滑动渲染,以免造成 “不符直觉” 用户体验。
于是,从每一次完整触控过程中,我们可最终得到一条坐标轨迹、在期间被识别和定性的原子操作( Down、Move or Up),及基于原子操作而被定性的宏观操作(Click、Fling or Drag)。这些宏观操作、原子操作以及坐标轨迹,都会被反馈到排版和渲染组件,从而,排版和渲染组件:
1.根据坐标差,来判断出滑动方向,
2.根据坐标差,结合系统预设坐标截取周期(例如 16ms),来判断滑动加速度,
3.根据被辨别宏观操作模式,来决定是继续惯性滑动,还是戛然而止。
符合预期的 “触控事件组件” 的 背景基石,就此落地。
然而,触控事件并非输入设备 “一个人的事”,它的存在也须兼顾排版渲染系统。
因而,在 排版渲染系统背景下,也就有了 Android 事件分发系统。具体如下: