先看一段录屏。同一个触摸序列,六次点击、一次长按、一次点击已选中的标签、一次慢拖、一次快速滑动,每个手势占4.2秒,对齐在触摸发生的那一刻。上面是UIKit,下面是Codename One。你能分辨出来吗? 这比看一张静态截图有用得多。用户能感觉到选中状态移动之前的延迟,能感觉到玻璃跟手指的方式,能感觉到松手之后的回弹。一个标签栏可以颜色完全正确,但在手里就是感觉不对。 Codename One是一个开源框架,用一套Java或Kotlin代码库构建原生iOS、Android、桌面和Web应用。上周的iOS 27主题已经很接近了,但那些交互仍然不够满意。要做得更接近,意味着要在真实触摸过程中记录UIKit的呈现层,把运动拟合到弹簧方程,并重建玻璃效果。 **为什么代码设对了,手感还是不对** 在代码里设置选中的标签,并不会产生完整的原生交互。UIKit是在控制器管理的标签栏上,为真实触摸执行那个运动的。所以探针使用了XCUITest的点击、长按和拖拽。 还有一个麻烦:UIKit并没有附加一个包含完整轨迹的简单CAAnimation。它逐帧更新图层数值。真正有用的记录是每个显示刷新时刻的呈现几何:位置、边界、变换、不透明度,以及解释这些的触摸时间戳。 每一帧都截一张图,会改变时序,足以污染测量结果。探针把几何记录和可选的帧快照分开。快照运行适合比较同一运动状态下的透镜,不适合拟合它的时序。同样,在已知背景上做无损截图能揭示模糊,而压缩视频会抹掉我们恰恰需要检查的那些边缘。 这些约束都记录在原生探针里。它们解释了为什么“录屏里看起来差不多”是不够的。 **点击、长按、拖拽,响应方式各不相同** 对于固定目标,阻尼弹簧遵循这样一个方程:x是位置,xₜ是目标,ω控制自然频率,ζ是阻尼。点号表示对时间的导数。临界阻尼弹簧的zeta等于1;更低的阻尼允许过冲。实现使用的是测量得到的常数,而不是靠肉眼重新调整整个动画。 当过冲后的剩余距离低于约0.195点时,行程停止。拖拽和按压模型包含一个测量得到的20毫秒触摸延迟。这些细节之所以可见,是因为你的手指给了动画一个移动的参照点。 当前的TabGlassMotion和TabGlassGesture包含了公式、保留的表格和停止规则。重新生成工具会用捕获数据检查闭式解,并重写那些仍然以表格形式存在的通道。 **拖动时,别让每个标签都被选中** 拖拽过程中,透镜按照自己的弹簧跟随手指,所以它可以滞后。松手时,UIKit选择手指下方的标签,而不是离滞后透镜最近的那个。然后透镜从当前位置和速度向那个标签沉降。 在这个交接点重置速度,会产生可见的中断。快速滑动后吸附到最近的透镜位置,会产生另一种不连续。按压已经选中的标签,也必须抬起并脉冲,而不能凭空产生向另一个标签的位移。长按必须保持抬起状态直到松手,而不是播放一个固定时长的点击动画,然后在你手指底下结束。 松手会加入一点轻微晃动。模型为它使用了一个共同的起点,尽管捕获数据显示不同手势类型的起始时间略有差异。这个近似,正是要在短演示之外保留完整手势套件的原因之一。 **背景变了,图标还得看得清** 一个鲜艳的标签图标,并不是简单地用带透明度的颜色画上去。它的结果取决于它后面的玻璃。探针在色调扫描中读取UIKit的颜色矩阵,VibrancyMatrix重建了这个变换。 对于非灰色的深色色调向量t,测量得到的形式是:e等于0.5乘以t的最小值除以最大值;M等于e乘以单位矩阵,加上0.5减e,再乘以全1向量与t转置的外积,除以t与自身的点积;偏移量等于t;输出等于M乘以背景,加上偏移量。单位矩阵是I,全1向量是三行一列的列向量。灰色色调有自己的公式,所以这个表达式不是可以粘贴到实现里覆盖一切的通用分支。浅色外观使用不同的形式。鲜艳度来源和录制的测试夹具把这些情况放在一起。 选中托盘还会变换标签栏已有的玻璃。Graphics.colorMatrixRegion在一个形状和可选遮罩内应用这个操作。抬起的透镜需要单独的操作:圆形斜面周围的折射、颜色色散、轮廓、边缘光和阴影。glassLensRegion提供了这条渲染路径。 这就是为什么调整一个圆角矩形的不透明度只能做到这一步。这个效果会改变它下面的内容。 **把玻璃动画留在GPU上** 如果渲染器错过了区分它的那些帧,再准确的运动模型也是浪费。iOS标签栏玻璃现在运行在GPU上,而不是经过一个CPU模糊并在其周围做GPU同步。可变图像复用它们的模板纹理。标签动画避免请求第二次整组件重绘,透镜暂存纹理……

打开网易新闻 查看精彩图片