08 · GT 主题 slider 视觉/触摸错位调查(立项前调查)
调查对象:设置→驾驶偏好→能量回收自定义滑块(GT 主题,Lemans/YU7 GT)。 实测发现视觉 thumb 与触摸判定位置偏差约 96px,是 N801PROB-132401「误触改写」 的物理放大器。本文给反编译证据链与四个调查问题的定案。 组件库无源码,证据全部来自 jadx 反编译 + aar 资源,存疑处已标注。
1. 现象与实测回放
实测(YU7 GT,2880×1800 @ density 360,即 1dp=2.25px;bugreport-mach-UKQ1.240411.001-2026-07-23):
- 当前值 67 时,用 input 注入单点触摸做三点标定(以 SET 日志为准):
- 摸 x=1265 → SET:65
- 摸 x=1280 → 值不变(同值 67)
- 摸 x=1376 → SET:79
- 三点共线:
touchValue(x) = (x − 749.4) × 0.12612(值/px),即触摸映射零点半程 跨度 ≈793px - 同值 67 时,视觉 thumb(用户感知位置)实测在 x≈1376
- 错位 = 1376 − 1280 ≈ 96px,触摸判定位置在视觉 thumb 左侧
- 后果:用户「以为摸到 thumb」实际落在映射为大值的位置,一次轻触即变值,
松手触发
SliderPreference.alignAdjustProgress取整(≤1 次变化门), 67 被静默改写为 70——132401 误触改写的物理放大器
2. 组件身份与版本对账
| 项 | 结论 | 证据 |
|---|---|---|
| 页面组件 | MiCarIconSeekBarPreference(能量回收三处 controller 均用它) | BrakingStrengthPreferenceController.java:23/51;base/settingsBaseUi/.../MiCarIconSeekBarPreference.java:70-71 inflate R.layout.micar_ui_pref_icon_seek_bar |
| 实际 widget | MiCarIconSlider extends micarx.androidbasewidget.IconSlider | micar_ui_pref_icon_seek_bar.xml:27;MiCarIconSlider.kt:14 |
| thumbWidth | 未显式设置,走样式默认 154dp | androidbasewidget/res/values/values.xml:133 micar_appcompat_icon_seekbar_thumb_width=154dp;样式 Widget.MiCar.IconSlider.DayNight 设置 thumbWidth(:511) |
| 主题委托 | GT_STYLE → GtIconSliderThemeDelegate(abw)+ GtSliderThemeDelegate(material) | IconSliderThemeDelegate.create(:51-63)、SliderThemeDelegate.create(:27-39) |
| 版本对账 | 本地 3.2.2.1 / 3.2.6 / 3.2.6-RC11-SNAPSHOT 三版,slider 相关类逐字节等价(3.2.2.1 为内联实现,3.2.6 起抽委托类,公式相同) | jadx diff 三包:GtIconSliderThemeDelegate、IconSlider、BaseSlider、GtSliderThemeDelegate、Slider 全部 zero-diff(3.2.2.1 仅结构差异,行为相同) |
调查用 aar(均已 jadx 反编译):
~/.gradle/caches/modules-2/files-2.1/micarx.support/{androidbasewidget,material}/3.2.6-RC11-SNAPSHOT/…/*.aar
对照:…/3.2.6/…、…/3.2.2.1/…。
3. 反编译证据链:视觉与触摸是两套几何
density 换算(360dpi,×2.25,车机 AutoDensity px 承重):154dp=346.5px, 8dp=18px,77dp=173.25px,6dp=13.5px。
3.1 视觉侧:thumb 是 154dp 宽的 pill,行程 = 视图宽 − thumbWidth
IconSlider.drawIconAndText(IconSlider.java:311-375):
int availableWidth = ((getWidth() - getPaddingLeft()) - getPaddingRight()) - w; // w=mThumbWidth=154dp
float centerX = Math.max(this.mThumbPadding,
Math.min((availableWidth * (getValue() - getValueFrom())) / sum, availableWidth - this.mThumbPadding));
float positionX = centerX + (w / 2);
float thumbLeft = (positionX - (w / 2)) - this.mOffsetXLeftCurrent; // = centerX(无按压偏移)
float thumbRight = positionX + (w / 2) + this.mOffsetXRightCurrent; // = centerX + w- pill(视觉 thumb)区间 =
[centerX, centerX + 154dp],centerX = (W − 154dp)·f - 数值文本居中于 pill(:374)
- GT 背景 drawable
micar_appcompat_icon_seekbar_thumb_bg_gt:左/右两条 6dp 竖线, 各内缩thumb_line_inset_horizontal=8dp,中间透明(appcompat aar values.xml:619-621)
GtIconSliderThemeDelegate.drawActiveTrack(:53-66):
int availableWidth = ((width - paddingLeft) - paddingRight) - thumbWidth;
float activeRight = ((availableWidth * (value - valueFrom)) / sum) - offsetXLeftCurrent;
canvas.drawLine(startX, sliderCenterY, activeRight, sliderCenterY, paint);- 蓝色填充右端 = pill 左缘 = (W − 154dp)·f,填充高度 = trackHeight(80dp)整段
3.2 触摸侧:thumb 被当作 2×8dp 的点,行程 = 视图宽 − 2×8dp
BaseSlider.generateTouchPosition(BaseSlider.java:1442-1447):
int effectivePadding = this.mThemeDelegate.getEffectiveThumbPadding(this.thumbPadding, this.mThumbPaddingGT);
this.touchPosition = ((x - this.trackSidePadding) - effectivePadding) / (this.trackWidth - (effectivePadding * 2));GtSliderThemeDelegate.getEffectiveThumbPadding(:22-24)返回 mThumbPaddingGTmThumbPaddingGT = micar_material_slider_thumb_padding_horizontal = 8dp(BaseSlider.java:427;material values.xml:67)- trackRadius=9999dp(非零)→ BUTT 圆头 →
trackSidePadding=0(BaseSlider.java:445-457;appcompat values.xml:491) - 抓取锚点
computeTouchOffset(:1468-1473):thumbX = trackSidePadding + effectivePadding + progress × (trackWidth − 2×effectivePadding)
3.3 对照 Normal 主题:零错位(设计自洽)
NormalSliderThemeDelegate.getEffectiveThumbPadding(:23-25)返回 thumbPadding;
IconSlider.onLayout(:268-272)setThumbPadding(mThumbWidth / 2) = 77dp。
触摸行程 [77dp, W−77dp] 与视觉 pill 行程 [0, W−154dp] 锚定完全一致——
Normal 主题下触摸点恒在 pill 中心,无错位。GT 把触摸 anchor 从 77dp 换成 8dp,
视觉没跟着改,两套几何就此脱钩。
3.4 GT 另有的行为差异(与错位相关但不构成根因)
GtIconSliderThemeDelegate.shouldIgnoreClick()(:121-123)= true;IconSlider.onTouchEvent(:418-420)每个触摸事件强制setIgnoreClick(true)→BaseSlider.transformTouchEvent(:1459-1466)把 DOWN 事件平移到当前 thumb 位置, 点轨道不跳值,整条轨道变成「就地抓取 thumb」- 按压扩展:
applyPressOffsets(:127-135)中间值双侧外扩 12dp、端点附近单侧 24dp (IconSlider.java:129-130,dimen 见 abw values.xml:131-132),经mOffsetXLeft/RightCurrent动画(:733-829)瞬时加宽 pill 并同步回缩填充端, 属按压暂态,不改变静态错位结论
4. 96px 的精确复现(结论:成立,且不是常量)
用三点标定反解布局:L + p = 749.4,W − 2p = 792.9,代入 p=18px(8dp)
→ 视图 L≈731.4px,W≈828.9px(两点定解,与组件几何自洽)。
f=0.67(值 67)时各视觉锚点 vs 触摸判定点(屏幕 px):
| 锚点 | 公式 | 计算值 | 与触摸点(1280)之差 |
|---|---|---|---|
| 触摸判定 67 | L + 18 + (W−36)·0.67 | 1280.0 | 0(SET 日志实证) |
| 蓝填充右端(pill 左缘) | L + (W−346.5)·0.67 | 1054.6 | 触摸在其右 225px |
| pill 左标记线 | 左缘+18+6.75 | 1079.4 | 右 201px |
| 数值文本中心(pill 中心) | 左缘+173.25 | 1227.9 | 右 52px |
| pill 右标记线 | 左缘+346.5−18−6.75 | 1376.4 | 左 96.4px ≈ 96px ✓ |
| pill 右缘 | 左缘+346.5 | 1401.1 | 左 121px |
实测「视觉 67 在 1376」与 pill 右标记线(GT thumb 的右缘竖线,用户最直觉的 thumb「位置」)精确吻合(差 0.4px,标定舍入内)。
错位通式(density 无关形式,dp;f=归一化值):
Δ(f) = x_视觉右标记线(f) − x_触摸(f) = (135 − 138·f) dp = (303.75 − 310.5·f) px @2.25x
- f=0.67 → 95.7px ≈ 实测 96px ✓
- f=0.978 → 0;f 更小 → 最大 303.75px(f=0)
- 斜率来源:
(W − 2×8dp)与(W − 154dp)两行程的半 thumb 锚定差77dp − 8dp = 69dp,加上行程斜率差
「96px」只是 f≈0.67 处的一次采样,不是固定 inset 常量;代码里不存在 96px 字面量。错位是「触摸 anchor 8dp vs 视觉 anchor 77dp」的结构性脱钩。
对其它锚点的通式(供不同「thumb 位置」读法):
- vs 填充右端:
触摸 − 填充端 = 18 + 310.5·f px(触摸恒在右,随值增大) - vs 数值文本中心:
触摸 − 文本中心 = 310.5·f − 155.25 px(f=0.5 为零点) - 逻辑触摸点恒落在 pill 内部(pill 内偏移
18 + 310.5·f ∈ [18, 328.5]⊂ [0, 346.5]), 即「摸 pill 任意处都能 grab」恒成立
5. 四个调查问题的结论
Q1 错位根因 —— 成立(结构性,非 track inset)
GT 特有偏移不是 track inset/padding,而是触摸 anchor 常量:
GtSliderThemeDelegate.getEffectiveThumbPadding 用 mThumbPaddingGT=8dp
替代了 Normal 的 thumbPadding=thumbWidth/2=77dp(BaseSlider.java:1443/1470;
NormalSliderThemeDelegate.java:23-25 对照),而视觉侧(IconSlider.drawIconAndText:320、
GtIconSliderThemeDelegate.drawActiveTrack:60)仍按 154dp pill 行程绘制。
触摸点与视觉右标记线的偏差 135 − 138·f dp,f=0.67 时即实测 96px。
Q2 「按值线性偏左」—— 部分成立,取决于视觉锚点
- 以 pill 右标记线/右缘为「视觉 thumb」:成立。触摸点在视觉左侧,
偏差
303.75 − 310.5·f px随值线性减小,f≈0.98 归零 - 以蓝填充右端为「视觉 thumb」:方向相反。触摸点在填充端右侧,
偏差
18 + 310.5·f px随值线性增大 - 共同点:都是值线性、无常数 96px;「系统性偏左」的说法只在右缘锚点下成立
Q3 按下瞬间同值 onValueChange —— 对 IconSlider GT 证伪(纯 DOWN 无回调)
DOWN 链路:transformTouchEvent(ignoreClick=true)→ computeTouchOffset
把事件精确平移到当前 thumb 位置(BaseSlider.java:1468-1473,数学上 touchValue
≡ 当前值)→ snapThumbToValue(:1538-1547)|value − current| < 1e-4 直接
return,不派发。
132401 修复前提中观察到的「同值回调」真实来源是 DOWN 后的首个微抖 MOVE:
float 值变(≥1e-4)触发 dispatchOnChangedFromUser(:1545),int 可能不变。
Settings 侧 P0b「int 真实变化才计数」(SliderPreference.java onValueChange)
对该路径仍精确有效。附:IconSeekBar(AppCompatSeekBar 系)是另一套触摸实现,
不构成本单证据。
Q4 setValue 后重绘路径 —— 组件库无滞留,责任在 Settings 调用侧
Slider.setValue → BaseSlider.setValuesInternal(:613-628):同值早退,
变值则 dispatchOnChangedProgrammatically() + postInvalidate()(:627)。
重绘时 thumb 位置/填充端/文本全部从 getValue() 实时取(IconSlider.java:320/299)。
「内部值 70、视觉滞留 67 达 6s」的滞留物是 thumbText 字符串——它由
Settings 通过 setThumbText() 写入(IconSlider.java:263-266),组件库不会
自动把 value 格式化成文本。旧版 MiCarIconSeekBarPreference.onSliderProgressChanged
无参 updateThumbText() 在 setValue 之前读旧值,P0a 已修(用传入 progress)。
视觉滞留半边责任在 Settings 调用侧,组件库升级对该半边无效。
6. 对 N801PROB-132401 的放大机理
- 用户想「摸 thumb 再拖/确认当前值」,手指落在 pill 右缘附近(f=0.67 时 x≈1376,触摸映射值 79)——与 thumb 判定位置差 ~96px
- 若组件行为是「点轨道即吸附」:
DOWN → touchValue=79 → 1 次变值回调 → 松手 onStopTrackingTouch → alignAdjustProgress(mProgressChangeTimes==1) → 79 取整 80 → 静默 SET 改写。错位越大,误命中越大值/越小值的概率越高 - 当前代码(3.2.2.1/3.2.6/RC11 一致)GT 强制
ignoreClick=true,点轨道是 「就地 grab」不跳值,放大链第 2 环在组件侧被卸掉——但 Settings 侧onStopTrackingTouch仍会触发alignAdjustProgress,取整门完全靠 Settings 的计数精度(P0b 修复)兜底 - ⚠️ 出入待解:07-23 实测记录「摸 1265→SET:65、摸 1376→SET:79」是 点按即变值行为,与本地三版代码「grab 不跳值」矛盾;三点又精确落在 无偏移映射线上(斜率 0.12612 = 100/792.9),不像 grab+微抖的随机结果。 可能:实测 ROM(26.07.15.Stable)所带组件库构建与本地三版行为不同 (如 GT 不强制 ignoreClick),或注入 tap 伴随了微拖。待立项后复测定案 (置信度:几何结论不受此影响;放大链第 2 环的成立条件受此影响)
7. 修复建议(组件库侧落点)
| 方案 | 落点 | 评价 |
|---|---|---|
| A. 触摸 anchor 对齐视觉(推荐) | GtSliderThemeDelegate.getEffectiveThumbPadding 改返回 thumbPadding(与 Normal 一致);或 BaseSlider 在 GT 下以 thumbWidth/2 作 effectivePadding | 一行级改动,直接消除错位;副作用:轨道两端各 77dp 变为 clamp 死区(0/100 吸附区)——Normal 主题历来如此,行为可接受;需回归 GT 全系 slider/seekbar |
| B. 视觉行程对齐触摸行程 | IconSlider.drawIconAndText 的 availableWidth 改用 W − 2×mThumbPaddingGT,pill 从 8dp 内缩行程起画 | pill 会压到轨道外/布局边缘,改动面大,不推荐 |
| C. 不动几何,维持 grab 语义 | 仅文档化「GT 整条轨道皆可 grab,thumb 位置仅视觉参考」 | 在 ignoreClick=true 下错位无实际功能危害(只有心理预期偏差),但「点按跳变」语义若被任何调用方打开(setIgnoreClick(false)/旧版组件)立即复活风险 |
配套验证方法(复用 132401 手法):uiautomator dump 拿 slider bounds →
input 两点标定触摸映射 → SET 日志对账 → 修复后期望
触摸(v) ≡ 视觉 pill 中心(v)(方案 A 后)。
Settings 侧无需改动(ignoreClick 由组件强制;P0a/P0b 修复独立于本错位)。
8. 残留待办
- 实测 ROM 组件版本与「点按即变值」复测:确认 26.07.15.Stable.U.CN 整机
所带 micarx 组件构建号及其 GT
shouldIgnoreClick行为;必要时用同 ROM 台架 重做三点标定(当前矛盾见 §6.4) - 方案 A 若立项:回归 Normal/Ultra 主题与 VerticalSlider(同族委托
GtVerticalSliderThemeDelegate/NormalVerticalSliderThemeDelegate未查) - 「视觉 thumb」措辞与报告人/产品对齐:填充右端 vs pill 右缘,两锚点的 错位方向相反(§5 Q2),文案需统一
GtSeekBarThemeDelegate(material,6527B,能量回收未用)未查;若其它页面 用 GT SeekBar 系,同法对账一遍
附:关键证据定位
| 证据 | 位置 |
|---|---|
| 触摸映射 | BaseSlider.generateTouchPosition(material BaseSlider.java:1442-1447);computeTouchOffset(:1468-1473) |
| GT 触摸 anchor=8dp | GtSliderThemeDelegate.getEffectiveThumbPadding(:22-24);mThumbPaddingGT 赋值(BaseSlider.java:427);dimen micar_material_slider_thumb_padding_horizontal=8dp(material values.xml:67) |
| Normal 对照=77dp | NormalSliderThemeDelegate.getEffectiveThumbPadding(:23-25);IconSlider.onLayout setThumbPadding(mThumbWidth/2)(:270-271) |
| 视觉 pill 行程 | IconSlider.drawIconAndText(:319-326);GtIconSliderThemeDelegate.drawActiveTrack(:53-66)/drawInactiveTrack(:69-83) |
| pill 右标记线 | drawable micar_appcompat_icon_seekbar_thumb_bg_gt.xml(左右竖线+inset);dimen inset=8dp/line=6dp(appcompat values.xml:619-621) |
| 点轨道不跳值(GT) | GtIconSliderThemeDelegate.shouldIgnoreClick=true(:121-123);IconSlider.onTouchEvent 强制 setIgnoreClick(true)(:418-420);BaseSlider.transformTouchEvent(:1459-1466) |
| setValue 触发重绘 | BaseSlider.setValuesInternal(:613-628,postInvalidate@:627) |
| thumbWidth=154dp | abw values.xml:133;Settings 布局未覆盖(micar_ui_pref_icon_seek_bar.xml:27-35) |
| 版本等价 | jadx diff:3.2.2.1 / 3.2.6 / 3.2.6-RC11-SNAPSHOT 三包 slider 类行为一致 |
| 实测标定 | transcript 8f0fce22(1265→65 / 1280→67 / 1376→79);device-profile density=360 |