一、效果说明
通过MultimodalAwarenessKit订阅用户握持手机的姿态,实时感知四种状态并驱动 UI 布局自适应:
| 握姿 | 快捷操作栏(ActionBar) | FAB 悬浮按钮 |
|---|---|---|
| 左手握持 | 靠左对齐 | 移至左侧 |
| 右手握持 | 靠右对齐 | 移至右侧 |
| 双手握持 | 居中对齐 | 移至右侧 |
| 未握持 / 未知 | 居中对齐 | 移至右侧 |
二、实现流程
Step 1 声明权限 → module.json5 ↓ Step 2 导入模块 → import { motion } from '@kit.MultimodalAwarenessKit' ↓ Step 3 定义响应式数据模型 → @ObservedV2 class GripState 用 @Trace 追踪 status,@Computed 派生布局属性 ↓ Step 4 页面生命周期中订阅/取消订阅 aboutToAppear → motion.on('holdingHandChanged', callback) aboutToDisappear → motion.off('holdingHandChanged', callback) ↓ Step 5 回调中赋值 grip.status,@Trace 触发最小范围重渲染 ↓ Step 6 UI 组件读取 @Computed 派生的布局属性(actionAlign / fabAlign) 自动完成对齐切换 + 过渡动画三、关键代码讲解
1. 权限声明(module.json5)
"requestPermissions": [ { "name": "ohos.permission.DETECT_GESTURE", "reason": "$string:permission_detect_gesture_reason", "usedScene": { "abilities": ["EntryAbility"], "when": "inuse" // 仅前台使用期间需要 } } ]注意权限选择:
holdingHandChanged(四状态,含双手)→ 必须使用ohos.permission.DETECT_GESTUREoperatingHandChanged(仅左/右,API 15+)→ 可用ohos.permission.ACTIVITY_MOTION两者均为
user_grant权限,首次运行时系统自动弹出授权弹框。
2. 导入模块
import{motion}from'@kit.MultimodalAwarenessKit';3. 响应式数据模型(核心设计)
@ObservedV2classGripState{// @Trace:仅 status 字段变化时触发依赖它的组件最小范围重渲染@Tracestatus:motion.HoldingHandStatus=motion.HoldingHandStatus.UNKNOWN_STATUS;// @Computed:status 不变则不重新计算,结果自动缓存@ComputedgetactionAlign():HorizontalAlign{if(this.status===motion.HoldingHandStatus.LEFT_HAND_HELD){returnHorizontalAlign.Start;}if(this.status===motion.HoldingHandStatus.RIGHT_HAND_HELD){returnHorizontalAlign.End;}returnHorizontalAlign.Center;}@ComputedgetfabAlign():HorizontalAlign{if(this.status===motion.HoldingHandStatus.LEFT_HAND_HELD){returnHorizontalAlign.Start;}returnHorizontalAlign.End;// 右手/双手/未握持均靠右}}为什么用
@ObservedV2而不是@Observed(V1)?
项目基于 HarmonyOS 6.1(API 23),全面采用 State Management V2。@Trace提供字段级细粒度追踪,只有status改变时才重渲染,避免整对象比较的额外开销。
4. 订阅与取消订阅(生命周期配对)
// ① 将回调保存为类成员,保证 on/off 操作的是同一个函数引用privatereadonlygripCallback:(status:motion.HoldingHandStatus)=>void=(status:motion.HoldingHandStatus):void=>{this.grip.status=status;// 赋值即触发 @Trace 响应链};// ② 页面出现时订阅aboutToAppear():void{motion.on('holdingHandChanged',this.gripCallback);}// ③ 页面销毁时精确取消,传入同一引用aboutToDisappear():void{motion.off('holdingHandChanged',this.gripCallback);}两个关键细节:
gripCallback必须保存引用,不能写成motion.off('holdingHandChanged')无参形式——无参会移除该事件的所有订阅者,在多组件场景下会误伤其他监听。aboutToAppear/aboutToDisappear必须成对,否则页面销毁后回调仍执行,向已销毁的状态对象写值,引发崩溃。
5. 快捷操作栏对齐(ActionBar组件)
// @Param 接收父级传入的对齐方向,@Trace 变化自动穿透刷新@ComponentV2struct ActionBar{@Param_align:HorizontalAlign=HorizontalAlign.Center;build(){Flex({justifyContent:this._align===HorizontalAlign.Start?FlexAlign.Start:this._align===HorizontalAlign.End?FlexAlign.End:FlexAlign.Center}){...}.animation({duration:300,curve:Curve.EaseInOut})// 对齐切换过渡}}// 父页面传入ActionBar({_align:this.grip.actionAlign})
Flex的justifyContent是布局属性,改变时子元素真实移动,并触发.animation()驱动的过渡动效。
6. FAB 悬浮按钮随握姿移动
Column(){this.fab()// FAB 自身不需要任何 offset}.width('100%').padding({bottom:32,left:24,right:24}).alignItems(this.grip.fabAlign)// 布局属性,真实改变位置.animation({duration:300,curve:Curve.EaseInOut})// 动画加在容器上
offsetvsalignItems的本质区别:offset在渲染阶段做视觉位移,不参与布局计算,父容器不感知子元素位置变化,动画效果也依赖子元素自身携带.animation()。alignItems是布局阶段属性,直接决定子元素的排列起点,改变时布局重新计算,容器上的.animation()即可完整驱动过渡。
四、注意事项
真机运行:握姿感知依赖设备内置传感器,模拟器不会触发
holdingHandChanged回调,需在支持该能力的 HarmonyOS 真机上验证。API 级别:
holdingHandChanged事件及HoldingHandStatus枚举从API 20起支持,项目compileSdkVersion为6.1.0(23),满足要求。设备能力检测:不支持握姿感知的设备,
motion.on(...)会抛出BusinessError(801),代码中已在catch块捕获并通过errorBanner展示,不会白屏崩溃。数据流方向:父页面用
@Local grip持有GripState实例,子组件通过@Param grip接收引用,@Trace字段变化会自动穿透刷新所有依赖该字段的子组件,无需额外@Watch或手动通知。