03 · 主题系统与 GT 点击抑制(ignoreClick 全解)
本篇回答两个问题:① Normal/GT/Ultra 主题到底差在哪;② “GT 主题点轨道不跳值”是怎么 实现的。后者是 N801PROB-132401 家族所有”点击抖动/误取整”问题的组件库侧地基。
1. 主题如何生效
flowchart LR Attr["theme attr<br/>micarxThemeStyleName"] --> Read["ThemeStyleUtil.getThemeNameFromAttr<br/>BaseSlider.java:591-592"] Read --> Create["XxxThemeDelegate.create(themeName)"] Create -->|GT_STYLE| GT["Gt 实现"] Create -->|ULTRA_STYLE| Ultra["Ultra 实现(与 GT 逐行相同)"] Create -->|"null/其他"| Normal["Normal 实现(原样直通)"]
四套 delegate 接口各管一个控件族,互不继承(01 篇 §4):
| 接口 | 服务对象 | 方法数 | 点击抑制方法 |
|---|---|---|---|
SliderThemeDelegate(MAT,:15-88) | BaseSlider/Slider | 9 | 无 |
IconSliderThemeDelegate(ABW,:18-154) | IconSlider | ~13 | shouldIgnoreClick() |
SeekBarThemeDelegate(MAT,:19-175) | AppCompatSeekBar + IconSeekBar | 16 | shouldTransformTouchEvent() |
VerticalSliderThemeDelegate(ABW,:16-55) | VerticalSlider | 3 | 无(XML attr/setter) |
2. 三主题差异总表
Slider 族(SliderThemeDelegate)
| 方法 | Normal | GT / Ultra |
|---|---|---|
adjustThumbDrawableBounds | 按 thumbDiameter 等比缩放 | 保持 drawable 固有尺寸 |
getEffectiveThumbPadding | thumbPadding | thumbPaddingGT(8dp,dimens.xml:30) |
adjustInactiveTrackRight | 原样 | 非激活轨道从 thumb 右侧之外起画(GtSliderThemeDelegate:31-42) |
adjustActiveTrackRight | 原样 | 激活轨道在 thumb 左侧之外收笔(GtSliderThemeDelegate:45-56) |
isPressEnlargeEnabled | true | false → setPressEnlarge 被 BaseSlider:3205 直接拦截 |
isNormalTheme | true | false → processAttributes 把 thumb 换成 GT 竖条(6dp×32dp,BaseSlider:626-635) |
hasCustomAnim | false → TrackingAnimation(整体 scaleY 1.2) | true → SliderAnimation(轨道增厚+thumb 拉高) |
SeekBar 族(SeekBarThemeDelegate)与 IconSlider 族
- GT/Ultra 的共性改造:thumb 换 GT 竖条资源、thumb icon 置 null、textStart/End 强制改写为
"min"/"max"数字(GtSeekBarThemeDelegate:21-46、GtIconSliderThemeDelegate:23-46)、 轨道在 thumb 处挖空成两段(clip)、hint 文字悬在轨道上方、轨道中心下移(height+hintTextHeight)/2。 - 点击抑制:GT/Ultra 恒 true,Normal 恒 false:
- IconSlider:
shouldIgnoreClick()— GtIconSliderThemeDelegate:152-154 / UltraIconSliderThemeDelegate:153-155 / NormalIconSliderThemeDelegate:109-111 - IconSeekBar:
shouldTransformTouchEvent()— GtSeekBarThemeDelegate:157-159 / UltraSeekBarThemeDelegate:157-159 / NormalSeekBarThemeDelegate:123-125
- IconSlider:
3. GT 点击抑制的实现:触摸事件重锚定(本篇核心)
3.1 机制总览
不是丢弃事件,而是把事件 X 坐标平移到 thumb 当前位置,使下游一切逻辑看到的等效于 “用户精确按在 thumb 上”。
flowchart TD Down["ACTION_DOWN 落在轨道任意点<br/>例:x=800,thumb 实际在 x=300"] --> Compute["computeTouchOffset(down)<br/>BaseSlider.java:2314-2321"] Compute --> Formula["progress=(value−from)/(to−from)<br/>thumbX=sidePad+effPad+progress×(trackWidth−2·effPad)<br/>**mOffset = thumbX − down.getX() = −500**"] Formula --> Apply["之后所有事件(DOWN/MOVE/UP/CANCEL)<br/>event.offsetLocation(mOffset, 0) :2310<br/>触摸点被永久'偷'到 thumb 上"] Apply --> Down2["下游 generateTouchPosition(x−500)<br/>反算 touchPosition ≡ thumb 当前进度"] Down2 --> Snap["snapThumbToValue: 新值≡旧值<br/>|Δ| < THRESHOLD(1e-4) :2417"] Snap --> NoChange["**值不变、无 onValueChange**"] Apply --> Drag["MOVE: x' = x−500 跟手走<br/>等效按住 thumb 拖动 → 值随动"]
3.2 Slider 族链路(ignoreClick)
// BaseSlider.java
:268 private boolean ignoreClick; // 标志位
:595 ignoreClick = ...getBoolean(MiSlider_ignoreClick, false); // XML 来源,默认 false
:670 public void setIgnoreClick(boolean) // 代码来源
:2177 if (ignoreClick){ transformTouchEvent(event); } // onTouchEvent 入口拦截
:2304 protected void transformTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case ACTION_DOWN: computeTouchOffset(event); // 只在 DOWN 算一次
default: event.offsetLocation(mOffset, 0); // 全程平移
}
}谁强制打开:material Slider 层的 XML 默认 false,默认样式 Widget.MiCar.Slider 也未设;
真正强制的是 IconSlider——IconSlider.onTouchEvent:570-572 每个触摸事件先
if (delegate.shouldIgnoreClick()) setIgnoreClick(true)。所以 GT/Ultra 的 IconSlider
必然 ignoreClick=true。
3.3 SeekBar 族链路(shouldTransformTouchEvent,孪生实现)
// IconSeekBar.java:596-598
if (mSeekBarThemeDelegate.shouldTransformTouchEvent()) { // GT/Ultra true
transformTouchEvent(event); // AppCompatSeekBar:240-248
}
// AppCompatSeekBar.java:250-257 —— 用 getProgress() 与屏幕 rawX 算同一偏移
private void computeTouchOffset(MotionEvent down) {
getLocationOnScreen(mSeekBarLocation);
float progress = (getProgress() - getMin()) / (float)(getMax() - getMin());
float currentX = mSeekBarLocation[0] + getPaddingStart() + progress * visualWidth;
mOffset = currentX - down.getRawX();
}注意两族算偏移的坐标系不同:BaseSlider 用 view 内 x,AppCompatSeekBar 用屏幕 rawX。
BaseSlider 里有个同名死字段 mSeekBarLocation(:326,从未使用),grep 时勿混淆。
3.4 为什么用”偏移”而不是”丢弃事件”
| 方案 | 后果 |
|---|---|
| 丢弃轨道 DOWN | 按压动画、onStartTrackingTouch、按住轨道直接拖全部失效 |
| 偏移重锚定(现方案) | 动画/回调/手势全保留,只是值锚点换成 thumb |
这正是产品裁决”GT 主题点击仅按压动画、无值变化”(N801PROB-132401,2026-10-13)的技术
底座。车机默认样式 Widget.MiCar.Slider 的 labelBehavior=gone,所以纯 GT 点击用户只感知到
thumb/轨道动画,值不动。
3.5 三个结构性隐患(排障必读)
- mOffset 只在 DOWN 算一次,手势期间不更新。若手势中途程序性 setValue 改了 thumb 位置,偏移不刷新 → thumb 相对手指瞬移(拖动脱钩)。GT 下滑动中下方下发值回写会踩到。
- DOWN 瞬间微抖被固化进 mOffset:若下游(Settings 层)还有取整逻辑,微抖跨过 .5
边界即随机击穿——N801PROB-132401 第三轮”GT 点击抖动”(34→30/37→40 方向随机)的成因。
修法是 Settings 层读
isIgnoreClick()把整个取整关掉(387202/387204 PS4), 组件库不动。 - UP tap 补偿分支不受影响:
lastEvent记录的是偏移后坐标(:2282),tap 判定 (:2243-2246)在同一坐标系内自洽。
4. “96px 错位”坐标系线索(GT 按压白点错位调查用)
能量回收 KB 08 篇记录的 GT thumb 视觉错位,组件库侧的坐标链:
- 触摸换算带补偿:
(x − trackSidePadding − effectivePadding) / (trackWidth − 2·effectivePadding)(BaseSlider:2288-2289); - thumb 绘制中心不带补偿:
centerX = trackSidePadding + normalizeValue(value) × trackWidth(:2130),halo/label/hotspot 同(:1905/:2149/:2596-2598); setTrackRadius()非 0 时把trackSidePadding直接清零(:647-660,清零在 :659), 而Widget.MiCar.Slider样式带 trackRadius(appcompat public_styles.xml:590);- GT 下 effectivePadding=8dp、thumb 固有宽 6dp,触摸坐标系与绘制坐标系的常量差即错位 来源;具体像素值随 density 变,“96px” 字面常量未在代码出现,数值来源未确认。
5. 按压动画的主题分流(一图)
flowchart TD PDOWN["ACTION_DOWN"] --> Q{"控件类型"} Q -->|"Slider(BaseSlider 原生)"| QA{"mPressEnlargeEnable &&<br/>!startCustomAnim()? :2188"} QA -->|"Normal(delegate hasCustomAnim=false)"| AN1["TrackingAnimation<br/>整体 scaleY 1.0→1.2, 300ms<br/>BaseSlider:3224-3252"] QA -->|"GT/Ultra(hasCustomAnim=true)"| AN2["Slider.SliderAnimation<br/>trackOffsetY 0→2dp + thumb高×2, 225ms<br/>Slider.java:173-228"] Q -->|IconSlider| QB["startCustomAnim 被 override 恒 true :340-347<br/>**BaseSlider/Slider 动画全被抑制**<br/>走自研双组动画(04 篇)"] Q -->|IconSeekBar| QC["startPressAnim 恒 true :626-649<br/>**父类 SeekBarAnimation 被抑制**<br/>走自研双组动画(04 篇)"]