智感握姿——动作感知实现流程操作指南
一、效果说明
通过 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. 响应式数据模型(核心设计)
@ObservedV2
class GripState {
// @Trace:仅 status 字段变化时触发依赖它的组件最小范围重渲染
@Trace status: motion.HoldingHandStatus = motion.HoldingHandStatus.UNKNOWN_STATUS;
// @Computed:status 不变则不重新计算,结果自动缓存
@Computed
get actionAlign(): HorizontalAlign {
if (this.status === motion.HoldingHandStatus.LEFT_HAND_HELD) {
return HorizontalAlign.Start;
}
if (this.status === motion.HoldingHandStatus.RIGHT_HAND_HELD) {
return HorizontalAlign.End;
}
return HorizontalAlign.Center;
}
@Computed
get fabAlign(): HorizontalAlign {
if (this.status === motion.HoldingHandStatus.LEFT_HAND_HELD) {
return HorizontalAlign.Start;
}
return HorizontalAlign.End; // 右手/双手/未握持均靠右
}
}
为什么用
@ObservedV2而不是@Observed(V1)?
项目基于 HarmonyOS 6.1(API 23),全面采用 State Management V2。@Trace提供字段级细粒度追踪,只有status改变时才重渲染,避免整对象比较的额外开销。
4. 订阅与取消订阅(生命周期配对)
// ① 将回调保存为类成员,保证 on/off 操作的是同一个函数引用
private readonly gripCallback: (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 变化自动穿透刷新
@ComponentV2
struct 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或手动通知。
更多推荐


所有评论(0)