重学安卓3-3:不如我们 从零开始设计一套 视图系统

作品简介
往期回顾专栏目录更新动态优惠政策版权须知
温馨提示:如这是第一次接触《重学安卓》,可通过上述链接快速了解《重学安卓》专栏,获取 专栏目录、试读内容、更新动态 和 发展状况。
截至目前,专栏已对 体系化文章 实施 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 事件分发系统。具体如下:
创作时间: