PLAN_floatball_polish.md 19 KB

PLAN · 悬浮球观感与空闲表现优化(待评审 · 未实施)

⛔ 本方案已弃用(2026-10-14)

用户明确表示「上面的方案放弃」,并改为直接要求去掉点击球后的跳动/展开动画 —— 那件事已单独做完(见 README.md §8 的「点击后先跳一下」一节与 src/floatball.js)。 本文件仅作为当时的分析留档,不要再照它实施;其中"球的直径 / 按钮文字 / 空闲淡化" 三个议题仍未定,未做任何改动。


2026-10-14。起因:三个观感问题(按钮文字 / 球直径 / 空闲淡化或贴边)。 本文只做方案讨论,一行代码都没改(dist 与设备上的产物仍一致)。 规格书原文取自资料库 doc JZf9xTgLXBFgjXJ4dJWVsU §10(本次逐条核对过,不是凭记忆)。


0. 先摆事实:现状基线(真机实测值)

设备 Pixel 5a / 1080×1920 / density 2。

项 值 来源
折叠态窗口 88×88 dp(242×242 px) collapsedSizeDp() = 热区 64 + 两侧内边距 12×2
展开态窗口 308×264 dp(616×528 px) ball-layout.txt 实测
球体 / 热区 / 呼吸环 Ø48 / Ø64 / Ø60 dp §10.1;环在 buildBall() 里硬编码 60
子按钮 / 扇形半径 Ø48 / R=96 dp §10.1
标签 高 24 dp、11px 字,实测最长「隐藏悬浮球」75 dp 宽 §10.1;ball-layout.txt
展开窗口里给标签留的位置 labelMax = 96 dp(硬编码) fanSizeDp() —— 实测最长标签只用了 75 dp,有 21 dp 是白留的
展开态遮屏面积 308×264 = 81,312 dp² 计算

规格书 §10 里与本次三个问题直接相关的原文(逐条摘录,后面反复引用):

条款 原文要点
§10.1 尺寸 「默认位置:屏幕左侧,左边距 12,垂直居中 → 球心 (36, 406)」「球体 Ø48(透明热区 64)」「扇形半径 R=96」「子按钮 Ø48」「标签胶囊 高 24、圆角 99、距按钮右侧 4px、11px 字」「展开动画 180ms」
§10.1 不透明度 「1.0(三态统一。球内是图片,半透明会发灰;状态一律靠外环表达)」
§10.3 四个按钮 图标分别是「播放三角 / 方块」「四宫格」「eye-off」「power」;「按钮数量上限就是 4 个。R=96 时相邻按钮间隙约 17.7px,已接近舒适下限」
§10.2 按钮坐标 给了可直接落码的按钮中心 / 按钮左上角 / 标签左上角 + 标签宽(38 / 59 / 71 / 60)
§10.4 三态 运行中外环「有(外扩到 r=28)」
§10.9 技术要点③ 「与无障碍点击冲突:脚本坐标点击可能点到球;运行时建议降不透明度,脚本侧点击区域避开球位」
§10.10 与运行条的关系 「若后续觉得重复,最省事的收敛方式是 App 前台时把球降到 0.3 透明度(保留但不抢注意力),而不是隐藏。」
§9 待确认项③ 「触控目标:既有控件视觉高 26px < 44pt → 实现时热区撑到 44」

⚠️ 有一处规格书内部张力要先说清楚,否则后面每条都会绕: §10.1 说不透明度「1.0 三态统一」,而 §10.9③ / §10.10 又建议"运行时降透明度""App 前台降到 0.3"。 合理的读法是:§10.1 约束的是「不要用透明度表达三种状态」(状态只许由外环表达); 而"按场景"降低透明度(App 前台 / 运行中)是规格书自己建议的。本方案的 Q3 就建立在这个读法上。


1. Q1 · 展开后要不要去掉按钮文字?

1.1 先问清"去掉它想解决什么"

两个完全不同的诉求,答案也不同:

诉求 正确的是
视觉啰嗦(一排"运行 / 回到 App / 隐藏悬浮球 / 退出应用"看着像工具条) 减视觉噪声
展开时那块透明窗口太大(挡住内容、引擎要绕开一大片) 减窗口尺寸

关键事实:这两件事在本实现里是耦合的 —— 窗口宽度里那 96 dp 就是给标签留的。 所以"标签"不只是视觉元素,它直接决定展开时窗口多大,而窗口大小又会经 ball-rect.json 影响引擎的避让范围(引擎绕开的是一整个窗口矩形,不是只有按钮)。

1.2 三个方案

方案 窗口宽 遮屏面积变化 语义风险 前置工程
A 纯图标(彻底去文字) 308 → 208 dp −32.5% 高:现有图标是字符 ▦/⊘/✕,⊘(隐藏) 与 ✕(退出) 都是"让球消失",无文字时极易点错 需先把图标换成 §10.3 的线性图标(四宫格 / eye-off / power),从 Ardot H2/H3(34:23/34:37)导出 PNG
B 缩短文字 308 → 260 dp(最长 "运行/停止" 2 字) −15.6% 中:「隐藏悬浮球」压成「隐藏」还行,但「退出应用」压成「退出」可能被读成"退出扇形" 无
C 保留文字,只降视觉权重(更小字 / 更低底色 alpha / 去掉胶囊底) 不变 0% 无 无

方案 C 解决不了"窗口太大" —— 它只治观感。所以如果诉求是第二个,C 不算答案。

1.3 一条必须写下来的技术结论(它否掉了两个"看起来很聪明"的方案)

不能用「按下按钮时才把窗口变大」来浮出文字。 因为改窗口尺寸会让 WindowManager 把进行中的触摸流判为 ACTION_CANCEL —— 那一刻用户的手指还按着,这次点击会被直接取消掉。README §3 第 6 条就是这个坑的真事记录 (早期把 CANCEL 当 UP 处理,表现是"每次展开立刻又收起")。 ⇒ 标签要么常驻在窗口内,要么根本没有;没有第三种"按需变大"。

同时否掉的还有:空闲变小 / 触摸变大这类尺寸动画 —— 同理(改尺寸打断手势), 而且它还要在"手指正按着球"时改尺寸。要"退让"就用透明度,不要用尺寸。

1.4 推荐

首选「A + 一次性引导」的组合,理由是它同时解决两个诉求且不牺牲语义:

  • 图标先升级为 §10.3 的四个线性图标(四宫格 / eye-off / power / 播放-方块)。 这四个都是系统级通用隐喻,无文字也能独立表意;这是"纯图标"能成立的前提。
  • 窗口收窄到 208 dp(−32.5%),引擎避让范围同步变小。
  • 首次使用的前两次展开仍显示文字(ball.labelsSeen 计数落盘,≥2 后不再显示), 解决"第一次不知道哪个是哪个"。
    • ✅ 这一步不违反上一条结论:窗口宽窄的切换发生在展开这一刻(手势已结束), 不是按下那一刻。现在的展开本来就是"几何先到位再建视图",机制现成。

次选「B 缩短文字」(不想动图标资源时的折中):窗口 −15.6%,代价小但要接受语义折损。

若诉求纯粹是"视觉啰嗦":只做 C 即可,一行都不用改结构。

顺带一个白捡的优化(与选哪个方案无关):fanSizeDp() 里 labelMax = 96 是硬编码, 而实测最长标签只有 75 dp —— 21 dp 是白留的。改成按当前实际文案算(measurePillWidth 已经能算), 窗口立刻从 308 降到 ~287 dp,零语义代价。


2. Q2 · 球直径改小

2.1 关键:视觉直径与触控直径是两件事,本实现已经解耦

FB.DIAM = 视觉球体;FB.HOT = 透明热区(触控目标)。两者独立。 所以"改小好看"与"会不会不好戳"可以分开决定:

  • §9③ 自己强调"热区撑到 44";Android 官方最小触控 48 dp。
  • 现状 Ø48 视觉 / Ø64 热区:每边有 8 dp 的"看不见但能点"的余量 —— 本来就有,不是新问题。
  • 缩小视觉球会放大这个余量(球 Ø40 + 热区 Ø64 → 每边 12 dp)。余量的代价是误触: 点到球旁边的空气也会展开扇形。

2.2 推荐

球 Ø40(−17%)+ 热区 Ø56(每边 8 dp 余量,仍 > 48 dp 官方最小)。

  • 视觉得体,且不牺牲可点性;热区从 64 → 56 会顺带把折叠态窗口从 88 → 80 dp 缩小一点。
  • 不建议 Ø36:mark_only_412 已把品牌标记放大到球直径 87.4%,Ø36 时标记只有 ~31 dp (2x 屏 62 px),笔画细节很可能糊 —— 这一项只能真机看一眼再定,我先给结论:Ø40 稳,Ø36 待验。
  • 不建议保持 Ø48 不动 —— 但也不认为 Ø48 是错的:它是规格书值,动它属于主动偏离。

2.3 ★ 必答的一个副问题:球心坐标跟着变,规格书 §10.2 的表要重算

§10.1 的「左边距 12 → 球心 (36, 406)」里,球心 36 = 12 + 半径 24。 本实现的 anchorX = dp(FB.EDGE) = 12dp 语义是球的最左边缘离屏 12 dp。于是球改小后:

取法 球心 x(Ø40) 规格书 §10.2 坐标表 观感
边距仍 12(推荐) 12 + 20 = 32 整表 x 左移 4 dp 球"贴边距离"不变,符合 §10.1 字面
保球心 36(EDGE 改 16) 36 整表不动 球离屏 16 dp,看着比现在"离边远一点"

两取法都要重算 §10.2 / §10.11 的两张坐标表(它们全部由球心推导:按钮中心 = 球心 + R·(cosθ, −sinθ))。 建议选前者(贴边 12 不变)—— "贴边"是这件东西的观感核心,球心只是个中间量。

2.4 改直径的联动清单(漏一处就"看起来有点歪",且不会有任何报错)

# 位置 为什么
1 FB.DIAM 视觉直径本体
2 FB.HOT 热区;决定 collapsedSizeDp() → 折叠窗口 + 发布给引擎的 ball-rect.json
3 呼吸环尺寸(buildBall / drawBallAt 里硬编码的 60) 球 Ø40 而环仍 60,会变成"球很小、环很粗"。§10.4 说的是运行中"外扩到 r=28"(= Ø56)→ 建议环改成 DIAM + 16,天然符合 §10.4
4 FB.restoreAnchor() / FB.clampAnchor() / FB.snapToEdge() / FB.applyPosition() 这四处都用 dp(FB.DIAM) 反推球心(球心 = 窗口位置 + 内边距 + 球心在根里的偏移),改了直径不改这里,球会整体偏
5 buildBall() / drawBallAt() 里 (FB.HOT - FB.DIAM) / 2 球在热区里的居中偏移
6 tools/smoke.js 两条断言((64 + 12*2) * density、48 * density / 2) 写死了 64 / 48。这是好事:漏改会让自检立刻变红 —— 只要跑一次自检就能确认联动没漏
7 品牌图可读性 只能真机看(见 2.2)
8 tools/audit-values.js 若有引用直径的断言也要跟着改(当前没有,但改完跑一遍确认)

联动之所以这么多,是因为"球心反推"这条公式里有直径。这也是为什么不建议把直径做成可配置 —— 一个 Store 字段就能改球大小,会让上述 8 处里的一半变成"只有在某个取值下才错", 而设备上没有 linter。要改就改常量。


3. Q3 · 空闲 3 秒后淡化 / 贴边?

3.1 先纠正一个前提:"贴边"已经在做了

§10.5 就是"松手吸附最近边缘并记忆位置",已实现并真机验过(ball.side + ball.yRatio 落盘)。 所以 Q3 真正要讨论的是"要不要按时间自动淡化"以及"要不要自动收起扇形"。

顺带明确否掉一个变体:空闲 3 秒自动把球吸到边缘(球不在边上时自己滑过去)。 理由:① 球会在用户眼皮底下自己移动,观感惊悚;② 移动的目标位置可能正是用户下一个要点的地方; ③ §10.5 已经把"吸附"绑在"拖动松手"这个明确的用户动作上,自动吸附会破坏这个语义。

3.2 规格书里的三条相关原文,指向的都不是"按时间"

条款 触发条件 透明度
§10.1 三态之间(空闲/运行/报错) 统一 1.0,不要用透明度表达状态
§10.9③ 运行时(防脚本坐标点击误点球) 建议降
§10.10 App 在自己前台时(那时 App 内已有运行条,球是重复信息) 降到 0.3(原话)

三条的共同点:淡化与否取决于"此刻有没有替代品 / 有没有冲突",与"用户多久没碰它"无关。

而"按时间淡化"有三个实打实的问题:

  1. 在 TikTok 上球是唯一控制入口 —— 淡了之后用户"找不到球",而这恰恰是最需要它的场景。 规格书 §10 开篇就写它「是跨 App 唯一的控制入口与运行状态可视处」。
  2. 球内是图片,淡化会发灰(§10.1 明写)。球的底是 #030014,压到 0.3 后叠在明亮的视频上, 品牌标记基本糊掉 —— 剩下一个"脏兮兮的暗圆",比不淡更难看。
  3. 和"运行中"直接冲突:球最大的价值是"跨 App 也能看见现在跑没跑"。 3 秒不碰就淡,等于把这条价值在 3 秒后自动关掉。

3.3 推荐:按场景,不按时间

场景 表现 依据
App 在自己前台 球 → 0.35(用 0.3–0.35,理由见下) §10.10 原话;此时 App 内运行条已经承担了状态显示
在别的 App(TikTok 等) 1.0,永不淡 它是那里唯一的控制入口
扇形展开着 1.0(无论场景) 展开时用户正在读按钮,淡了没法用
触摸 / 拖动 / 状态切换的瞬间 立刻回 1.0 并保持 1.2 秒 否则"点了它却几乎看不见"
  • App 前台信号已经有了:ui.emitter.on("resume") 现成在用(main.js 里用它做"回到 TikTok"提醒)。 再加一个 "pause" 即可 —— resume → 淡,pause → 恢复 1.0。 离线桩也已经支持任意事件(env.emitter.emit("pause")),所以可以直接写断言。 备选信号是轮询 currentPackage()(引擎侧就是这么判断"TikTok 在前台"的), 但它多一个 setInterval、且依赖无障碍服务;ui.emitter 更轻,优先用它, 真机确认它确实在切走时触发(这是本方案唯一的未知数)。
  • 为什么用 0.35 而不是 §10.10 的 0.3:0.3 在深色底 #030014 上叠到亮色视频上几乎看不见。 这是对规格书的主动偏离,会写进注释与 README。若你更想严格照规格书,用 0.3 也行。
  • 淡化对象是整个窗口根视图(球 + 外环一起淡)。在"按场景"方案里这不矛盾: App 前台时状态已由运行条承担,所以环一起淡掉是对的。

3.4 "3 秒"这个念头最该落在哪里:扇形的自动收起

如果确实想要"一段时间没动静就退让",最合理的落点是扇形自动收起,而不是球的淡化:

  • 用户点了球、摊开扇形,然后什么都没点就走了 —— 这时球"一直摊着",既挡屏幕又占着引擎的避让区。
  • 现在只有"点任一按钮后自动收起"(上一轮加的),"展开后不点任何按钮"这条路没管。
  • 建议阈值 6 秒,不是 3 秒:扇形上有 4 个标签要读, 真机 dump 显示「隐藏悬浮球」这种 5 字标签读起来要时间,3 秒偏急。
  • 实现上要和 sync() 的三态刷新共存:FB.open 变化 → 重置计时器。

4. 三个问题的推荐汇总

问题 推荐 偏离规格书? 影响面
Q1 按钮文字 图标升级为 §10.3 线性图标 → 去文字(窗口 −32.5%);前两次展开仍显示文字做引导。过渡:缩短文案(−15.6%) 是(§10.1 有标签胶囊) fanSpecs/openFan/fanSizeDp/品牌图资源 + 1 个新字段
Q1′ 白捡的 labelMax 96 → 按实际文案算(实测只需 75),窗口 −7% 否 fanSizeDp 一行
Q2 直径 球 Ø40 + 热区 Ø56,边距仍 12 dp(球心 → 32,§10.2 表重算) 是(§10.1 是 Ø48/Ø64) 8 处联动(§2.4),其中 2 处是自检断言
Q3 空闲 按场景淡化(App 前台 0.35 / 其它 App 1.0)+ 扇形 6 秒自动收起;不做按时间淡化、不做自动吸附 §10.10 是(0.35 vs 0.3);自动收起规格书未提 floatball.js 新增一个"目标透明度"的唯一所有者 + ui.emitter.on("pause")

5. 若批准,实施顺序(每步都可独立验收)

  1. Q1′ + Q3 的自动收起(最小、零偏离、纯收益):fanSizeDp 按实测文案算;扇形 6 秒无操作自动收起。
  2. Q3 的按场景淡化:先验 ui.emitter.on("pause") 在真机上是否触发(这是唯一的未知数), 再落透明度逻辑。注意:同一时刻只能有一个"透明度的写者" —— 现有 fadeIn() 也在写 FB.root.setAlpha(),两处并发会出现"展开动画把淡化值顶掉" 这类没有报错、只有肉眼能看出的小毛病。必须收敛成一个 setBallAlpha(target) (这是上一轮"尺寸动画与建视图打架"的同类问题,别再踩一次)。
  3. Q2 直径:按 §2.4 的 8 处清单一次改完,跑 smoke.js 让那两条断言告诉你有没漏。
  4. Q1 图标:需要先从 Ardot 34:23 / 34:37 导出四个线性图标(PNG,2x/3x)→ 换掉字符图标 → 收窄窗口 → 加引导计数。

6. 验证计划(批准后照做)

离线(PC)

  • smoke.js:新增/修改断言 —— ① 窗口宽度随 labelMax 收敛;② 展开后 6 秒(env.flushIntervals() 或假时钟) 未点任何按钮 → FB.open === false;③ emit("pause") → 球 alpha 1.0,emit("resume") → 0.35; ④ 展开态下 resume 不淡化;⑤ 直径改动后折叠窗口尺寸断言(现有两条会自动变红,改完再绿)。
  • audit-values.js:若 Q1 走"引导计数",新字段要登记进 Store.FIELDS(场景 A/C/F 会覆盖)。
  • 铁律:新字段必须同时进字段表,否则重启后静默丢值(详情卷 B.9)。

真机(Pixel 5a)

  • ball-debug 打开,ball-layout.txt 核对新几何逐像素(这是唯一能看出"有点歪"的手段)。
  • 切到 TikTok / 回 App,各看一次透明度(ui.emitter 是否触发)。
  • 半透明状态下真的点一次球,确认能正常展开(alpha 不影响命中,但要实证)。
  • 引擎跑一轮,确认避让区随窗口收窄而变小(读 ball-rect.json)。
  • ⚠️ 跑任务时不要 uiautomator dump(会撞掉无障碍服务,把界面盖成系统设置页)。

7. 待你拍板

  1. Q1:走「图标升级 + 去文字」(要动图标资源),还是先走「缩短文案」过渡,还是只降视觉权重?
  2. Q2:Ø40/Ø56(推荐)、Ø36/Ø48(更小、待实测)、还是保持 Ø48/Ø64?球心取"边距 12(球心 32)"还是"保规格书球心 36"?
  3. Q3:按场景淡化(推荐)/按时间淡化(你原本的想法)/两者都要/都不做?扇形自动收起要不要一起做(建议 6 秒)?