HarmonyOS鸿蒙Next中怎么实现类似“智感握姿”左右弹出按钮的动画?

HarmonyOS鸿蒙Next中怎么实现类似“智感握姿”左右弹出按钮的动画? 虽然系统开放了左手和右手的握持检测的API,但还需要实现按钮从一侧消失,再从另一侧出现的动画,该如何实现?

cke_5326.png


更多关于HarmonyOS鸿蒙Next中怎么实现类似“智感握姿”左右弹出按钮的动画?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

真机效果图:

cke_156.gif

动画实现思路:

检测到切换手握持后,先让控件消失,然后在动画结束时将控件切换到另一边,最后让控件出现。

完整代码:

需要先在 entry/src/main/module.json5 下配置 ohos.permission.DETECT_GESTURE 权限并向用户申请,具体可看:怎么实现 Mate80 系列同款握持检测?

// 从鸿蒙多模态感知 Kit 中引入 motion 模块,用于监听握持状态
import { motion } from '@kit.MultimodalAwarenessKit';

@Entry
@ComponentV2
struct FloatComponent {
  /* ========================= 状态变量 ========================= */
  // 控制悬浮按钮是否可见
  @Local isVisible: boolean = true;
  // 控制悬浮按钮在左侧还是右侧
  @Local isPositionedLeft: boolean = true;

  /* ========================= 握持状态缓存 ========================= */
  // 上一次握持状态(防抖用)
  private oldHandStatus = motion.HoldingHandStatus.NOT_HELD;
  // 最新一次握持状态(防抖用)
  private newHandStatus = motion.HoldingHandStatus.NOT_HELD;
  // 防抖锁:true 表示可以响应新事件,false 表示正在防抖中
  private isDebouncing: boolean = true;

  /* ========================= 防抖 & 切换逻辑 ========================= */
  /**
   * 真正执行业务逻辑的函数:
   * 1. 隐藏按钮(500ms动画)
   * 2. 切换左右位置
   * 3. 重新显示按钮(500ms动画)
   * 4. 1.5s防抖:期间若状态再次变化则递归调用,否则释放防抖锁
   */
  private processHandHeldChange = (data: motion.HoldingHandStatus) => {
    // 1.5s后检查:如果期间状态又变了,则继续递归防抖;否则释放锁
    setTimeout(() => {
      if (this.newHandStatus != this.oldHandStatus) {
        this.oldHandStatus = this.newHandStatus;
        this.processHandHeldChange(data);
      } else {
        this.isDebouncing = true; // 释放防抖锁
      }
    }, 1500);

    // 立即隐藏按钮
    this.isVisible = false;

    // 500ms后切换左右并重新显示(与隐藏动画衔接)
    setTimeout(() => {
      this.isPositionedLeft = !this.isPositionedLeft;
      this.isVisible = true;
    }, 500);
  };

  /* ========================= 握持事件回调 ========================= */
  /**
   * 监听“握持手改变”事件:
   * - 若状态无变化则直接返回
   * - 仅当左手或右手握持时才触发业务逻辑
   * - 通过防抖锁避免1.5s内重复触发
   */
  private onHandStatusChanged: Callback<motion.HoldingHandStatus> = (
    data: motion.HoldingHandStatus
  ) => {
    // 状态未变,无需处理
    if (this.oldHandStatus == data) return;

    // 只关心“左手握持”或“右手握持”
    if (
      data == motion.HoldingHandStatus.LEFT_HAND_HELD ||
        data == motion.HoldingHandStatus.RIGHT_HAND_HELD
    ) {
      this.newHandStatus = data;
      if (!this.isDebouncing) return; // 防抖中,直接丢弃
      this.isDebouncing = false; // 加锁
      this.oldHandStatus = data; // 更新缓存
      this.processHandHeldChange(data); // 执行业务
    }
  };

  /* ========================= 生命周期 ========================= */
  aboutToAppear(): void {
    try {
      // 注册握持状态监听
      motion.on('holdingHandChanged', this.onHandStatusChanged);
      console.info('on succeeded');
    } catch (e) {
      console.error('Failed on; err code = ' + e.code);
    }
  }

  aboutToDisappear(): void {
    try {
      // 页面销毁时移除监听,防止内存泄漏
      motion.off('holdingHandChanged'); // 移除所有同类订阅
      console.info('off succeeded');
    } catch (e) {
      console.error('Failed off; err code = ' + e.code);
    }
  }

  /* ========================= UI ========================= */
  build() {
    // 根据 isPositionedLeft 决定按钮在左还是右
    Stack({
      alignContent: this.isPositionedLeft ? Alignment.Start : Alignment.End,
    }) {
      // 仅当可见时才渲染按钮
      if (this.isVisible) {
        Button({ type: ButtonType.Circle, stateEffect: true }) {
          SymbolGlyph($r('sys.symbol.camera')); // 相机图标
        }
        .borderRadius(16)
        .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
        .size({ width: 40, height: 40 })
        .backgroundColor($r('sys.color.multi_color_aux_03'))
        .margin(20)
        // 切换左右时的滑入动画
        .transition(
          TransitionEffect.move(
            this.isPositionedLeft ? TransitionEdge.START : TransitionEdge.END
          ).animation({ duration: 500, curve: Curve.Ease })
        );
      }
    }
    .width('100%')
    .height('100%');
  }
}

更多关于HarmonyOS鸿蒙Next中怎么实现类似“智感握姿”左右弹出按钮的动画?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,可通过ArkUI的TouchTarget组件和Gesture手势事件监听握姿状态。使用animateTo或显式动画(如KeyframeAnimation)定义按钮从屏幕侧边滑入的位移与透明度变化。结合ColumnRow布局的offset属性控制位置,并利用状态变量(@State)触发动画。

在HarmonyOS Next中实现“智感握姿”的左右弹出按钮动画,可以结合系统提供的握持检测API与ArkUI的动画能力。核心思路是:监听握持状态变化,并控制按钮组件的显隐与位移动画。

以下是关键步骤和代码示例:

  1. 获取握持状态:使用display.getDefaultDisplay().getHoldingPosture()获取当前握持状态(左手、右手或无)。
  2. 定义动画状态:为按钮组件(例如RowColumn)设置初始位置(如左侧屏幕外)。通过状态变量(如isLeftHand)控制目标位置。
  3. 应用位移动画:使用ArkUI的显式动画(如animateTo)或属性动画(如animation属性),在握持状态改变时,平滑地将按钮从一侧移出屏幕,再移入另一侧。

示例代码框架

import { display } from '@kit.ArkUI';

@Entry
@Component
struct SmartButton {
  @State isLeftHand: boolean = false; // 握持状态
  @State buttonOffset: number = -200; // 按钮水平偏移,初始在左侧外

  aboutToAppear() {
    // 监听握持状态变化
    display.getDefaultDisplay().on('holdingPostureChange', (posture: display.HoldingPosture) => {
      this.isLeftHand = (posture === display.HoldingPosture.LEFT_HAND);
      this.updateButtonPosition();
    });
  }

  updateButtonPosition() {
    // 根据握持方向计算目标偏移量
    let targetOffset = this.isLeftHand ? 20 : -200; // 示例值:左手时右侧显示,右手时左侧显示
    animateTo({ duration: 300, curve: Curve.EaseOut }, () => {
      this.buttonOffset = targetOffset;
    });
  }

  build() {
    Column() {
      // 按钮组件,通过offset调整水平位置
      Button('Action')
        .offset({ x: this.buttonOffset })
    }
    .width('100%')
    .height('100%')
  }
}

关键点

  • 使用offsettranslate属性控制按钮位置,配合动画实现平滑移动。
  • 动画曲线(如Curve.EaseOut)可提升视觉效果。
  • 需在aboutToAppear中注册握持状态监听,并在aboutToDisappear中取消监听以释放资源。

此方法通过响应握持状态变化,触发组件位移动画,实现了按钮在屏幕左右侧的智能切换效果。

回到顶部