04 · IconSeekBar 与 IconSlider 精读

两个组件是 androidbasewidget 模块的车机定制壳:在基础滑杆上加 thumb 文字/图标、 textStart/textEnd 两端文案、hint 位置、Folme 弹簧按压动画。二者代码高度对称但 不同源(01 篇)。

1. IconSeekBar(int progress 生态,1131 行)

构造要点(ABW/IconSeekBar.java:115-210)

  • 三个构造强制包 ContextThemeWrapper(Widget_MiCar_IconSeekBar_DayNight)(:115-121), XML 传 theme 也会被覆盖。
  • GT/Ultra 下 delegate 改写:mTextStart/mTextEnd 被强制替换为 "0"/"100" (:176-179,常量 MIN/MAX_VALUE_GT 于 :40-41)。
  • 构造末尾 clearThumbDrawable()(:208)——原生 thumb 不画,只画自绘 bg+icon+text。
  • checkValue():max==min 抛 IAE(:212-216;setMax/setMin 触发 :256-265)。 IconSeekBarTest 的边界用例钉的就是它。

绘制管线

flowchart TD
    OD["onDraw :347-370"] --> S1["canvas 按 mScale Y 缩放 :360"]
    S1 --> S2{"GT/Ultra?"}
    S2 -->|是| S3["delegate.setupOnDrawClipPath<br/>轨道裁成 thumb 两侧两段 :361-367<br/>GtSeekBarThemeDelegate:82-106"]
    S2 -->|否| S4["整轨(Normal)"]
    S3 --> S5["super.onDraw(框架画轨道)"]
    S4 --> S5
    S5 --> S6["drawIconAndText :377-554"]
    S6 --> S7["thumb 中心 X = availableWidth×(p−min)/(max−min)<br/>clamp [thumbPadding, W−thumbPadding] :381-383"]
    S7 --> S8{"内容形态"}
    S8 -->|仅文字| S9[":488-494"]
    S8 -->|仅图标| S10[":498-504"]
    S8 -->|图标+文字| S11["按 mIconGravity 四方位排布 :507-553"]
  • hint 文案:hintTextBelow 时画滑块下方(:396-405);否则 GT/Ultra 由 delegate 画在轨道 上方且按压时按 thumb 左右边界 clip(GtSeekBarThemeDelegate:120-149)。
  • clipPathIfNeeded() 返回 false(:372-375),关掉父类 micarx AppCompatSeekBar 的整轨 clip (AppCompatSeekBar:129-148),改由自己的 delegate clip 接管。
  • 高度:GT/Ultra 经 adjustMeasuredHeight 加 hint 高度(:226-232)。

按压动画(两组,弹簧插值器枚举 SpringInterpolatorType :1106-1130)

组触发(delegate 分流)内容回弹
Normal 组 startBgPressAnimation(:652-772)shouldStartPressAnim()=false → 走 PressAnimHelper 路径,带 thumb 命中检测(:637-646)文字上移、thumb 高×1.5、宽×0.65、圆角×1.1、轨道 scaleY×1.05startUpBgPressAnimation :792-912
GT/Ultra 组 startBgExpandAnimation(:915-1007)delegate 直接受理左右扩张 offset + 文字上移/放大 + 轨道增高(mTrackOffsetYScale→mTrackExpandHeightScale/1000,千分位整数资源)startUpBgExpandAnimation :1010-1100

2. IconSlider(float value 生态,1144 行)

构造与动画抑制

  • delegate 创建于 :134;GT/Ultra 强制 thumb 背景换 GT 资源、icon 置 null、textStart/End 改写(GtIconSliderThemeDelegate:23-46)。
  • clearCustomThumbDrawable()(:191)清掉 BaseSlider 的 thumb。
  • startCustomAnim()/reverseCustomAnim() override 恒 true(:340-347)——一举抑制掉 BaseSlider 的 mTrackingAnim 和 material Slider 的 mSliderAnim(03 篇 §5),IconSlider 完全用自研动画。

几何与绘制钩子

  • onLayout 里 setThumbPadding(mThumbWidth/2)(:289)——把 BaseSlider 触摸映射内缩半个 thumb 宽,经 getEffectiveThumbPadding 进入 generateTouchPosition 公式(02 篇 §2)。
  • 轨道 path hook:setTrackPath→delegate applyTrackPath(:350-356,GT 自绘圆角矩形, 中心=(h+hintH)/2);
  • 激活/非激活轨道:drawActiveTrack/childDrawInactiveTrack 交 delegate 自绘 (:359-381,GT 画线 GtIconSliderThemeDelegate:65-106,clamp 越界);
  • 轨道中心:calculateTrackCenter→getSliderCenterY(:328-337,GT/Ultra 下移 (height+hintTextHeight)/2)——GT 主题”轨道中心不在 view 中心”由此而来。

按压偏移的边缘特判(GT/Ultra)

applyPressOffsets(GtIconSliderThemeDelegate:158-167):
  value ≈ MAX_VALUE_GT(100) → 只向左扩 [edge, 0]
  value ≈ MIN_VALUE_GT(0)   → 只向右扩 [0, edge]
  中间                       → 双侧扩 [expand, expand]

⚠️ 判定用常量 MAX_VALUE_GT=100/MIN_VALUE_GT=0(IconSlider.java:39-41)而非真实 valueTo/valueFrom,比较阈值 DISTANCE_THRESHOLD=1E-5。量程不是 0~100 的 slider,边缘 特判永远失效,恒走中间档——接新量程时注意。

与 IconSeekBar 的差异对照

维度IconSeekBarIconSlider
值模型int progress(ProgressBar)float value(BaseSlider)
回调框架 OnSeekBarChangeListenerSlider.OnChangeListener/OnSliderTouchListener
点击抑制delegate shouldTransformTouchEvent → AppCompatSeekBar transformdelegate shouldIgnoreClick → setIgnoreClick(true)(:570-572)
轨道增厚量mTrackOffsetYScale(比例,继承 micarx AppCompatSeekBar :48)trackOffsetY(像素,继承 BaseSlider :256)
tooltip label无继承 BaseSlider TooltipDrawable(默认样式 gone)
按压偏移判定int progress 等值比较float + 1E-5 阈值

3. VerticalSlider 补遗(垂直自绘版,809 行)

  • extends View 独立实现,拷贝 BaseSlider 骨架(values/stepSize/snapTouchPosition/ transformTouchEvent 同名机制)。
  • 轨道 clip 中挖掉所有 thumb 区域:setClipForAllThumbs(:234-246,Path.Op.DIFFERENCE); inactive 两段 + active 一段 drawLine(:274-287)。
  • thumb 按压横向拉宽动画:Folme useValue + ValueProperty,width→2×width(:397-467), 仅 GT/Ultra 且 drawablePersistence=false。
  • 宽度由 track 宽+水平 padding 反推,onMeasure 强制 EXACTLY(:184-209)。
  • setValues 会排序后存储(:545/:568)——多 thumb 传入乱序不会崩,但下标语义变。

4. 公开 API 速查

IconSlider(继承链全量): setThumbText/getThumbText(:276-283)、setTextStart/End(:200-222)、 setHintTextPosition(:307-320,非法值抛 IAE)、setPressAnimEnable(:1086-1088)、 继承 setValue/getValue(Slider.java:119/:99)、setValueFrom/To(BaseSlider:825/:851)、 setStepSize(:988)、addOnChangeListener/addOnSliderTouchListener(:1121/:1144)、 setIgnoreClick/isIgnoreClick(:666-672)、setScrollDragEnable(:3199)、 setPressEnlarge(:3203,GT/Ultra 被 delegate 拦截)。

IconSeekBar:同名 text/hint/pressAnim API(:307-338/:235-248/:1102-1104)+ 框架 setOnSeekBarChangeListener/setProgress/setMax/setMin(带 max≠min 校验)。

fromUser 语义:触摸(含 DOWN snap、每个 MOVE、UP 终 snap)= true;程序 set = false。 IconSeekBar 遵循框架 SeekBar 语义。