HarmonyOS ArkUI 表冠事件(Digital Crown Event)全面解析与实战演示

HarmonyOS ArkUI 表冠事件(Digital Crown Event)全面解析与实战演示

文章目录

一、数字表冠核心概念

1.1 什么是数字表冠?

数字表冠(Digital Crown) 是华为手表侧边的旋转旋钮,类比于鼠标滚轮但专为小屏穿戴设计。它支持两种物理操作:

  1. 旋转(Rotate):顺时针或逆时针拨动,产生连续的增量值,驱动 UI 滚动、数值增减、缩放等
  2. 按压(End/Start):按下/松开表冠,触发确认、返回或阶段性交互

数字表冠的典型应用场景:

  • 列表滚动:在消息列表、联系人列表中上下翻页
  • 数值调节:调节音量、亮度、闹钟时间、运动目标
  • 图片缩放:在图库中放大/缩小预览图片
  • 进度控制:调节视频/音乐播放进度、健康指标设定
  • 菜单选择:在圆形表盘菜单中旋转定位选项

1.2 表冠事件与其他输入事件对比

对比项表冠事件 onDigitalCrown轴事件 onAxisEvent触摸事件 onTouch
输入设备手表数字表冠鼠标滚轮/触控板触摸屏/手写笔
设备限制✅ 仅 Watch 设备PC/平板手机/平板/手表
增量单位sensitivity(原始增量)/ focusSensitivity(框架校准值)offset(XY 偏移量)触点坐标
焦点依赖✅ 需要组件有焦点✅ 需要组件有焦点❌ 无需焦点
旋转方向正值/负值区分顺逆XY 轴方向无方向概念
速度感知focusSensitivity 放大倍率滚动速度滑动速度需手势计算
关键注意onDigitalCrownWatch 专属 API,在手机、平板等非穿戴设备上该回调永远不会触发。开发 Watch 应用时需在 module.json5deviceTypes 中包含 "wearable" 才能正常发布。

二、核心 API 详解

2.1 onDigitalCrown 事件接口

onDigitalCrown 是挂载在任意可聚焦 ArkUI 组件上的通用事件属性,签名如下:

// onDigitalCrown 事件属性签名(ArkTS).onDigitalCrown((event: CrownEvent)=>void)
  • 无返回值:表冠事件不需要消费控制,直接处理回调即可
  • 触发前提:组件必须处于焦点状态,容器组件需显式设置 .focusable(true)
  • 触发时机:用户拨动表冠旋转时持续触发,每次旋转检测间隔约 16ms(60fps 频率)

2.2 CrownEvent 完整结构

CrownEvent 是表冠事件的核心数据对象,包含以下 4 个字段:

// CrownEvent 完整接口说明(ArkTS)interfaceCrownEvent{ timestamp:number// 事件时间戳(毫秒),用于计算旋转速度 deviceId:number// 输入设备 ID,区分不同硬件来源 sensitivity:number// 原始灵敏度增量:传感器检测到的本次旋转量 focusSensitivity:number// 焦点灵敏度增量:框架按焦点强度放大/缩小后的最终值}

2.3 两种灵敏度字段详解

字段类型说明适用场景
sensitivitynumber原始增量,传感器直接输出,正值=顺时针,负值=逆时针需要精确物理感知的场景(如绘图、游戏)
focusSensitivitynumber框架校准值,在原始值基础上乘以焦点权重系数常规 UI 滚动、数值调节(推荐优先使用)
最佳实践:日常开发中优先使用 focusSensitivity 驱动 UI 变化;仅在需要感知硬件原始转速(如转速游戏、精密仪器模拟)时才使用 sensitivity
// 读取两种灵敏度的对比示例.onDigitalCrown((event: CrownEvent)=>{console.info(`时间戳: ${event.timestamp}ms`)console.info(`设备ID: ${event.deviceId}`)console.info(`原始增量 sensitivity: ${event.sensitivity.toFixed(4)}`)console.info(`校准增量 focusSensitivity: ${event.focusSensitivity.toFixed(4)}`)// 正值 = 顺时针旋转;负值 = 逆时针旋转const dir = event.focusSensitivity >0?'顺时针 ↻':'逆时针 ↺'console.info(`旋转方向: ${dir}`)})

三、基础用法:表冠数据实时显示

3.1 可运行完整示例

// entry/src/main/ets/pages/Index.ets@Entry@Component struct BasicCrownDemo {@State timestamp:number=0@State deviceId:number=0@State sensitivity:number=0@State focusSensitivity:number=0@State totalRotation:number=0@State eventCount:number=0@State isFocused:boolean=falsebuild(){Column({ space:12}){Text('表冠事件基础演示').fontSize(20).fontWeight(FontWeight.Bold).fontColor(Color.White).margin({ top:20})// 焦点状态指示Row(){Text(this.isFocused ?'● 监听中':'○ 未激活').fontSize(12).fontColor(this.isFocused ?'#4CAF50':'#9E9E9E')Blank()Text(`触发 ${this.eventCount} 次`).fontSize(12).fontColor('#9E9E9E')}.width('90%')// 数据面板Column({ space:8}){Row(){Text('旋转方向:').fontWeight(FontWeight.Medium).width(120).fontColor('#BBBBBB')}Row(){Text('原始增量:').fontWeight(FontWeight.Medium).width(120).fontColor('#BBBBBB')Text(this.sensitivity.toFixed(4)).fontColor('#42A5F5')}Row(){Text('校准增量:').fontWeight(FontWeight.Medium).width(120).fontColor('#BBBBBB')Text(this.focusSensitivity.toFixed(4)).fontColor('#FF9800')}Row(){Text('累计旋转:').fontWeight(FontWeight.Medium).width(120).fontColor('#BBBBBB')Text(this.totalRotation.toFixed(2)).fontColor('#CE93D8')}Row(){Text('设备 ID:').fontWeight(FontWeight.Medium).width(120).fontColor('#BBBBBB')Text(this.deviceId.toString()).fontColor('#80CBC4')}Row(){Text('时间戳:').fontWeight(FontWeight.Medium).width(120).fontColor('#BBBBBB')Text(this.timestamp.toString()).fontColor('#A5D6A7').fontSize(11)}}.width('90%').padding(14).backgroundColor('#1E1E2E').borderRadius(14).border({ width:1, color:'#333355', style: BorderStyle.Solid }).alignItems(HorizontalAlign.Start)// 表冠感应区Column(){Text(this.isFocused ?'⌚ 请拨动表冠':'👆 点击激活').fontSize(14).fontColor(this.isFocused ?'#4CAF50':'#888888')}.width('80%').height(80).backgroundColor(this.isFocused ?'#1B2A1B':'#1E1E1E').borderRadius(40).justifyContent(FlexAlign.Center).border({ width:2, color:this.isFocused ?'#4CAF50':'#444444', style: BorderStyle.Solid }).focusable(true).animation({ duration:300, curve: Curve.EaseOut }).onFocus(()=>{this.isFocused =true}).onBlur(()=>{this.isFocused =false}).onDigitalCrown((event: CrownEvent)=>{this.timestamp = event.timestamp this.eventCount++})Text('提示:点击圆形区域获取焦点后拨动手表表冠').fontSize(11).fontColor('#666666').textAlign(TextAlign.Center).width('90%')}.width('100%').height('100%').backgroundColor('#121212').alignItems(HorizontalAlign.Center)}}

运行效果如图所示

在这里插入图片描述

四、列表滚动控制

4.1 表冠驱动列表滚动

列表滚动是手表最核心的交互场景。以下示例通过 Scroller 控制器,将 focusSensitivity 增量换算为像素偏移量,实现丝滑的列表滚动效果:

// entry/src/main/ets/pages/Index.ets@Entry@Component struct CrownScrollDemo {@State scrollOffset:number=0@State currentItem:number=0@State crownSpeed:number=0private scroller: Scroller =newScroller()privatereadonlySCROLL_FACTOR:number=80// 灵敏度放大系数private listData:string[]=['🏃 晨跑记录 - 5.2km','💓 心率监测 - 72bpm','😴 睡眠分析 - 7h30m','🔥 卡路里 - 2150kcal','🏋️ 力量训练 - 45min','🚴 骑行记录 - 22km','🧘 冥想训练 - 15min','💧 饮水提醒 - 2.1L','📱 消息通知 - 12条','⏰ 闹钟设置 - 07:30','🌡️ 天气 - 晴 23°C','🎵 音乐控制 - 播放中']build(){Column({ space:0}){// 标题栏Text('健康数据').fontSize(18).fontWeight(FontWeight.Bold).fontColor(Color.White).width('100%').textAlign(TextAlign.Center).padding({ top:16, bottom:12}).backgroundColor('#0A1628')// 速度指示条Row(){Text('旋转速度:').fontSize(11).fontColor('#666666').width(80)Column().height(4).width(`${Math.min(Math.abs(this.crownSpeed)*20,100)}%`).backgroundColor(this.crownSpeed >0?'#4CAF50':'#F44336').borderRadius(2).animation({ duration:100, curve: Curve.Linear })}.width('100%').padding({ left:12, right:12, top:6, bottom:6}).backgroundColor('#0D0D1A')// 可滚动列表List({ scroller:this.scroller, space:2}){ForEach(this.listData,(item:string, index:number)=>{ListItem(){Row(){Text(item).fontSize(14).fontColor(index ===this.currentItem ? Color.White :'#AAAAAA').layoutWeight(1)if(index ===this.currentItem){Text('▶').fontSize(12).fontColor('#4CAF50')}}.width('100%').height(48).padding({ left:16, right:16}).backgroundColor(index ===this.currentItem ?'#1A3A1A':'transparent').borderRadius(8).border({ width: index ===this.currentItem ?1:0, color:'#4CAF50', style: BorderStyle.Solid }).animation({ duration:150, curve: Curve.EaseOut })}})}.layoutWeight(1).width('100%').focusable(true).defaultFocus(true).edgeEffect(EdgeEffect.Spring).onDigitalCrown((event: CrownEvent)=>{// 同步高亮当前可见项const newIndex = Math.max(0, Math.min(this.listData.length -1,))if(newIndex !==this.currentItem){this.currentItem = newIndex }})}.width('100%').height('100%').backgroundColor('#0A0A14')}}

运行效果如图所示

在这里插入图片描述

五、数值调节控制器

5.1 音量/亮度旋钮

手表上调节音量、亮度是典型的"旋钮式"交互。以下示例实现了一个圆形旋钮组件,用表冠驱动数值在 0~100 范围内平滑调节:

// entry/src/main/ets/pages/Index.ets@Entry@Component struct CrownKnobDemo {@State volume:number=50@State brightness:number=75@State activeKnob:number=0// 0=音量, 1=亮度@State isFocused:boolean=falseprivatereadonlySTEP:number=2// 每次增量步进privateclamp(val:number, min:number, max:number):number{return Math.max(min, Math.min(max, val))}@BuilderKnobItem(label:string, value:number, index:number, color:string){Column({ space:6}){// 圆形进度旋钮Stack(){// 背景圆环Column().width(80).height(80).borderRadius(40).backgroundColor('#1A1A2E').border({ width:4, color:'#2A2A3E', style: BorderStyle.Solid })// 进度指示(用 Text 模拟弧形角度)Text(`${value}`).fontSize(22).fontWeight(FontWeight.Bold).fontColor(this.activeKnob === index ? color :'#CCCCCC')}.width(80).height(80).onClick(()=>{this.activeKnob = index }).border({ width:this.activeKnob === index ?2:0, color: color, style: BorderStyle.Solid }).borderRadius(40).animation({ duration:200, curve: Curve.EaseOut })Text(label).fontSize(12).fontColor(this.activeKnob === index ? color :'#666666')// 进度条Column(){Column().width(`${value}%`).height('100%').backgroundColor(color).borderRadius(2).animation({ duration:80, curve: Curve.Linear })}.width(80).height(4).backgroundColor('#2A2A3E').borderRadius(2)}.alignItems(HorizontalAlign.Center)}build(){Column({ space:20}){Text('旋钮调节演示').fontSize(18).fontWeight(FontWeight.Bold).fontColor(Color.White).margin({ top:24})Text(this.isFocused ?'⌚ 拨动表冠调节数值':'👆 点击下方区域激活').fontSize(12).fontColor(this.isFocused ?'#4CAF50':'#666666')// 双旋钮布局Row({ space:30}){this.KnobItem('🔊 音量',this.volume,0,'#42A5F5')this.KnobItem('☀️ 亮度',this.brightness,1,'#FFB300')}// 当前激活旋钮名称Text(`当前调节:${this.activeKnob ===0?'音量':'亮度'} = ${this.activeKnob ===0?this.volume :this.brightness}`).fontSize(14).fontColor(Color.White).fontWeight(FontWeight.Medium)// 快速切换提示Text('点击旋钮切换调节目标,拨动表冠调节数值').fontSize(11).fontColor('#555555').textAlign(TextAlign.Center).width('90%')// 隐藏焦点承载区Column().width('100%').height(60).focusable(true).defaultFocus(true).onFocus(()=>{this.isFocused =true}).onBlur(()=>{this.isFocused =false}).onDigitalCrown((event: CrownEvent)=>{const delta = Math.round(event.focusSensitivity *this.STEP*10)if(this.activeKnob ===0){this.volume =this.clamp(this.volume + delta,0,100)}else{this.brightness =this.clamp(this.brightness + delta,0,100)}})}.width('100%').height('100%').backgroundColor('#0A0A14').alignItems(HorizontalAlign.Center)}}

六、图片缩放控制

6.1 表冠驱动图片缩放

以下示例展示了用表冠控制图片缩放比例,模拟手表图库的放大/缩小交互:

// entry/src/main/ets/pages/Index.ets@Entry@Component struct CrownZoomDemo {@State scale:number=1.0@State scaleText:string='100%'@State isFocused:boolean=false@State lastDelta:number=0privatereadonlyMIN_SCALE:number=0.5privatereadonlyMAX_SCALE:number=3.0privatereadonlyZOOM_FACTOR:number=0.15build(){Column({ space:0}){// 顶部信息栏Row(){Text('图库缩放').fontSize(14).fontColor(Color.White).fontWeight(FontWeight.Medium)Blank()Text(this.scaleText).fontSize(14).fontColor('#42A5F5').fontWeight(FontWeight.Bold)}.width('100%').padding({ left:16, right:16, top:12, bottom:8}).backgroundColor('#0A1020')// 图片展示区(作为焦点承载区)Stack(){// 背景Column().width('100%').height('100%').backgroundColor('#111122')// 图片占位(用渐变色块模拟图片)Column(){Text('🌄').fontSize(60*this.scale).animation({ duration:50, curve: Curve.Linear })Text('风景照片').fontSize(14).fontColor('#AAAAAA').margin({ top:8})}.justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).scale({ x:this.scale, y:this.scale }).animation({ duration:50, curve: Curve.Linear })// 焦点激活提示if(!this.isFocused){Column(){Text('👆 点击激活表冠缩放').fontSize(12).fontColor('#888888')}.width('100%').height(40).justifyContent(FlexAlign.Center).backgroundColor('#00000099').position({ x:0, y:'auto'})}}.width('100%').layoutWeight(1).focusable(true).defaultFocus(true).onFocus(()=>{this.isFocused =true}).onBlur(()=>{this.isFocused =false}).onDigitalCrown((event: CrownEvent)=>{this.lastDelta = event.focusSensitivity const newScale =this.scale + event.focusSensitivity *this.ZOOM_FACTORthis.scale = Math.max(this.MIN_SCALE, Math.min(this.MAX_SCALE, newScale))this.scaleText =`${Math.round(this.scale *100)}%`})// 底部控制栏Row({ space:16}){Text('🔍−').fontSize(16).fontColor('#888888')// 缩放进度条Column(){Column().height('100%').width(`${((this.scale -this.MIN_SCALE)/(this.MAX_SCALE-this.MIN_SCALE))*100}%`).backgroundColor('#42A5F5').borderRadius(2).animation({ duration:50, curve: Curve.Linear })}.layoutWeight(1).height(4).backgroundColor('#333333').borderRadius(2)Text('🔍+').fontSize(16).fontColor('#888888')}.width('100%').padding({ left:20, right:20, top:10, bottom:16}).backgroundColor('#0A1020')}.width('100%').height('100%').backgroundColor('#111122')}}

七、进度与时间选择器

7.1 表冠时间轮盘选择器

以下示例实现了手表场景中最常见的时间选择器,通过表冠旋转可分别调节小时和分钟:

// entry/src/main/ets/pages/Index.ets@Entry@Component struct CrownTimePickerDemo {@State hour:number=7@State minute:number=30@State editMode:number=0// 0=调小时, 1=调分钟@State isFocused:boolean=false@State feedbackMsg:string='拨动表冠调节时间'privateformatTime(h:number, m:number):string{return`${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}`}build(){Column({ space:0}){// 时钟显示Column({ space:4}){Text(this.formatTime(this.hour,this.minute)).fontSize(52).fontWeight(FontWeight.Bold).fontColor(Color.White).letterSpacing(4)// 模式切换指示Row({ space:8}){Text('时').fontSize(14).fontColor(this.editMode ===0?'#42A5F5':'#444444').fontWeight(this.editMode ===0? FontWeight.Bold : FontWeight.Normal).padding({ left:12, right:12, top:4, bottom:4}).backgroundColor(this.editMode ===0?'#0D2340':'transparent').borderRadius(8).border({ width:this.editMode ===0?1:0, color:'#42A5F5', style: BorderStyle.Solid }).onClick(()=>{this.editMode =0})Text('分').fontSize(14).fontColor(this.editMode ===1?'#FF9800':'#444444').fontWeight(this.editMode ===1? FontWeight.Bold : FontWeight.Normal).padding({ left:12, right:12, top:4, bottom:4}).backgroundColor(this.editMode ===1?'#2A1A00':'transparent').borderRadius(8).border({ width:this.editMode ===1?1:0, color:'#FF9800', style: BorderStyle.Solid }).onClick(()=>{this.editMode =1})}}.width('100%').padding({ top:40, bottom:30}).backgroundColor('#0A0A14').alignItems(HorizontalAlign.Center)// 反馈信息区Column(){Text(this.isFocused ?'⌚ '+this.feedbackMsg :'👆 点击激活表冠调节').fontSize(13).fontColor(this.isFocused ?'#AAAAAA':'#555555').textAlign(TextAlign.Center)}.width('100%').padding({ top:16, bottom:16}).backgroundColor('#0D0D1A')// 调节区(焦点承载)Column(){Text(this.editMode ===0?'调节小时(0–23)':'调节分钟(0–59)').fontSize(12).fontColor('#555555').margin({ bottom:8})// 当前数值大显示Text(this.editMode ===0?this.hour.toString().padStart(2,'0'):this.minute.toString().padStart(2,'0')).fontSize(48).fontColor(this.editMode ===0?'#42A5F5':'#FF9800').fontWeight(FontWeight.Bold)// 进度条Row(){Column().height(3).width(this.editMode ===0?`${(this.hour /23)*100}%`:`${(this.minute /59)*100}%`).backgroundColor(this.editMode ===0?'#42A5F5':'#FF9800').borderRadius(2).animation({ duration:80, curve: Curve.Linear })}.width('70%').height(3).backgroundColor('#222233').borderRadius(2).margin({ top:10})}.layoutWeight(1).width('100%').backgroundColor('#0A0A14').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).focusable(true).defaultFocus(true).onFocus(()=>{this.isFocused =true}).onBlur(()=>{this.isFocused =false}).onDigitalCrown((event: CrownEvent)=>{const delta = event.focusSensitivity >0?1:-1if(this.editMode ===0){this.hour =(this.hour + delta +24)%24this.feedbackMsg =`小时 → ${this.hour.toString().padStart(2,'0')}`}else{this.minute =(this.minute + delta +60)%60this.feedbackMsg =`分钟 → ${this.minute.toString().padStart(2,'0')}`}})}.width('100%').height('100%').backgroundColor('#0A0A14')}}

八、综合实战:完整表冠交互演示页面

8.1 功能说明

以下综合示例整合了本文所有核心知识点,单个页面通过 Tab 切换同时演示四个场景:

  1. 数据面板:实时显示 CrownEvent 全部 4 个字段及累计统计
  2. 数值调节:双旋钮(音量 + 亮度),点击切换调节目标
  3. 列表滚动:虚拟健康数据列表,表冠滚动 + 高亮跟踪
  4. 时间选择:小时/分钟双模式选择器

8.2 完整可运行代码

// entry/src/main/ets/pages/Index.ets@Entry@Component struct CrownEventDemo {// ── 事件数据 ──────────────────────────────────────@State evtTimestamp:number=0@State evtDeviceId:number=0@State evtSensitivity:number=0@State evtFocusSensitivity:number=0@State evtDirection:string='—'@State evtCount:number=0@State totalRotation:number=0// ── 选项卡 ────────────────────────────────────────@State activeTab:number=0private tabs:string[]=['📊 数据','🎛️ 旋钮','📋 列表','⏰ 时间']// ── 旋钮调节 ──────────────────────────────────────@State volume:number=50@State brightness:number=70@State activeKnob:number=0// ── 列表 ──────────────────────────────────────────@State listHighlight:number=0@State listScrollOffset:number=0private scroller: Scroller =newScroller()private listItems:string[]=['🏃 晨跑 5.2km','💓 心率 72bpm','😴 睡眠 7h30m','🔥 卡路里 2150','🏋️ 训练 45min','🚴 骑行 22km','🧘 冥想 15min','💧 饮水 2.1L','📱 通知 12条','⏰ 闹钟 07:30']// ── 时间选择 ──────────────────────────────────────@State hour:number=8@State minute:number=0@State timeEditMode:number=0// ── 焦点 ──────────────────────────────────────────@State isFocused:boolean=falseprivateclamp(v:number, lo:number, hi:number):number{return Math.max(lo, Math.min(hi, v))}privatehandleCrown(event: CrownEvent):void{// 更新公共数据字段this.evtTimestamp = event.timestamp this.evtDeviceId = event.deviceId this.evtSensitivity = event.sensitivity this.evtFocusSensitivity = event.focusSensitivity this.evtDirection = event.focusSensitivity >0?'顺时针 ↻':'逆时针 ↺'this.evtCount++this.totalRotation += event.focusSensitivity const sign = event.focusSensitivity >0?1:-1switch(this.activeTab){case1:// 旋钮const delta = Math.round(Math.abs(event.focusSensitivity)*20)* sign if(this.activeKnob ===0){this.volume =this.clamp(this.volume + delta,0,100)}else{this.brightness =this.clamp(this.brightness + delta,0,100)}breakcase2:// 列表this.scroller.scrollBy(0, event.focusSensitivity *60)this.listHighlight =this.clamp(this.listHighlight + sign,0,this.listItems.length -1)breakcase3:// 时间if(this.timeEditMode ===0){this.hour =(this.hour + sign +24)%24}else{this.minute =(this.minute + sign +60)%60}break}}@BuilderTabData(){Column({ space:10}){Text(this.isFocused ?'⌚ 拨动表冠观察数据变化':'👆 点击激活').fontSize(11).fontColor(this.isFocused ?'#4CAF50':'#666666').margin({ top:8})Column({ space:8}){Row(){Text('方向:').fontColor('#888888').width(100)Text(this.evtDirection).fontColor(this.evtFocusSensitivity >=0?'#4CAF50':'#F44336').fontWeight(FontWeight.Bold)}Row(){Text('原始增量:').fontColor('#888888').width(100)Text(this.evtSensitivity.toFixed(4)).fontColor('#42A5F5')}Row(){Text('校准增量:').fontColor('#888888').width(100)Text(this.evtFocusSensitivity.toFixed(4)).fontColor('#FF9800')}Row(){Text('累计旋转:').fontColor('#888888').width(100)Text(this.totalRotation.toFixed(2)).fontColor('#CE93D8')}Row(){Text('触发次数:').fontColor('#888888').width(100)Text(this.evtCount.toString()).fontColor('#80CBC4')}Row(){Text('设备 ID:').fontColor('#888888').width(100)Text(this.evtDeviceId.toString()).fontColor('#A5D6A7')}}.width('90%').padding(12).backgroundColor('#1A1A2E').borderRadius(12).border({ width:1, color:'#2A2A3E', style: BorderStyle.Solid }).alignItems(HorizontalAlign.Start)}.width('100%').alignItems(HorizontalAlign.Center)}@BuilderTabKnob(){Column({ space:16}){Text('点击旋钮切换目标,拨动表冠调节').fontSize(11).fontColor('#666666').margin({ top:10})Row({ space:24}){// 音量旋钮Column({ space:6}){Stack(){Column().width(72).height(72).borderRadius(36).backgroundColor('#0D1A2E').border({ width:3, color:this.activeKnob ===0?'#42A5F5':'#222233', style: BorderStyle.Solid })Text(`${this.volume}`).fontSize(20).fontWeight(FontWeight.Bold).fontColor(this.activeKnob ===0?'#42A5F5':'#AAAAAA')}.width(72).height(72).onClick(()=>{this.activeKnob =0})Text('🔊 音量').fontSize(11).fontColor(this.activeKnob ===0?'#42A5F5':'#666666')Column(){Column().height('100%').width(`${this.volume}%`).backgroundColor('#42A5F5').borderRadius(2).animation({ duration:60, curve: Curve.Linear })}.width(72).height(3).backgroundColor('#222233').borderRadius(2)}.alignItems(HorizontalAlign.Center)// 亮度旋钮Column({ space:6}){Stack(){Column().width(72).height(72).borderRadius(36).backgroundColor('#2A1A00').border({ width:3, color:this.activeKnob ===1?'#FF9800':'#222233', style: BorderStyle.Solid })Text(`${this.brightness}`).fontSize(20).fontWeight(FontWeight.Bold).fontColor(this.activeKnob ===1?'#FF9800':'#AAAAAA')}.width(72).height(72).onClick(()=>{this.activeKnob =1})Text('☀️ 亮度').fontSize(11).fontColor(this.activeKnob ===1?'#FF9800':'#666666')Column(){Column().height('100%').width(`${this.brightness}%`).backgroundColor('#FF9800').borderRadius(2).animation({ duration:60, curve: Curve.Linear })}.width(72).height(3).backgroundColor('#222233').borderRadius(2)}.alignItems(HorizontalAlign.Center)}}.width('100%').alignItems(HorizontalAlign.Center)}@BuilderTabList(){Column(){List({ scroller:this.scroller, space:2}){ForEach(this.listItems,(item:string, index:number)=>{ListItem(){Row(){Text(item).fontSize(13).fontColor(index ===this.listHighlight ? Color.White :'#888888').layoutWeight(1)if(index ===this.listHighlight){Text('◀').fontSize(10).fontColor('#4CAF50')}}.width('100%').height(42).padding({ left:14, right:14}).backgroundColor(index ===this.listHighlight ?'#1A3A1A':'transparent').borderRadius(8).animation({ duration:100, curve: Curve.EaseOut })}})}.width('100%').layoutWeight(1).edgeEffect(EdgeEffect.Spring)}.width('100%').layoutWeight(1)}@BuilderTabTime(){Column({ space:12}){Text(`${this.hour.toString().padStart(2,'0')}:${this.minute.toString().padStart(2,'0')}`).fontSize(48).fontWeight(FontWeight.Bold).fontColor(Color.White).margin({ top:20})Row({ space:12}){Text('时').fontSize(14).padding({ left:16, right:16, top:6, bottom:6}).fontColor(this.timeEditMode ===0?'#42A5F5':'#444444').backgroundColor(this.timeEditMode ===0?'#0D2340':'transparent').borderRadius(8).border({ width:this.timeEditMode ===0?1:0, color:'#42A5F5', style: BorderStyle.Solid }).onClick(()=>{this.timeEditMode =0})Text('分').fontSize(14).padding({ left:16, right:16, top:6, bottom:6}).fontColor(this.timeEditMode ===1?'#FF9800':'#444444').backgroundColor(this.timeEditMode ===1?'#2A1A00':'transparent').borderRadius(8).border({ width:this.timeEditMode ===1?1:0, color:'#FF9800', style: BorderStyle.Solid }).onClick(()=>{this.timeEditMode =1})}// 当前编辑值进度条Column(){Column().height('100%').width(this.timeEditMode ===0?`${(this.hour /23)*100}%`:`${(this.minute /59)*100}%`).backgroundColor(this.timeEditMode ===0?'#42A5F5':'#FF9800').borderRadius(2).animation({ duration:80, curve: Curve.Linear })}.width('70%').height(4).backgroundColor('#222233').borderRadius(2)Text(this.timeEditMode ===0?'调节小时 (0–23)':'调节分钟 (0–59)').fontSize(11).fontColor('#555555')}.width('100%').alignItems(HorizontalAlign.Center)}build(){Column({ space:0}){// 顶部标题Text('ArkUI 表冠事件综合演示').fontSize(17).fontWeight(FontWeight.Bold).fontColor(Color.White).width('100%').textAlign(TextAlign.Center).padding({ top:14, bottom:10}).backgroundColor('#0A1020')// 选项卡Row(){ForEach(this.tabs,(tab:string, index:number)=>{Text(tab).fontSize(11).layoutWeight(1).textAlign(TextAlign.Center).fontColor(this.activeTab === index ?'#42A5F5':'#666666').fontWeight(this.activeTab === index ? FontWeight.Bold : FontWeight.Normal).padding({ top:8, bottom:8}).border({ width:{ bottom:this.activeTab === index ?2:0}, color:'#42A5F5', style: BorderStyle.Solid }).onClick(()=>{this.activeTab = index })})}.width('100%').backgroundColor('#0D1020').border({ width:{ bottom:1}, color:'#1A1A2E', style: BorderStyle.Solid })// 主内容区(含 focusable + onDigitalCrown)Column({ space:0}){if(this.activeTab ===0){this.TabData()}elseif(this.activeTab ===1){this.TabKnob()}elseif(this.activeTab ===2){this.TabList()}else{this.TabTime()}}.layoutWeight(1).width('100%').focusable(true).defaultFocus(true).onFocus(()=>{this.isFocused =true}).onBlur(()=>{this.isFocused =false}).onDigitalCrown((event: CrownEvent)=>{this.handleCrown(event)})}.width('100%').height('100%').backgroundColor('#0A0A14')}}

九、注意事项与最佳实践

9.1 设备兼容性

表冠事件是 Watch 专属能力,开发时必须注意:

  1. deviceTypes 配置module.json5 中需包含 "wearable" 才能使用表冠相关 API
  2. 模拟器选择:在 DevEco Studio 的 Device Manager 中选择 Watch 类型模拟器运行
  3. 非手表设备onDigitalCrown 回调在手机、平板上不会触发,应提供触摸/按键兜底交互
  4. 硬件差异:不同型号手表表冠的 sensitivity 基准值可能不同,建议基于 focusSensitivity 做相对控制
// module.json5 设备类型配置{"module":{"deviceTypes":["wearable"],// 必须包含 wearable...}}

9.2 灵敏度调参建议

focusSensitivity 是框架归一化后的值,直接使用时应乘以业务系数:

场景推荐系数说明
列表滚动(像素)× 80~120每档旋转滚动约 80~120px
音量/亮度(0~100)× 10~20每档旋转变化约 2~4%
图片缩放(倍率)× 0.1~0.2每档旋转缩放约 10~20%
时间选择(档)取符号 sign每档旋转 ±1
游戏精确控制使用 sensitivity 原始值需要精确物理感知

9.3 焦点与事件生命周期

// 标准的焦点 + 表冠事件联动模式Column().focusable(true).defaultFocus(true)// 页面入口组件设置默认焦点.onFocus(()=>{// 获得焦点时启动动画/状态提示this.isListening =true}).onBlur(()=>{// 失去焦点时停止反馈、重置状态this.isListening =false}).onDigitalCrown((event: CrownEvent)=>{// 在此处理旋转逻辑// 无需返回值,直接处理即可})
常见误区ColumnRowStack 等容器组件默认 focusable=false,必须显式添加 .focusable(true) 后才能响应表冠事件。同时推荐在页面根容器加 .defaultFocus(true) 避免进入页面后表冠无响应。

9.4 性能优化建议

表冠旋转会以约 60fps 的频率持续触发事件,处理不当会导致 UI 卡顿:

  • 避免在回调中做耗时计算:复杂算法应使用节流(throttle)或防抖(debounce)处理
  • 状态更新精简化:每次只更新必要的 @State 变量,减少无效重渲染
  • 动画时长设短:配套动画使用 50~100ms 短时长,保证跟手感
  • Scroller.scrollBy 异步化:大列表滚动考虑使用 animateTo 包裹,避免同步阻塞

十、与其他事件协同使用

10.1 表冠 + 触摸双通道

在手表 UI 中,同一功能应同时支持表冠和触摸操作:

// 表冠 + 触摸操作同一 @State@State selectedIndex:number=0Column().focusable(true).onDigitalCrown((event: CrownEvent)=>{// 表冠旋转选择const sign = event.focusSensitivity >0?1:-1this.selectedIndex = Math.max(0, Math.min(9,this.selectedIndex + sign))})// 同一列表项也支持触摸点击Text(item).onClick(()=>{// 触摸点击直接跳转this.selectedIndex = index })

10.2 表冠 + 按键联动

手表侧边按键(onKeyEvent)与表冠配合,实现"旋转选择、按键确认"的标准手表交互模式:

Column().focusable(true).onDigitalCrown((event: CrownEvent)=>{// 表冠旋转:移动光标const sign = event.focusSensitivity >0?1:-1this.cursorIndex =(this.cursorIndex + sign +this.items.length)%this.items.length }).onKeyEvent((event: KeyEvent)=>{// 侧键按下:确认选择if(event.type === KeyType.Down && event.keyCode === KeyCode.KEYCODE_DPAD_CENTER){this.confirmedItem =this.items[this.cursorIndex]returntrue}returnfalse})
最佳实践:表冠负责「导航/调节」,侧键(物理按键)负责「确认/返回」,两者职责分离,符合手表 UX 设计规范。避免将确认操作放在表冠的旋转停止判断中,因为 CrownEvent 没有明确的 End 阶段信号。

总结

本文系统讲解了 HarmonyOS ArkUI 表冠事件 的完整知识体系,核心要点回顾:

  1. CrownEvent 四字段timestamp(时间戳)、deviceId(设备ID)、sensitivity(原始增量)、focusSensitivity(框架校准值)
  2. 两种灵敏度sensitivity 用于精密物理感知,focusSensitivity 用于常规 UI 驱动(推荐)
  3. 焦点是前提:容器组件必须设置 .focusable(true),入口组件建议加 .defaultFocus(true)
  4. Watch 专属onDigitalCrown 仅在 wearable 设备类型上生效,需配置 module.json5
  5. 四大场景:列表滚动(系数 80~120)、旋钮调节(系数 10~20)、图片缩放(系数 0.1~0.2)、时间选择(取符号)
  6. 协同使用:表冠负责导航/调节,侧键(onKeyEvent)负责确认,触摸作为兜底
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

Read more

零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体

零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体

零代码上手!用 Rokid 灵珠平台,5 步搭建专属旅游 AR 智能体 灵珠平台简介 okid 自研 AI 开发平台,基于多模态大模型与轻量化架构,打造零门槛、全栈化 AI 开发体系。平台提供可视化编排、预置能力组件,支持原型到云端、端侧一站式敏捷部署,并深度适配 Rokid Glasses 智能眼镜,通过专属硬件接口与低功耗优化,实现 AI 应用高效端侧落地,助力开发者快速打造视觉识别、语音交互等穿戴式 AI 应用,拓展 AI + 物理世界的交互边界可视化编排工具,拖拽式快速搭建应用预置丰富能力组件库,涵盖对话引擎、视觉识别等核心模块支持从原型设计到云端、端侧的一站式敏捷部署提供设备专属适配接口,实现硬件深度协同搭载低功耗运行优化方案,保障端侧持久稳定运行 实战:搭建旅游类AR智能体 1、进入灵珠平台 登录灵珠平台后,你将看到简洁直观的工作台界面 点击创建智能体按钮,

By Ne0inhk
2026最新秋叶绘世Stable Diffusion整合包下载 秋叶ComfyUI整合包下载 ai生图必备 绘世启动器.exe 绘世2.8.13下载 绘世启动器2.8.13下载地址

2026最新秋叶绘世Stable Diffusion整合包下载 秋叶ComfyUI整合包下载 ai生图必备 绘世启动器.exe 绘世2.8.13下载 绘世启动器2.8.13下载地址

2026最新秋叶绘世Stable Diffusion整合包下载 秋叶ComfyUI整合包下载 ai生图必备 绘世启动器.exe 绘世2.8.13下载 绘世启动器2.8.13下载地址 绘世2.8.13下载 | 绘世2.8.12下载 | 绘世启动器2.8.13下载地址 秋叶绘世Stable Diffusion整合包# 解压密码:bilibili-秋葉aaaki 【下载链接】 https://pan.quark.cn/s/41f42720f1c7?pwd=ZhBP 链接:https://pan.quark.cn/s/41f42720f1c7?pwd=ZhBP 提取码:ZhBP 解压密码:bilibili-秋葉aaaki 一定要用网盘官方客户端下载,否则压缩包极有可能损坏无法解压。下载完毕一定要先测试压缩包是否完好再解压!

By Ne0inhk
宇树机器人SDK2开发指南:从环境搭建到Demo测试

宇树机器人SDK2开发指南:从环境搭建到Demo测试

本文以宇树 G1 人形机器人为主线,系统介绍 unitree_sdk2(C++)与 unitree_sdk2_python(Python)的完整开发流程,涵盖通信架构原理、环境搭建、依赖安装、Demo 编译运行、网络配置以及常见问题处理,适合具身智能领域的初中级开发者快速上手。 目录 1. SDK2 概述与架构原理 2. 开发环境要求 3. 获取官方 SDK 包 4. 安装依赖与编译 5. 机器人与开发机网络配置 6. 调试并运行 Demo 7. Python SDK Demo 测试 8. 常见问题与解决方案 9. 总结 1. SDK2 概述与架构原理 1.

By Ne0inhk