HarmonyOS 6.1 车机应用实战:把电商“搬”进智能座舱
系列番外·智慧出行篇。上篇聊完空间计算,评论区有车友问:“鸿蒙车机这么火,能不能把咱们的电商Demo适配到车机上?等红灯时逛个街?” 这需求太真实了!HarmonyOS智能座舱(HarmonySpace)本身就是分布式能力的集大成者。今天我们将电商Demo“上车”,打造一个专为驾驶场景优化的车载商城。重点解决三大车机开发痛点:驾驶安全约束(DO NOT DISTURB)、语音交互替代触控、屏幕自适应(横屏+折叠屏)。全程基于API23,含官方文档未提及的车机专属UI规范。
一、前言:为什么车机是电商的新蓝海?
在手机端,电商App已是一片红海。但在车机端,这是一个全新的增量市场:
-
场景独特:用户在通勤、自驾游途中,可能产生特定的购物需求(如订购咖啡、预订酒店、购买周边文创)。
-
系统级整合:鸿蒙车机不是简单的Android车机,它能与手机、手表无缝流转。手机上选好的商品,上车自动续接;车机上下的单,手机手表同步通知。
-
交互变革:驾驶时不能低头玩手机,因此语音交互和简化UI成为核心。这恰好是鸿蒙的强项。
本文将基于之前的电商Demo,改造出一个符合车规级安全标准的车机应用。我们将重点放在“听单”和“语音购”上,而非传统的滑动浏览。
二、核心概念辨析(手机App vs 车机App)
很多开发者直接把手机App搬到车机上,结果审核不通过。必须先理解两者的本质差异:
|
维度 |
手机电商App |
车机电商App(合规版) |
|---|---|---|
|
核心交互 |
手指触控(精细操作) |
语音交互(主要)+ 旋钮/触摸(辅助) |
|
UI设计 |
纵向滚动,信息密集 |
横向布局,卡片化,字体巨大,信息极简 |
|
安全约束 |
无特殊限制 |
驾驶模式:时速>5km/h时,禁止视频播放、复杂表单输入 |
|
硬件能力 |
触屏、摄像头 |
方向盘按键、旋钮、车速信号、GPS |
|
适用场景 |
深度购物、比价 |
即时性消费:订餐、买景区门票、充值时 |
💡 核心认知:车机App的首要原则是驾驶安全。任何可能导致驾驶员分心的设计都是违规的。我们的目标不是做一个“能用的车机App”,而是做一个“安全的车机App”。
三、代码实现:从手机到车机的“适应性进化”
3.1 环境准备与配置
-
新建模块:在DevEco Studio中,右键项目 -> New -> Module -> Car Module。
-
权限配置:车机对权限管控极严。修改
module.json5:{ "module": { "deviceTypes": ["car"], // 声明为车机应用 "requestPermissions": [ { "name": "ohos.permission.DRIVE_MODE", "reason": "检测驾驶状态以调整UI" }, { "name": "ohos.permission.MICROPHONE", "reason": "语音搜索商品" }, { "name": "ohos.permission.LOCATION", "reason": "推荐沿途服务区商品" } ] } } -
屏幕适配:车机多为横屏(1280x720或1920x1080)。在
resources目录下新建layout-car目录存放车机专用布局。
3.2 构建车机专属UI(横向卡片流)
创建entry_car/src/main/ets/pages/Index.ets。抛弃手机端的List纵向滚动,改用Swiper横向切换。
import { router } from '@kit.ArkUI'
import { abilityAccessCtrl } from '@kit.AbilityKit'
@Entry
@Component
struct CarIndex {
private swiperController: SwiperController = new SwiperController()
@State currentIndex: number = 0
@State isDriving: boolean = false // 是否处于驾驶模式
private goodsList: GoodsBean[] = [] // 复用之前的商品数据模型
aboutToAppear(): void {
this.loadGoodsForDrive()
this.monitorDrivingStatus()
}
/**
* 加载适合驾驶场景的商品(如咖啡、零食、景区票)
*/
loadGoodsForDrive(): void {
// 模拟数据:只加载即时性消费品
this.goodsList = [
{ id: 101, name: '沿途星巴克咖啡', price: 35, image: 'coffee.jpg' },
{ id: 102, name: '服务区特色盒饭', price: 45, image: 'rice.jpg' },
{ id: 103, name: '张家界景区门票', price: 228, image: 'ticket.jpg' }
]
}
/**
* 监听驾驶状态(模拟车速信号)
*/
monitorDrivingStatus(): void {
// 实际开发中,需通过Car Kit获取真实车速
// 此处模拟:5秒后进入驾驶模式
setTimeout(() => {
this.isDriving = true
promptAction.showToast({ message: '已进入驾驶模式,部分功能受限' })
}, 5000)
}
build() {
Stack() {
// 背景图:模拟驾驶场景
Image($r('app.media.car_bg'))
.width('100%')
.height('100%')
.blur(this.isDriving ? 0 : 10) // 非驾驶模式模糊背景,减少干扰
Column() {
// 顶部状态栏
Row() {
Text('智慧商城')
.fontSize(28)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
Blank()
// 驾驶模式指示器
if (this.isDriving) {
Text('🚗 行驶中')
.fontSize(18)
.fontColor('#FFD700')
.backgroundColor('#333')
.padding(8)
.borderRadius(16)
}
}
.width('90%')
.margin({ top: 30 })
Blank()
// 核心:横向商品卡片流
Swiper({
controller: this.swiperController,
indicator: false, // 隐藏指示器,减少视觉干扰
loop: false
}) {
ForEach(this.goodsList, (item: GoodsBean) => {
this.GoodsCard(item)
}, (item: GoodsBean) => item.id.toString())
}
.width('100%')
.height(400)
.onChange((index: number) => {
this.currentIndex = index
})
Blank()
// 底部操作提示(语音优先)
Row() {
Text('🎤 说“小艺小艺,购买这个”')
.fontSize(22)
.fontColor(Color.White)
Blank()
// 辅助触控:仅在停车时可用
Button('确认购买')
.enabled(!this.isDriving) // 驾驶时禁用按钮
.backgroundColor(this.isDriving ? '#666' : '#0A59F7')
.onClick(() => this.buyCurrentItem())
}
.width('90%')
.margin({ bottom: 30 })
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
/**
* 商品卡片组件(车机专用:大图、大字、少文字)
*/
@Builder
GoodsCard(item: GoodsBean) {
Column() {
Image(item.image)
.width(500)
.height(280)
.borderRadius(24)
.objectFit(ImageFit.Cover)
Text(item.name)
.fontSize(32)
.fontColor(Color.White)
.margin({ top: 15 })
.maxLines(1)
Text(`¥${item.price}`)
.fontSize(36)
.fontColor('#FFD700')
.fontWeight(FontWeight.Bold)
.margin({ top: 10 })
}
.padding(20)
.backgroundColor('#1A1A1A')
.borderRadius(32)
.shadow({ radius: 20, color: '#000000' })
}
/**
* 购买当前商品(语音调用此方法)
*/
buyCurrentItem(): void {
const item = this.goodsList[this.currentIndex]
// 模拟下单
promptAction.showToast({ message: `已下单:${item.name},请停车后支付` })
// 实际开发中:调用云函数创建订单,并同步到手机端
}
}
3.3 集成语音交互(核心差异点)
车机不能让司机低头打字,必须集成语音。创建entry_car/src/main/ets/common/VoiceControl.ets。
import { voiceInteraction } from '@kit.VoiceInteractionKit'
import { BusinessError } from '@kit.BasicServicesKit'
/**
* 车机语音控制类
*/
export class VoiceControl {
private context: Context = null!
private voiceEngine: voiceInteraction.VoiceEngine | null = null
constructor(context: Context) {
this.context = context
}
/**
* 初始化语音引擎
*/
async init(): Promise<void> {
try {
// 创建语音引擎,指定车机场景
this.voiceEngine = await voiceInteraction.createVoiceEngine(this.context, {
scenario: voiceInteraction.Scenario.IN_CAR, // 关键:车机场景优化
wakeupWords: ['小艺小艺'], // 唤醒词
asrConfig: {
vadEnable: true, // 自动端点检测(自动判断说话结束)
maxSpeechTime: 10 // 最长录音10秒
}
})
// 注册语义理解回调
this.voiceEngine.on('intent', (intent: voiceInteraction.Intent) => {
this.handleIntent(intent)
})
console.log('车机语音引擎初始化成功')
} catch (err) {
const e = err as BusinessError
console.error('语音引擎初始化失败:', e.message)
}
}
/**
* 处理语音意图
*/
private handleIntent(intent: voiceInteraction.Intent): void {
console.log('识别到意图:', intent.action)
switch (intent.action) {
case 'buy_current_item':
// 用户说“购买这个”
// 调用UI层的购买逻辑
break
case 'next_item':
// 用户说“下一个”
// 调用Swiper的next()
break
case 'previous_item':
// 用户说“上一个”
break
default:
console.log('未识别的指令')
}
}
/**
* 开始录音识别
*/
startListening(): void {
if (this.voiceEngine) {
this.voiceEngine.startListening()
}
}
/**
* 释放资源
*/
release(): void {
if (this.voiceEngine) {
this.voiceEngine.release()
}
}
}
注意:在CarIndex.ets的aboutToAppear中初始化VoiceControl,并在onDestroy中释放。
四、踩坑记录(官方文档没写的6个车机坑)
-
驾驶模式检测延迟:
Car Kit获取车速有1-2秒的延迟。不要直接用实时车速判断是否禁用UI,而要监听DRIVING_STATUS_CHANGED广播,否则会出现界面闪烁。 -
语音ASR(识别)不准:车机环境嘈杂(风噪、路噪)。必须在
asrConfig中开启降噪算法(noiseSuppression: true),否则识别率极低。 -
UI字体大小:车机UI的最小字体不得小于24fp。官方规范是“扫视可见”,驾驶员只有几百毫秒的视觉停留时间。
-
横屏适配陷阱:手机布局是
Column纵向,matchParent在横屏下会变宽。车机布局应多用Row和固定尺寸,防止拉伸变形。 -
方向盘按键映射:车机应用必须支持方向盘上的“上一首/下一首”按键。这需要监听
KeyEvent,并将其映射为Swiper的滑动事件。 -
审核红线:时速超过5km/h时,绝对不能弹出输入法(Keyboard)。如果必须输入,必须引导用户说“请停车后操作”或使用语音输入。
更多推荐


所有评论(0)