03 · 源码精讲
代码基线:
fuben23_micarsettings/MiCarSettings@rls-xcd-u-mp-26-v7.0(HEAD70f7b9afd= 取整修复,与cc27d08ba同一 Change-IdI4a34b69c3943fb0574e6fca89b181850db0e766e,内容 diff 为空;dev 双生3f3f7659a)。 所有 file:line 以 rls 分支为准;在 dev 工作树核对会有 1~6 行漂移 (dev 多了若干 MLog 行,逻辑逐字一致),以符号定位为准。 仓库:/home/zbc/car/fuben23_micarsettings/MiCarSettings。 ⚠️ controller 半数为 Kotlin,检索必须同时覆盖.java+.kt(纯 java grep 会漏掉 Custom/MainCustom 两个取整 controller,已踩过的坑)。
本篇逐段拆手势判定/取整/下发的全部相关代码。手势状态机与取整判定表的设计讲解在 02-手势语义与取整机制,本篇负责「代码到底长什么样、在哪一行」。
文件地图
| 文件 | 行数 | 角色 |
|---|---|---|
base/settingsBaseUi/src/main/java/com/android/car/settings/common/SliderPreference.java | 1118 | 手势判定 + 取整 + 下发的总控,全部滑块的父类 |
base/settingsBaseUi/src/main/java/com/android/car/settings/miauto/preferences/MiCarIconSeekBarPreference.java | 144 | 带图标/数值标签的滑块 Preference,取整 bug 的案发地 |
base/settingsBaseUi/src/main/java/com/android/car/settings/common/pageroute/IFusionSlider.kt | 97 | Settings 仓侧的滑块接口(组件库实现的适配口) |
base/settingsBaseUi/src/main/java/com/android/car/settings/miauto/common/views/MiCarIconSeekBar.kt | 102 | 组件库 IconSeekBar 的薄增强壳 |
settingsPage/micarDrivingSettings/src/main/java/com/android/car/settings/miauto/driving/BrakingStrengthPreferenceController.java | 250 | 驾驶偏好主页 controller(Java) |
settingsPage/micarDrivingSettings/src/main/java/com/android/micar/settings/driving/custom/CustomEnergyRecoveryStrengthController.kt | 200 | M1/M2 自定义页 controller(Kotlin) |
settingsPage/micarDrivingSettings/src/main/java/com/android/micar/settings/driving/custom/main/MainCustomEnergyRecoveryStrengthController.kt | 151 | 主驾自定义页 controller(Kotlin) |
settingsPage/micarDrivingSettings/src/main/java/com/android/micar/settings/driving/custom/BaseCustomVehicleProgressPrefController.java | 271 | Custom 的父类(注意是 .java) |
settingsPage/micarDrivingSettings/src/main/java/com/android/micar/settings/driving/custom/main/BaseMainCustomVehicleProgressPrefController.kt | 97 | MainCustom 的父类 |
base/settingsVehicleLib/src/main/java/com/android/car/settings/miauto/vehicle/BaseVehicleProgressPrefController.java | 258 | 值映射 + 信号下发/回显的中枢基类 |
页面拓扑(哪个 XML 挂哪个 controller、车型门控)见 01-功能与页面结构;GT(mach)只挂主页一个滑块。
1. SliderPreference:手势判定 + 取整 + 下发总控
1.1 常量与工作模式(:77-99)
public final static int SLIDER_NORMAL = 0; // 该模式滑动过程中一直下发信号
public final static int SLIDER_ADAPTER_VEHICLE = 1; // 该模式保证拖动进度条手指抬起时一定触发callbackChange
public static final int MAX_PROGRESS_CHANGE_TIMES = 1; // 判断是否静止时进度最大改变次数
public static final float DEFAULT_DESCRIPTION_STEP_VALUE = 1F; // 默认的Slider步长,如果设置为10,云端会进行四舍五入SLIDER_NORMAL(:77)= 拖动跟手逐值下发;SLIDER_ADAPTER_VEHICLE(:78)= 拖动中不下发、 抬手一次。基类默认是后者(见 §4BaseVehicleProgressPrefController.onCreateInternal:43), 三个能量回收 controller 都显式改回SLIDER_NORMAL(§3)。MAX_PROGRESS_CHANGE_TIMES = 1(:81)——「点击 vs 拖动」的判定门: 一次手势内整数值变化次数== 1视为「点击轨道」,松手时取整吸附;> 1视为拖动,不吸附。DEFAULT_DESCRIPTION_STEP_VALUE(:83)的注释是语音/云端侧的关键约定: 步长设为 10 时云端会做四舍五入——这就是为什么三个 controller 都设STEP_FOR_VOICE_ASSIST = 10(语音「调大一点」= ±10,与点击取整粒度一致)。- 取整粒度字段:
mIsRoundUpWhenClick(:141)默认 false、mRoundUpStep = 10(:143); 开关入口setValueRoundUp()(:494-496)、setValueRoundUpStep()(:502-509)。
1.2 手势三回调(mSliderChangeListener :166-236)
onStartTrackingTouch(:191-199)
mIsTrackingTouch = true;
mOriginProgress = fusionSlider.getValue(); // 记录起点,供松手判"无位移"onValueChange(:202-235)——三层过滤,修复主战场
// ① 顶部同值过滤:非取整模式下,整数值没变直接 return(防重置 mOriginProgress)
if (mSliderValue == getIntSliderValue(value) && !mIsRoundUpWhenClick) { ... return; } // :205-208
mOriginProgress = Float.MIN_VALUE; // :209
// ② 置灰保护:滑动中信号置灰后,fromUser 的回调不再受理(:210-220,含 9 行现象注释)
if (!isEnabled() && fromUser) { ... return; }
// ③ 取整分支(:221-231)
if (mIsRoundUpWhenClick) {
if (mProgressChangeTimes > 0) { // 首次点击不下发,继续滑动/抬手都会下发
onSliderProgressChanged(slider, getIntSliderValue(value), fromUser);
}
// 仅整数值真实变化才计入取整判定次数:
// 按下 thumb 瞬间控件会回调一次同值的 onValueChange,
// 若计入,「摸到 thumb 但未拖动」会被误判为点击而触发取整,
// 拖动设定的值被静默改写到整十倍数(N801PROB-132401)
if (mSliderValue != getIntSliderValue(value)) { // :229
mProgressChangeTimes++; // :230
}
} else {
onSliderProgressChanged(slider, getIntSliderValue(value), fromUser);
}修复前(:229 无整数值判断)mProgressChangeTimes++ 无差别计数,按下 thumb 的同值回调
也算 1 次 → 「摸 thumb 未拖」累计成 == 1 → 被当点击取整。修复的全部内容之一
就是把计数条件收窄到「整数值真实变化」(:229-231 及 :225-228 四行注释)。
另一处在 §1.3 的判定门(修复前为 <= 1,0 次也取整)。
②的 9 行注释(:211-219)解释了一个隐蔽时序:preference.setEnabled(false) 与
preference.setValue(x) 都走 notifyChanged(),在重新 onBindViewHolder 之前 slider
未置灰,仍会产生 fromUser 回调;若受理,显示值会变成手停留位置而非信号值。
onStopTrackingTouch(:169-188)——执行顺序是修复的另一半
mIsTrackingTouch = false;
alignAdjustProgress(fusionSlider); // :173 先取整吸附(内部:先回调刷新标签,后 setValue)
onSliderStopTrackingTouch(fusionSlider); // :174 再抬手兜底下发
mOriginProgress = Float.MIN_VALUE;
mProgressChangeTimes = 0; // :175-176 复位,供下一次手势
// :181-186 埋点(仅 mTrackParamKey 非空时);:187 更新可见即可说 description1.3 alignAdjustProgress(:331-346)——取整吸附本体
int originProgress = getIntSliderValue(slider);
// 仅发生一次真实位移(点击轨道)时才取整吸附;
// 0 次变化(触摸 thumb 但未产生位移)不取整——避免拖动设定后误触 thumb,
// 当前值被静默改写到整十倍数(N801PROB-132401)
if (mIsRoundUpWhenClick && mProgressChangeTimes == MAX_PROGRESS_CHANGE_TIMES) { // :336
originProgress = originProgress / mRoundUpStep * mRoundUpStep +
(originProgress % mRoundUpStep >= (mRoundUpStep / 2 + mRoundUpStep % 2) ?
mRoundUpStep : 0); // :337-339
originProgress = Math.max(originProgress, mSeekBarData.mMin); // :340
originProgress = Math.min(originProgress, mSeekBarData.mMax); // :341
// Slider.setValue()方法会判断是否与当前一致,如果一致,不会回调onValueChange()方法,
// 需要手动调用一次onSliderProgressChanged()刷新滑块刻度
onSliderProgressChanged(slider, getIntSliderValue(originProgress), true); // :343
}
slider.setValue(originProgress); // :345三个要点:
- 判定门 :336:
mProgressChangeTimes == MAX(1)。修复前为<= 1, 「摸 thumb 0 次变化」也命中 → 取整。配合 §1.2③ 的计数收窄,双击点/摸 thumb 都不再误吸附。 - half-up 公式 :337-339:
v/10*10 + (v%10 >= (10/2 + 10%2 = 5) ? 10 : 0)。 余数 ≥5 进位:67→70、45→50;余数 <5 舍去:44→40、33→30。mRoundUpStep%2的写法 兼容奇数粒度(如 step=5 时阈值=3)。 - 先回调、后 setValue :343→:345——数值标签不滞留的关键时序。
FusionSlider「同值 setValue 不回调 onValueChange」(:342 注释;FusionSlider 源码不在本仓,
此行为仅此注释背书,见 00-能量回收-README 纪律 2)。
所以取整后必须手动调一次
onSliderProgressChanged刷新刻度与标签; 且这次回调发生在slider.setValue()之前,下游必须读传入的progress而不是mSlider当前值——这就是 §2 MiCarIconSeekBarPreference 修复点存在的原因。
1.4 onSliderProgressChanged(:348-358)——跟手下发的入口
if (mShowMinValue > 0 && progress < mShowMinValue) { // :349-352 下限钳位
slider.setValue(mShowMinValue); return;
}
if (fromUser && (mIsContinuousUpdate || !mIsTrackingTouch)) {
// 车控信号slider来回拖动的操作不下发信号, 避免信号上报后UI回弹
syncValueInternal(slider, false); // :354-357
}mIsContinuousUpdate 由基类默认开启(§4 :36),所以 SLIDER_NORMAL 下每次拖动
onValueChange → 这里 → syncValueInternal(slider, false) 逐值下发。
1.5 onSliderStopTrackingTouch(:368-382)——抬手兜底
if (!isEnabled()) { return; } // :369-372 滑动中被置灰,不下发
if (mOriginProgress == slider.getValue()) { return; } // :374-377 只点没滑,不下发
// 抬手时强制下发信号:1、处理抬手下发
// 2、处理跟手下发时,最后一次信号回弹时未松手,导致回弹失败
syncValueInternal(slider, true); // :381 force=true1.6 syncValueInternal(:979-997)——下发的最后一米
if (isStepValidate(mStepValue)) { // :981-984 有级调节:对齐到 mStepValue 倍数
sliderValue = getIntSliderValue(slider) / mStepValue * mStepValue;
} else {
sliderValue = getIntSliderValue(slider); // :985 无级:四舍五入取整
}
// 车控使用场景需要保证必须执行一次,可通过传入forceRequestCallChange来实现
if (sliderValue != mSliderValue || forceRequestCallChange) { // :989
if (callChangeListener(sliderValue)) { // :990 → Preference 链 → controller 下发
setValueInternal(sliderValue, false); // :991 受理后写内部值
} else {
slider.setValue(mSliderValue); // :993 拒收则 UI 回弹到旧值
}
}callChangeListener 一路走到 BaseVehicleProgressPrefController.handlePreferenceChanged
(§4),那里才决定是否真的写车控属性。
1.7 值写回 UI 的拖动保护(setValue :903-913 / setValueInternal :915-928)
public void setValue(int sliderProgress, boolean forceUpdate) {
// 滑动过程中信号上报不允许更新UI位置,否则会出现跳闪
if (!isTrackingTouch() || forceUpdate) {
setValueInternal(MathUtils.clamp(sliderProgress, getMin(), getMax()), true);
} else { /* ignored (tracking touch) */ }
}信号回显(§4 updateProgress → setValue)在拖动中默认被吞——手还在滑,UI 不跟信号跳;
forceUpdate=true 只在「信号不可用需强制落值」时使用(:200 调用处)。
setValueInternal 里 persistInt(sliderProgress) 做 SharedPreferences 持久化。
1.8 置灰遮罩(:529 / :545-574)
mAllowTouchWhenDisable = true(默认)且 isEnabled() == false 时,
onBindViewHolder 在 slider 父容器上加一个同尺寸遮罩 View(tag=mask_view,
:529 调用、:545-574 实现)。遮罩接管点击并路由到 mClickListenerInDisable——
三个 controller 都注册它弹「不可用原因」toast(§3)。这就是「置灰仍可点、点了弹提示」
而非静默吞掉的机制。
1.9 语音/可见即可说(mSliderVoiceAssistListener :238-321)
OPERATION_SET_PROGRESS(:247-265):云端给定 progress →
getSliderValueFromProgress 映射 + mDescriptionOffset → 合法性检查
(:256-260,同值/越界直接 false)→ setValueInternal(targetValue, true) +
callChangeListener(targetValue) 下发。调大/调小(:280-291/:267-278)按
getStepValue() 步进;调到最大/最小(:293-316)走 getValueTo()/getValueFrom()。
语音路径不经过取整分支(mIsRoundUpWhenClick 只在触摸回调链里),
云端侧的四舍五入约定靠 :83 注释里的步长语义保证。
2. MiCarIconSeekBarPreference:144 行的数值标签层(全量拆)
类声明 :42 extends SliderPreference,布局 micar_ui_pref_icon_seek_bar(:69-72)。
2.1 bindSlider(:85-98)
先走父类绑定,再校验 mSlider instanceof IFusionSlider(:91-94,失败打 error 并 return false),
然后 applySliderData(:100-110)把左右端文案与初始 thumbText 刷上去。
校验存在的意义:布局里的 view 必须是 IFusionSlider 一族,否则后面所有接口调用都会 ClassCastException。
2.2 onSliderProgressChanged 覆写(:112-120)——132401 修复点
@Override
protected void onSliderProgressChanged(IFusionSlider slider, int progress, boolean fromUser) {
super.onSliderProgressChanged(slider, progress, fromUser);
// 必须使用传入的 progress 更新数值标签:
// 取整场景(SliderPreference#alignAdjustProgress)在 slider.setValue() 之前回调本方法,
// 若改读 mSlider 当前值会拿到取整前的旧值,导致数值标签滞留旧值,
// 与车辆实际属性值不一致(N801PROB-132401)
updateThumbText(progress); // :119
}修复前这里调的是无参 updateThumbText()(:122-124),它读 getIntSliderValue(mSlider)
——而 §1.3 的回调发生在 setValue() 之前,mSlider 内部值还是取整前的旧值,
于是标签画了旧值、系统值已是新值,分裂到下一次触摸(05 篇半面 B)。
修复 = 改用传入的 progress(:119)+ 四行注释(:115-118)。
2.3 两个 updateThumbText 重载(:122-128)与 getThumbText(:134-139)
private void updateThumbText() { updateThumbText(getIntSliderValue(mSlider)); } // :122-124 保留给"读当前值"场景
public void updateThumbText(int progress) {
((IFusionSlider) mSlider).setThumbText(getThumbText(progress)); // :126-128
}
private String getThumbText(int progress) {
if (mThumbTextGetter != null) return mThumbTextGetter.getThumbText(progress);
return String.valueOf(progress); // :138 默认就是数字本身
}- 默认标签 = 进度值的十进制字符串(:138);要显示「45%」「低/中/高」等文案,
用
setThumbTextGetter(:130-132)注入ThumbTextGetter接口(:141-143)。 - 能量回收三个 controller 都没注入 getter,标签即数值(与 bugreport 里「显示 67 实际 70」 的读数口径一致,所以标签值可以直接和信号值对账)。
3. 三个取整 controller:同配置三兄弟
三者的取整配置完全相同(setValueRoundUp(true) + SLIDER_NORMAL + 语音步长 10),
差异在挂载页面、propId 和最小值。共同父类见 §3.4。
3.1 BrakingStrengthPreferenceController(主页,Java,250 行)
- @PropertyRange(lower=0, upper=100, defaultVal=100) :46-50;
常量区 :53-57(MIN=0、MIN_HYBRID=1、MAX=100、STEP_FOR_VOICE_ASSIST=10)。
混动车型通过动态代理把 lower 换成 1(:86-104
createModifiedConfigRange, 判据CONFIGURATION_FOR_HMI_DISPLAY_HYBRID:93-94)。 - onCreateInternal :106-156:
- :111
setStepValueForVoiceAssist(10);:112setValueRoundUp(true); :113setWorkMode(SLIDER_NORMAL)(注释原话「滑动过程中下发信号」) - :114-135 注册
OnFusionSliderChangeListener仅用于抬手埋点 (TIP_BRAKING_STRENGTH_SLIDER_TOUCH+ 当前值) - :137-142 读
BRAKING_MODE缓存定初始可见性(== Energy.RegenerationMode.ADVANCED); :144-150 再注册 ViewModel observer 持续跟随——双保险 - :152-153
setAllowTouchWhenDisable(true)+ 置灰点击弹showDisableToast() - :154-156
setValue初值 =ENERGY_REGENERATION_LEVEL当前属性值
- :111
- onPropertyManagerPrepared :199-206:属性服务就绪后再刷一次可见性
(
BRAKING_MODE == ADVANCED,与 :137-142/:144-150 同判据)。 - getPropertyIdSet :206-208 =
ENERGY_REGENERATION_LEVEL(下发目标,唯一); getObservedPropertyIdSet :211-216 = 下发集 +ENERGY_REGENERATION_BAN_REASON(多观测一个禁用原因信号)。 - onHandlePropertyChange :224-232:值变了先推
LiveData<CarPropertyValue>(喂雷达图, 见 04-信号链路与下游消费 §5.1)再走父类回显; onIgnorePropertyFeedback :235-242:回显被屏蔽时(自身 SET 的反馈)同样推雷达—— 雷达图两条刷新路径,保证自设值与外部值都能上图。 - isDependencyAvailable :245-249 =
EnergyRecoveryModeHelper.isEnergyRecoveryAvailable( BAN_REASON)(BAN_REASON==NO_INHIBIT 且信号状态可用才 true,见 04 §6.2)。 - showDisableToast :158-191:按 BAN_REASON 枚举分别弹文案—— 战斗/湿滑/ADAS/除锈(RSTREMV)/陡坡缓降(HDC)/防晕车/拖挂,共 7 个 case + default 不弹。
3.2 CustomEnergyRecoveryStrengthController(M1/M2 自定义页,Kotlin,200 行)
- @PropertyRange :32-36,数值同主页(lower=0/upper=100/defaultVal=100,常量 :46-49, 混动 lower=1 的代理 :160-192 同构)。
- 取整配置 :57-59(:57 语音步长 10、:58
setValueRoundUp(true)、:59SLIDER_NORMAL)。 - propId 不是 ENERGY_REGENERATION_LEVEL,而是按当前自定义模式动态选:
getPropertyIdsForM1 :88-92=Driving.REGENERATION_PARAMETER_MODE1_STATUS,getPropertyIdsForM2 :94-98=..._MODE2_STATUS; 父类getPropertyIdSet :178-185按mSteeringModeViewModel.isM1Mode()/isM2Mode()分发, 非 M1/M2 返回空集(不下发)。 - 可见性门控与主页相同(缓存 :61-67 + observer :69-77 + prepared :154-158,
全部
BRAKING_MODE == ADVANCED);观测集 :100-105 同样追加 BAN_REASON。 getInitProgress :142-144从SteeringModeViewModel读当前模式的回收强度做初值;updateProgressSteeringModeData :146-148把进度回写模式数据(账号同步用)。- 置灰 toast 走公共实现
EnergyRecoveryModeHelper.showDisabledReasonToast(:194-199)。
3.3 MainCustomEnergyRecoveryStrengthController(主驾自定义页,Kotlin,151 行)
- ⚠️ @PropertyRange lower=1(:26,常量 :41
BRAKING_STRENGTH_MIN_VALUE = 1), 不是 0——主驾自定义页的可调区间是 1~100(upper=100/defaultVal=100 与兄弟一致)。 与 README「关键数字一页纸」的「主自定义页 1~100」口径吻合。 - 取整配置 :60-61(:60
setValueRoundUp(true)、:61SLIDER_NORMAL);语音步长 :43/:59。 - 可见性用
getAvailabilityStatus :48-55(CONDITIONALLY_UNAVAILABLE)+ observer :63-74- prepared :139-143,判据同为
BRAKING_MODE == ADVANCED。
- prepared :139-143,判据同为
- propId 按驾驶模式四选一:
getPropertyIdForEco/Comfort/Sport/SportPlus :85-95=REGENERATION_PARAMETER_{ECONOMIC,COMFORT,SPORT,SPORT_PLUS}_MODE_STATUS。 - 观测集 :97-102 追加 BAN_REASON;toast 同走 EnergyRecoveryModeHelper(:145-150)。
3.4 两个自定义父类
BaseCustomVehicleProgressPrefController.java(Custom 的父类,271 行):
- :29
extends BaseVehicleProgressPrefController<MiCarIconSeekBarPreference>—— Preference 类型在此锁定为 MiCarIconSeekBarPreference,所以修复(:119)天然覆盖 Custom。 - :46
setWorkMode(SLIDER_NORMAL)(父类统一再设一次,与子类重复但无害)。 updateVehicleProperty :134-142:仅 M1/M2 模式才setVehicleProperty(propId 来自 :178-185 的动态集),无论下不下发都把值缓存进 SteeringModeViewModel(:140)。onIgnorePropertyFeedback :144-148/onHandlePropertyChange :150-154→notifyRadarViewRefresh :159-164:M1/M2 propId 变化时推 LiveData 刷雷达(同 §3.1 双路径)。
BaseMainCustomVehicleProgressPrefController.kt(MainCustom 的父类,97 行):
- :21-26 同样
extends BaseVehicleProgressPrefController<MiCarIconSeekBarPreference>; :34SLIDER_NORMAL。 - :45-53
initProgressFromCache:onCreate 时用VehiclePermanentListener缓存值先画 UI。 - :55-61
getPropertyIdSet走DriveModePropertyProvider.getDriveModePropertyIdSet()(四模式分发,空集打 error);:63-69updateVehicleProperty空集直接 false。 - :84-91
notifyRadarViewRefresh:四模式 propId 命中才推雷达。
4. BaseVehicleProgressPrefController:值映射与下发/回显中枢(258 行)
4.1 恒等映射(:142-158)
protected int propValToProgress(int propertyValToIndex) { // :142-151
... return (propertyValToIndex + mCfgPropertyRange.offset());
}
protected int progressToPropVal(int progress) { // :153-158
... return progress - mCfgPropertyRange.offset();
}offset() 注解默认 0(base/settingsVehicleLib/.../annotation/PropertyRange.java:36),
三个 controller 都没覆写 offset ⇒ 属性值 ≡ 滑块进度,0~100 恒等。
排查「100-x 反转」类猜想时这就是证伪依据(05 篇 §3.4 用它驳了 JarviX 的反转假设)。
滑块区间在 updateSliderRange :67-100 由 Range(lower+offset, upper+offset)(:79)设定,
init && defaultVal 合法 时 :93-95 用默认值先画。
4.2 默认工作模式的反转(:33-44)
getPreference().setContinuousUpdate(true); // :36
...
// 在slider拖动过程中不会直接下发车控信号
getPreference().setWorkMode(SliderPreference.SLIDER_ADAPTER_VEHICLE); // :43基类默认 = 抬手才下发(:43),三个能量回收 controller 各自在 onCreateInternal 里
改回 SLIDER_NORMAL(§3)。改任何 slider 需求时先确认这个默认值,别把兄弟控件的行为带歪。
4.3 下发闸门(handlePreferenceChanged :217-231 / updateVehicleProperty :233-238)
// E3 slider车控逻辑调整为拖拽过程中不下发信号,手指抬起时发送一次, 避免拖动过程中信号上报UI回弹
if (getPreference().getWorkMode() == SliderPreference.SLIDER_NORMAL
|| !getPreference().isTrackingTouch()) { // :223-224
if (updateVehicleProperty((Integer) progress)) { return true; }
}SLIDER_NORMAL ⇒ 任何时刻都进 updateVehicleProperty;
SLIDER_ADAPTER_VEHICLE ⇒ 只有非拖动中(抬手那一次)才进。
updateVehicleProperty :233-238:progressToPropVal 换算后
setVehicleProperty(propId, propVal, areaId, true)(:237,同步下发,
保证顺序;同步下发的端到端时延实证见 04 §3)。取 getPropertyIdSet().iterator().next()
第一个 propId——三个 controller 的集合都是单元素,安全。
4.4 回显路径(onHandlePropertyChange :160-168 / updateProgress :179-201)
外部(非自身 SET)的属性变化:
- :183
setEnabled(isPropAvailable)——isPropertyAvailable && isDependencyAvailable && isInnerLogicAvailable三者与;BAN_REASON 置灰就从这里生效(BAN_REASON 在观测集里, 变化会触发本路径,见 04 §6.2)。 - :187-191 非法值打 warning 丢弃;:192
propValToProgress换算; - :196-199 有级调节(
getStepSize()>0)时对齐到步长节点; - :200
setValue(seekProgress, !isPropAvailable)写 UI——拖动中被 §1.7 的保护吞掉。
5. 组件库分层:为什么「升级组件库修不好」
本功能的代码边界:
| 层 | 位置 | 内容 |
|---|---|---|
| 接口层(Settings 仓) | IFusionSlider.kt:10 | getValue/setValue/setThumbText/... 接口与 OnFusionSliderChangeListener(:72);setThumbText 默认空实现(:52) |
| 薄壳(Settings 仓) | MiCarIconSeekBar.kt:14 | class MiCarIconSeekBar : IconSeekBar, IFusionSlider,仅增强 touch 事件(:8 import micarx.androidbasewidget.IconSeekBar) |
| 总控(Settings 仓) | SliderPreference.java(本篇 §1) | 手势判定、取整、计数、下发、置灰遮罩、语音 |
| 渲染实现(micarx 组件库) | com.mi.car.material.slider.Slider(SliderPreference.java:59 import)、micarx.androidbasewidget.IconSeekBar | 绘制、触摸事件产生、thumb 文本渲染 |
依赖声明(rls 行号):base/settingsBaseUi/build.gradle:90
def micarx_lib_version = "3.2.6-RC11-SNAPSHOT",:106 api "micarx.support:androidbasewidget:$micarx_lib_version"。
结论:取整判定、标签刷新、信号下发全部在 Settings 仓 settingsBaseUi;
组件库只产出触摸事件流(onStartTracking/onValueChange/onStopTracking)和渲染。
132401 这类「取整误触发/标签滞留」bug,改组件库版本天然修不到——逻辑不在那一层
(「3.2.4 称已修 → 3.2.6 仍在」的完整复盘见
05-排障实战-N801PROB-132401 §5)。
当前基线组件库已是 3.2.6-RC11-SNAPSHOT,bug 修复靠的是本仓 70f7b9afd(即 cc27d08ba 内容)。
6. 同族控件:谁受影响、谁不受影响
| 控件 | 继承 | onSliderProgressChanged | 开取整? | 结论 |
|---|---|---|---|---|
| 能量回收三 controller 用的 MiCarIconSeekBarPreference | SliderPreference(:42) | :112-120 已修(读传入 progress) | 是(:112/:58/:60) | ✅ 修复覆盖 |
RoofSunShadeSliderPreference.kt(天窗遮阳帘,micarLockSettings/…/preferences/) | 直接 extends SliderPreference(:25) | :152-155 仍读 getSliderValue() 当前值(同款旧写法) | 否(micarLockSettings 内无 setValueRoundUp 调用点,grep 实证) | ⚠️ 潜在债:写法未修,但因未开取整,alignAdjustProgress 的提前回调路径不会触发,暂时安全;谁给它开 setValueRoundUp(true) 就会复现 132401 半面 B |
MiCarHeightLimitSeekBarPreference.kt(限高,settingsAutoPilot/…/preference/) | extends MiCarIconSeekBarPreference(:34) | 继承修复版 | — | ✅ 继承即修复 |
排查「还有没有同类隐患」的标准动作:
grep -rn "onSliderProgressChanged" --include="*.kt" --include="*.java" | grep getSliderValue\|getIntSliderValue(mSlider)
找出所有覆写后仍读控件当前值的 Preference,再核对其 controller 是否开了 setValueRoundUp。
7. 改代码 checklist
| 需求 | 改哪里 |
|---|---|
| 改取整粒度(如 10→5) | controller 加 setValueRoundUpStep(5)(:502-509);注意 :337-339 公式已兼容奇数粒度 |
| 关/开取整 | controller 的 setValueRoundUp(true/false)(三处:Braking :112、Custom :58、MainCustom :60) |
| 改语音步长 | controller 的 STEP_FOR_VOICE_ASSIST 常量(Braking :57、Custom :49、MainCustom :43) |
| 改可见性门控 | BRAKING_MODE == ADVANCED 三处判据(缓存读 + observer + onPropertyManagerPrepared),三个 controller 共 9 处,必须同步改 |
| 改置灰文案 | Braking showDisableToast :158-191 自己的文案表;Custom/MainCustom 在 EnergyRecoveryModeHelper.showDisabledReasonToast |
| 加新 ban 原因 | Driving$EnergyRegenerationBanReason 枚举(jar 侧)+ 两个 toast 实现 + isEnergyRecoveryAvailable 语义 |
| 改数值标签文案 | setThumbTextGetter(MiCarIconSeekBarPreference :130-132,接口 :141-143) |
| 改下发 propId | 主页改 getPropertyIdSet :206-208;Custom 改 :88-98;MainCustom 改 :85-95(注意 M1/M2/四模式分发父类) |
| 看日志 | adb shell setprop log.tag.SliderPreference D(rls 上关键路径多为 MLog.d;>>> Done set propVal 在 VehicleControlManager.java:121,I 级默认可见) |
⚠️ rls 分支
SliderPreference.java/MiCarIconSeekBarPreference.java/RoofSunShadeSliderPreference.kt的 import 是com.android.car.setings.carui.R(setings 少一个 t)——这不是笔误:rls 分支base/settingsBaseUi/src/main/AndroidManifest.xml:3声明的模块包名就是package="com.android.car.setings.carui",import 与之自洽,不要「顺手修正」。 dev 分支则相反:manifest 无 package 属性,base/settingsBaseUi/build.gradle:8namespace 'com.android.car.settings.carui'(拼写正确),两分支各自自洽, cherry-pick 跨分支改动时注意别把两边的 R 引用混了。
下一篇
04-信号链路与下游消费:值离开 App 之后去哪了—— propId 三件套、CARSIGNAL/pps 逐段实证、回显屏蔽、雷达图与 MIoT 上报、三视角对账排查法。