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() | :58 | Tab 对应的主信号 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=true。hasInitTab()(: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
四道关卡:
- Guard 1(
:110):featureIgnorePropertyValues命中则忽略(子类可 override 过滤特定值)。 shouldUpdateUI必须 true(:116)。- Guard 2 areaId 校验(
:118):仅对 Tab 信号(getTabPropId())校验,areaId 不符则丢弃——避免收到不属于当前区域的信号。 - 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)的核心:
- 可选车模联动(
:207-209)。 - 立即给点击的 Tab 打
isProcessing=true(:211-215)——提供即时 loading 视觉反馈,不等信号回包。 - 调子类
setCarProperty(tab)下发(:217)。 - 埋点上报(
:220-222)。
onDisabledTabClicked / onSelectedTabClicked / onTitleIconClicked(:225-235)默认空实现,留给子类覆盖。
七、钩子方法(open,子类可选覆盖)
| 钩子 | 行号 | 默认行为 | 子类用途 |
|---|---|---|---|
isTabDependencyAvailable(tab) | :189 | 返回 true | 检查依赖信号(如不可用原因) |
isTabProcessing(tab, val) | :194 | 返回 false | 判断过渡态 loading |
getObservedPropertyIdSet() | :199 | = getPropertyIdSet() | 扩展监听依赖信号 |
getPropValueByTab(tab) | :182 | tab.data as Int | 自定义 Tab→信号值转换 |
ignoreInvalidProperty() | :202 | false | 忽略无效信号值 |
obtainSetPropertyId() | :276-279 | getPropertyIdSet() 第一个 | 指定下行信号 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(待汇总填充)。