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
实际 widgetMiCarIconSlider extends micarx.androidbasewidget.IconSlidermicar_ui_pref_icon_seek_bar.xml:27;MiCarIconSlider.kt:14
thumbWidth未显式设置,走样式默认 154dpandroidbasewidget/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)返回 mThumbPaddingGT
  • mThumbPaddingGT = 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)之差
触摸判定 67L + 18 + (W−36)·0.671280.00(SET 日志实证)
蓝填充右端(pill 左缘)L + (W−346.5)·0.671054.6触摸在其右 225px
pill 左标记线左缘+18+6.751079.4右 201px
数值文本中心(pill 中心)左缘+173.251227.9右 52px
pill 右标记线左缘+346.5−18−6.751376.4左 96.4px ≈ 96px ✓
pill 右缘左缘+346.51401.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 的放大机理

  1. 用户想「摸 thumb 再拖/确认当前值」,手指落在 pill 右缘附近(f=0.67 时 x≈1376,触摸映射值 79)——与 thumb 判定位置差 ~96px
  2. 若组件行为是「点轨道即吸附」:DOWN → touchValue=79 → 1 次变值回调 → 松手 onStopTrackingTouch → alignAdjustProgress(mProgressChangeTimes==1) → 79 取整 80 → 静默 SET 改写。错位越大,误命中越大值/越小值的概率越高
  3. 当前代码(3.2.2.1/3.2.6/RC11 一致)GT 强制 ignoreClick=true,点轨道是 「就地 grab」不跳值,放大链第 2 环在组件侧被卸掉——但 Settings 侧 onStopTrackingTouch 仍会触发 alignAdjustProgress,取整门完全靠 Settings 的计数精度(P0b 修复)兜底
  4. ⚠️ 出入待解: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. 残留待办

  1. 实测 ROM 组件版本与「点按即变值」复测:确认 26.07.15.Stable.U.CN 整机 所带 micarx 组件构建号及其 GT shouldIgnoreClick 行为;必要时用同 ROM 台架 重做三点标定(当前矛盾见 §6.4)
  2. 方案 A 若立项:回归 Normal/Ultra 主题与 VerticalSlider(同族委托 GtVerticalSliderThemeDelegate/NormalVerticalSliderThemeDelegate 未查)
  3. 「视觉 thumb」措辞与报告人/产品对齐:填充右端 vs pill 右缘,两锚点的 错位方向相反(§5 Q2),文案需统一
  4. GtSeekBarThemeDelegate(material,6527B,能量回收未用)未查;若其它页面 用 GT SeekBar 系,同法对账一遍

附:关键证据定位

证据位置
触摸映射BaseSlider.generateTouchPosition(material BaseSlider.java:1442-1447);computeTouchOffset(:1468-1473)
GT 触摸 anchor=8dpGtSliderThemeDelegate.getEffectiveThumbPadding(:22-24);mThumbPaddingGT 赋值(BaseSlider.java:427);dimen micar_material_slider_thumb_padding_horizontal=8dp(material values.xml:67)
Normal 对照=77dpNormalSliderThemeDelegate.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=154dpabw 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