车载Launcher开发:从架构到定制
Launcher,说白了就是车载系统的「门面」。用户一上车,第一眼看到的就是它。我做了这么多年车载开发,可以说Launcher的体验直接决定了用户对这套系统的第一印象。今天我们就来聊聊车载Launcher的开发,从架构设计到多屏适配,再到快捷控制面板,把这块硬骨头啃下来。
Launcher架构:别急着写代码
很多人一上来就撸代码,结果后面改得想哭。我个人习惯,先画好架构图再动手。车载Launcher的架构,其实可以拆成三层:
- 数据层:负责管理应用列表、快捷方式、壁纸等数据源
- 逻辑层:处理交互逻辑、页面切换、状态管理
- UI层:渲染界面,处理触摸事件
为什么要分层?说白了就是为了解耦。你想想看,如果UI和逻辑混在一起,换一个屏幕尺寸就得重写一大片代码,那不得累死?
核心原则:Launcher架构要遵循「数据驱动UI」的模式。数据变了,UI自动更新。千万别搞反了。
CarLauncher与AppGrid:两个核心组件
CarLauncher和AppGrid,是车载Launcher里最核心的两个东西。CarLauncher负责主界面,AppGrid负责应用列表。我刚开始做的时候,总想把它们揉在一起,后来发现分开才是正道。
CarLauncher:主界面的设计思路
CarLauncher通常包含这几个区域:
- 状态栏:显示时间、信号、电量等信息
- 快捷卡片:音乐、导航、天气等常用功能
- 底部Dock栏:常用应用快捷入口
- 壁纸/主题:背景展示
嗯,这里要注意一点:CarLauncher的布局要考虑到驾驶安全。按键要够大,间距要够宽,不能让驾驶员分心去点小按钮。我曾经见过一个项目,把按钮做得很小很精致,结果被安全评审打回来重做,教训深刻啊。
我的经验:CarLauncher的触摸目标尺寸建议不小于64dp,间距不小于8dp。这是Android Automotive的官方建议,也是血的教训换来的。
AppGrid:应用列表的展示逻辑
AppGrid说白了就是所有应用的网格展示。但车载和手机不一样,车载的应用列表要考虑:
- 应用数量通常较少(20-50个)
- 需要支持分类(导航、音乐、工具等)
- 支持搜索和排序
- 支持隐藏/禁用某些应用
代码实现上,我推荐用RecyclerView配合GridLayoutManager。为什么?因为灵活。你可以轻松调整列数、间距、动画效果。
// AppGrid的核心实现片段
class AppGridFragment : Fragment() {
private lateinit var appAdapter: AppAdapter
private lateinit var appViewModel: AppViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<RecyclerView>(R.id.app_grid)
recyclerView.layoutManager = GridLayoutManager(context, 4) // 4列
recyclerView.addItemDecoration(GridSpacingItemDecoration(16)) // 间距
appAdapter = AppAdapter { appInfo ->
// 点击启动应用
startActivity(appInfo.intent)
}
recyclerView.adapter = appAdapter
// 观察数据变化
appViewModel.appList.observe(viewLifecycleOwner) { apps ->
appAdapter.submitList(apps)
}
}
}
多屏Launcher适配:别被屏幕数量吓到
车载系统最头疼的就是多屏适配。仪表盘、中控屏、副驾屏、后排屏……每个屏幕的尺寸、分辨率、交互方式都不一样。我刚开始做多屏适配时,差点被搞崩溃。
后来我总结了一套方法:
- 使用ConstraintLayout:相对布局,自适应强
- 定义尺寸资源:不同屏幕用不同的dimens.xml
- Fragment化:每个屏幕区域用独立的Fragment
- 统一ViewModel:数据共享,UI独立
避坑指南:我曾经在一个项目里,给每个屏幕单独写了一套布局文件,结果后期维护量爆炸。后来改成用Fragment+ViewModel的模式,一个数据源驱动多个UI,清爽多了。
多屏适配的核心,其实就是「数据统一,UI分离」。你想想看,仪表盘和中控屏显示的是同一个车速数据,但展示方式完全不同。仪表盘用指针,中控屏用数字。这不就是典型的数据统一、UI分离吗?
| 屏幕类型 | 典型分辨率 | 适配策略 |
|---|---|---|
| 仪表盘 | 1920x720 | 横屏、宽高比大、注重驾驶信息 |
| 中控屏 | 1280x720 | 横屏、触摸交互、功能丰富 |
| 副驾屏 | 1280x720 | 横屏、娱乐为主、独立交互 |
| 后排屏 | 1920x1080 | 横屏/竖屏、影音娱乐 |
快捷控制面板(Quick Controls)开发
快捷控制面板,就是用户从屏幕顶部下滑或者从侧边滑出的那个控制中心。音量、亮度、蓝牙、WiFi、驾驶模式切换……这些高频操作都放在这里。
开发快捷控制面板,有几个关键点:
- 手势识别:下滑、侧滑、长按等手势的识别
- 动画效果:展开/收起要有流畅的过渡动画
- 状态同步:控制项的状态要和系统实时同步
- 可配置性:支持用户自定义快捷开关的顺序
我个人习惯用BottomSheetBehavior来实现快捷控制面板的展开和收起。配合自定义的动画插值器,效果非常丝滑。
// 快捷控制面板的核心逻辑
class QuickControlsPanel(context: Context, attrs: AttributeSet?) :
LinearLayout(context, attrs) {
private val controls = mutableListOf<QuickControl>()
fun addControl(control: QuickControl) {
controls.add(control)
// 动态添加控制项视图
val view = LayoutInflater.from(context)
.inflate(R.layout.item_quick_control, this, false)
view.findViewById<ImageView>(R.id.icon).setImageResource(control.icon)
view.findViewById<TextView>(R.id.label).text = control.label
view.setOnClickListener { control.onClick() }
addView(view)
}
fun updateControlState(id: String, isActive: Boolean) {
// 更新某个控制项的状态
controls.find { it.id == id }?.let { control ->
control.isActive = isActive
// 更新UI
}
}
}
小技巧:快捷控制面板的动画时长建议控制在200-300ms之间。太短了显得生硬,太长了用户会觉得卡顿。这个数值是我在多个项目中试出来的黄金区间。
还有一个容易被忽略的点:快捷控制面板要支持「半展开」和「全展开」两种状态。半展开显示最常用的几个开关,全展开显示所有控制项。这样既方便快速操作,又不占用太多屏幕空间。
好了,关于车载Launcher开发的核心内容就聊到这里。从架构设计到具体实现,从单屏到多屏适配,再到快捷控制面板,每一步都有坑,但也都有解法。做车载开发就是这样,细节决定成败,经验积累多了,自然就顺手了。