21、HMI 框架与工具:Car UI Library、布局适配、主题定制

各位好,今天我们来聊聊 Android Automotive 里最“看得见摸得着”的部分——HMI。说白了,就是用户跟车机交互的那层皮。我做了好几年的车载项目,发现很多开发者把精力都花在底层逻辑上,结果 UI 一跑起来,要么字体小得看不清,要么在横竖屏切换时布局乱成一锅粥。嗯,今天我们就来把这些坑填上。

Car UI Library:官方给的“脚手架”

Google 其实替我们想了不少。Car UI Library 是一套专门为车载场景设计的 UI 组件库。它跟普通的 Jetpack Compose 或传统 View 系统不一样,它内置了驾驶安全相关的交互限制。比如,某些操作在车辆行驶时会被禁用。

我个人习惯把 Car UI Library 看作一个“半成品”。它提供了基础控件,但你要自己搭积木。举个例子,它的 CarTextButton 已经帮你处理好了触摸区域的最小尺寸——48dp,这是为了驾驶时盲操作。我在项目中遇到过,有同事直接用普通 Button,结果用户开车时老点错,后来全换成了 Car UI Library 的组件。

核心组件一览:
  • CarTextButton:带最小触摸区域的文本按钮
  • CarIconButton:图标按钮,常用于快捷操作
  • CarListItem:列表项,支持多行文本和图标
  • CarToolbar:顶部工具栏,带返回和标题
  • CarScrollBar:滚动条,适配车载触摸屏

你可能会问:“这些组件跟普通 Material Design 组件有啥区别?”区别大了。Car UI Library 的组件默认会监听驾驶状态。比如,当车速超过某个阈值,按钮的点击事件会被拦截。这个逻辑是写死在库里的,你不需要自己写 if-else。

布局适配:不只是“自适应”那么简单

车载屏幕的尺寸千奇百怪。有 7 寸的,有 12.3 寸的,还有那种贯穿整个中控的“带鱼屏”。布局适配,说白了就是让你的 UI 在这些屏幕上都能正常显示。

我建议你抛弃传统的 dp 思维。车载屏幕的物理尺寸差异太大,用 dp 做绝对定位会死得很惨。正确的做法是使用 ConstraintLayout 配合 百分比约束。比如,一个导航地图应该占屏幕宽度的 70%,而不是固定 600dp。

还有一个容易被忽略的点:安全区域。车载屏幕通常有圆角、刘海(摄像头区域)或者物理按键遮挡。Android Automotive 提供了 WindowInsets 来处理这些。我曾经有个项目,UI 在模拟器上完美,一上真机,左侧的返回按钮被物理按键挡住了。后来加了 setOnApplyWindowInsetsListener 才解决。

避坑指南: 我曾经在适配竖屏中控屏时,发现 Car UI Library 的列表在竖屏下显示不全。后来发现是 CarScrollBar 的默认高度写死了。解决方案是重写 onMeasure,根据屏幕高度动态计算列表高度。

主题定制:让车厂有自己的“皮肤”

每个车厂都想让自己的车机看起来与众不同。主题定制就是干这个的。Android Automotive 的主题系统基于 Material Design 3,但做了车载化扩展。

核心思路是:用属性(Attribute)代替硬编码颜色。比如,你定义一个 ?attr/carColorPrimary,然后在不同的主题里给这个属性赋不同的值。这样,换主题时只需要改一个 themes.xml 文件。

我见过最夸张的项目,一个 App 有 12 套主题,对应不同车型的仪表盘风格。嗯,这里要注意,主题文件不要写死颜色值,一定要用 @color/xxx 引用。否则后期维护会想哭。

<!-- 主题定义示例 -->
<style name="Theme.Volvo" parent="Theme.CarUI">
    <item name="carColorPrimary">@color/volvo_blue</item>
    <item name="carColorOnPrimary">@color/white</item>
    <item name="carFontFamily">@font/volvo_sans</item>
</style>

<style name="Theme.BMW" parent="Theme.CarUI">
    <item name="carColorPrimary">@color/bmw_red</item>
    <item name="carColorOnPrimary">@color/white</item>
    <item name="carFontFamily">@font/bmw_sans</item>
</style>

你想想看,如果每个页面都直接写 #FF0000,换主题时得改多少地方?用属性引用,一行代码就能换整套配色。

知识体系总览

下面这张图是我自己整理的 HMI 框架核心逻辑。你可以看到,Car UI Library 在最底层提供基础组件,布局适配层处理屏幕差异,主题定制层负责视觉风格。三层各司其职,缺一不可。

Android Automotive HMI 框架三层结构 第一层:Car UI Library 基础组件:CarTextButton, CarIconButton, CarListItem, CarToolbar 特性:驾驶状态感知、最小触摸区域、安全交互限制 第二层:布局适配 ConstraintLayout + 百分比约束 + WindowInsets 适配:异形屏、带鱼屏、竖屏中控、安全区域 第三层:主题定制 Material Design 3 + 车载扩展属性(carColorPrimary 等)

实战中的几个关键点

光讲理论没意思,我分享几个实际项目中的经验。

场景 问题 解决方案
横竖屏切换 布局重新加载,状态丢失 使用 onSaveInstanceState 保存状态,或使用 ViewModel
字体大小 车载屏幕远,字体太小看不清 使用 sp 单位,并设置最小字号为 14sp
触摸反馈 驾驶时触摸无震动反馈 使用 View.performHapticFeedback() 添加触觉反馈
多主题切换 切换时页面闪烁 使用 AppCompatDelegate.setDefaultNightMode() 配合夜间模式
警告: 不要在生产环境中使用 getResources().getColor() 直接获取颜色值。这会导致主题切换时不生效。一定要通过 Theme 属性引用。

总结一下

Car UI Library 给了我们一套“安全”的组件,布局适配让我们面对各种屏幕不慌,主题定制则让车厂有了自己的调性。这三者结合起来,才能做出一个既好看又好用的车载 HMI。

我记得刚入行时,总觉得 UI 是设计师的事。后来发现,不懂 HMI 框架的开发者,写出来的代码要么跑不动,要么跑起来丑得没法看。嗯,希望今天的分享能帮你少走一些弯路。


公众号:蓝海资料掘金营,微信deep3321