BaseVehicleImageTabPreferenceController 基类模板

源码:base/settingsVehicleLib/src/main/java/com/android/car/settings/miauto/vehicle/BaseVehicleImageTabPreferenceController.kt(280 行)

一、定位:模板方法模式

这是「带图标 Tab + 车辆信号驱动」设置项的通用骨架基类。它把所有这类设置项的共性流程(Tab 初始化、信号注册、点击埋点、语音可见即可说、车模联动)固化成模板,子类只需填空 4 个抽象方法即可获得完整能力。

classDiagram
    class CarPropertyMgrPreferenceController {
        <<信号管理基类>>
        +getProperty(id)
        +setVehicleProperty()
    }
    class BaseVehicleImageTabPreferenceController {
        <<abstract>>
        +buildTabData()*
        +propValToTabIndex(val)*
        +setCarProperty(tab)*
        +getTabPropId()*
        #onHandlePropertyChange()
        #updateTabByPropertyChange()
        #notifyUIByPropertyChange()
        -initUIData()
    }
    class ImageTabPreference {
        <<UI>>
        +setData(tabs)
        +notifyDataChanged()
    }
    CarPropertyMgrPreferenceController <|-- BaseVehicleImageTabPreferenceController
    BaseVehicleImageTabPreferenceController ..> ImageTabPreference : 持有/驱动

它继承 CarPropertyMgrPreferenceController(信号管理能力),并实现三个接口(:37):

  • NewTabPreference.ITabCallback —— Tab 点击回调
  • BasicPreferenceOnClickListener —— Preference 点击监听
  • IVehiclePerformFeature —— 车辆操作特性

二、四个抽象方法(子类必须实现)

抽象方法行号职责
buildTabData():49创建 Tab 数据数组
propValToTabIndex(propertyVal):52信号值 → Tab 索引映射
setCarProperty(tab):55点击 Tab 时下发信号
getTabPropId():58Tab 对应的主信号 ID

三、生命周期

sequenceDiagram
    participant L as 生命周期
    participant B as Base
    participant P as Preference
    L->>B: onCreateInternal()
    B->>P: setTabCallback(this) / setOnTitleIconClickListener(this)
    L->>B: onPostCreateInternal()
    B->>P: setPreferenceViewBindedListener(onViewBindedFinish→registerPropertyCallback)
    L->>B: onHandlePropertyManagerPrepared()
    B->>B: initUIData()  -- 构建 Tab 并渲染
    L->>B: onDestroyInternal()
    B->>P: setTabCallback(null)
  • onCreateInternal()(:62-66):把自身注册成 Tab 回调和标题图标点击监听。
  • onPostCreateInternal()(:68-75):在 View 绑定完成后才注册信号回调(registerPropertyCallback)——时机关键,确保 UI 就绪后再收信号。
  • onHandlePropertyManagerPrepared()(:82-85):信号管理器就绪后调用 initUIData() 构建 Tab。
  • onDestroyInternal()(:77-80):清理解除回调。

四、Tab 初始化:initUIData(:89-98)

protected open fun initUIData() {
    mTabs = buildTabData().also {
        it.forEach { tab -> mSupportedValues.add(tab.data) }
    }
    preference.setData(mTabs)
    preference.notifyDataChanged()
    mTabHasInit = true   // 标记初始化完成
}

调用子类 buildTabData() 拿到 Tab 数组 → 把每个 Tab 的 data 加入 mSupportedValues(用于后续校验)→ 喂给 Preference 渲染 → 标记 mTabHasInit=truehasInitTab()(:100)供子类判断是否已初始化。

五、信号处理核心链路 ⭐

这是基类最核心的机制,三个方法逐层调用:

flowchart TD
    A["onHandlePropertyChange(propVal, shouldUpdateUI)<br/>:104 入口"] --> B{Guard 检查}
    B -->|featureIgnorePropertyValues 命中| X[return 忽略]
    B -->|shouldUpdateUI=false| X
    B -->|通过| C{areaId 校验<br/>仅 Tab 信号}
    C -->|areaId 不匹配| X
    C -->|通过| D["计算 isPropAvailable<br/>= 信号available && isInnerLogicAvailable"]
    D --> E["notifyUIByPropertyChange(propId, val, isAvailable)<br/>:135"]
    E -->_a1{"propId == getTabPropId?"}
    E -->|是| F["updateTabByPropertyChange(val, isAvailable)<br/>:151"]
    E -->|否| G["notifyChanged=false<br/>子类需自行处理依赖信号"]
    F --> H["遍历 mTabs 更新<br/>isAvailable/mIsSelected/isProcessing"]
    H --> I["preference.isEnabled = isTabAvailable"]

5.1 onHandlePropertyChange(:104-133)—— 入口 + Guard

四道关卡:

  1. Guard 1(:110):featureIgnorePropertyValues 命中则忽略(子类可 override 过滤特定值)。
  2. shouldUpdateUI 必须 true(:116)。
  3. Guard 2 areaId 校验(:118):仅对 Tab 信号(getTabPropId())校验,areaId 不符则丢弃——避免收到不属于当前区域的信号。
  4. Guard 4+5(:125-128):综合信号可用性(isPropertyAvailable)和内部逻辑(isInnerLogicAvailable)。

5.2 notifyUIByPropertyChange(:135-149)—— 分发

when(propId):只有 getTabPropId()updateTabByPropertyChange;其他信号(依赖信号)默认 notifyChanged=false,不刷新 UI——这正是为什么 RearProjectionModeTabController 要在 super 之前自己处理依赖信号(01-RearProjectionModeTabController.md §4.8)。

5.3 updateTabByPropertyChange(:151-177)—— 刷新 Tab 三态

flowchart LR
    P["propVal"] --> Q["propValToTabIndex(val)"]
    Q --> R["遍历每个 Tab 计算三态"]
    R --> S1["isAvailable = isPropAvailable && isTabDependencyAvailable(tab)"]
    R --> S2["mIsSelected = (nextIdx == index)"]
    R --> S3["isProcessing = isSelected && isTabProcessing(tab, val)"]
    S1 & S2 & S3 --> T["有变化则 hasChange=true"]
    T --> U["preference.isEnabled = isTabAvailable(至少一个可用)"]

Tab 三态(驱动 UI):

  • isAvailable:是否可用(综合主信号 + 依赖信号)
  • mIsSelected:是否选中(由状态信号决定)
  • isProcessing:是否 loading 中(过渡态)

未初始化(!mTabHasInit)或索引越界时直接返回 false(:152-160)。

六、Tab 点击回调(ITabCallback)

sequenceDiagram
    participant U as 用户点击Tab
    participant B as Base.onSelectTabChanged
    participant T as Tab数组
    participant Sub as 子类.setCarProperty
    U->>B: onSelectTabChanged(tab)
    B->>B: shouldChangeCarModelWhenClick? → triggerCarModelChange
    B->>T: 重算: 点击的tab.mIsSelected=true, isProcessing=true(即时loading)
    B->>Sub: setCarProperty(tab) -- 下发命令
    B->>B: TrackEvent 埋点上报(若 needReportClickEvent)

onSelectTabChanged()(:206-223)的核心:

  1. 可选车模联动(:207-209)。
  2. 立即给点击的 Tab 打 isProcessing=true(:211-215)——提供即时 loading 视觉反馈,不等信号回包。
  3. 调子类 setCarProperty(tab) 下发(:217)。
  4. 埋点上报(:220-222)。

onDisabledTabClicked / onSelectedTabClicked / onTitleIconClicked(:225-235)默认空实现,留给子类覆盖。

七、钩子方法(open,子类可选覆盖)

钩子行号默认行为子类用途
isTabDependencyAvailable(tab):189返回 true检查依赖信号(如不可用原因)
isTabProcessing(tab, val):194返回 false判断过渡态 loading
getObservedPropertyIdSet():199= getPropertyIdSet()扩展监听依赖信号
getPropValueByTab(tab):182tab.data as Int自定义 Tab→信号值转换
ignoreInvalidProperty():202false忽略无效信号值
obtainSetPropertyId():276-279getPropertyIdSet() 第一个指定下行信号 ID

八、语音可见即可说(onBuildMiCarViewDescription, :253-271)

为语音助手构建可说的描述:title + 热词(assistViewDesc / summary),标记 enable/selected/checked。受 shouldConfigViewDescription() 开关控制。

九、总结

基类用模板方法 + 大量钩子把车辆信号驱动的 Tab 设置项抽象到位:

  • 子类填 4 个抽象方法即获完整能力;
  • 信号处理三段式(onHandlePropertyChange → notifyUIByPropertyChange → updateTabByPropertyChange)层层把关;
  • Tab 三态(isAvailable / mIsSelected / isProcessing)是整个 UI 的状态核心;
  • 对依赖信号默认不处理,留给子类按需扩展。

信号在更底层的注册/收发机制(CarPropertyMgrPreferenceController / SettingsCarPropertyManager)见 04-信号管理层.md(待汇总填充)。