别靠肉眼:用 ffmpeg 抽帧拼图客观验证 UI 动画

给 App 做 AI 聊天功能时,遇到一个问题:键盘弹出的瞬间,底部输入栏明显闪动。加了 adjustResize 后我「觉得」好了——但动画就几百毫秒,到底是真平滑了,还是我看顺眼了?这篇讲怎么用一段录屏 + ffmpeg 抽帧拼图,把「流不流畅」从主观感受变成看得见的客观证据

一个说不清的「闪动」

场景是一个 edge-to-edge 的聊天页,底部是输入栏。点输入框、键盘弹出的那一刻,输入栏会闪一下——位置跳、还有一下发黑。

聊天页:底部是输入栏,键盘弹出的瞬间就在这里发生闪动

问题不在于改不改得动,而在于怎么确认改好了

UI 动画的 bug 特别容易陷进这种主观泥潭。我需要一个能定格、能回放、能对比的客观依据。


先说那个怀疑的修复:adjustResize

根因其实不复杂。开了 edge-to-edge(enableEdgeToEdge)后,窗口默认可能走 pan 模式——键盘弹出时系统平移整个窗口;而 Compose 这边又用 imePadding() 给输入栏做了 inset 动画。两套机制同时作用、互相打架,过渡帧里就出现了位置跳变和未绘制的黑块。

修法是给 Activity 显式声明 adjustResize,让 IME 以动画 inset 的形式平滑下发,不再 pan:

<activity
    android:name=".MainActivity"
    android:windowSoftInputMode="adjustResize" />

改完一行,我「觉得」好了。但改完不能拍脑袋说好了——尤其是动画这种主观的东西。下面才是这篇的主角。


把动画「定格」:录屏 + 抽帧拼图 ⭐

思路很简单:把时间轴铺平成空间。录一段交互过程,按固定帧率抽出动画那段的每一帧,平铺成一张图——整条动画轨迹一眼看完,哪一帧不对一目了然。

两条命令:

# 1. 录制交互过程(最多 3 秒),触发动画后等它录完再 pull
adb shell screenrecord --time-limit 3 /sdcard/rec.mp4
adb pull /sdcard/rec.mp4 .

# 2. 归一帧率 → 截动画时间窗 → 缩放 → 平铺成一张图
ffmpeg -i rec.mp4 \
  -vf "fps=30,select='between(n\,15\,42)',scale=200:-1,tile=7x4" \
  -frames:v 1 montage.png

-vf 这串滤镜链是关键,拆开看:

为了让对比公平,修复前后用完全相同的参数各录一段、各拼一张。


怎么读这张图

判据很直接:

先看修复前——键盘上来的过程中,中途蹦出一块黑色区域,这就是肉眼只能感觉到「抖了一下」的真身:

修复前:键盘弹出过程中蹦出黑色闪帧

再看修复后,相同时间窗、相同拼图参数。键盘平滑滑入,输入栏始终贴着键盘顶部一起上移,没有任何黑块或跳变:

修复后:键盘与输入栏平滑同步上移,无异常帧

两张图一摆,「修好了没」不再需要争论。一个技巧:如果差异藏在局部,给 ffmpeg 加一段 crop 只截关键区域再放大,跳变会更刺眼。


为什么这招好用

肉眼调动画总会陷进主观泥潭。给它一把「看得见」的客观标尺,很多说不清的体验问题,瞬间就有了答案。


这篇是和 Claude Code 一起完成的:它驱动 adb/ffmpeg 跑完整条录屏抽帧流程,也是它在拼图里先发现了那帧黑块。验证方法本身,也是协作里磨出来的。