HarmonyOS 6学习:PanGesture手势驱动月亮半圆轨迹“滚动”术

在HarmonyOS 6的天气或日历应用中,常需要实现"月亮随手指在半圆形轨迹上滑动"的交互效果。开发者常踩的坑是:手势识别了,但月亮图片死活不动,只有内部的数值在变 。这并非手势API的Bug,而是**"手势事件"与"组件位移"未建立数学映射** 。本文将彻底解析PanGesture的坐标转换逻辑,通过**"角度-弧度-坐标"**公式,实现真正的像素级跟随。

一、月亮"不动"的根因:事件与UI的断联

1. 问题现场:为何只有数字在变?

场景复现 :开发者使用PanGesture监听滑动,在onActionUpdate中直接修改@State变量来更新月亮位置。

预期效果 实际效果 技术现象
手指滑动,月亮沿半圆轨迹平滑滚动 ❌ 月亮原地不动,仅下方的日期/数值变化 console打印显示手势偏移量正常,但UI未响应

错误代码示例

复制代码
// 错误示例:仅更新了数据,未更新月亮位置
@State currentAngle: number = 0; // 角度

PanGesture(this.panOption)
  .onActionUpdate((event: GestureEvent) => {
    this.currentAngle = event.offsetX / 2; // 仅修改了角度状态
    this.updateDateByAngle(); // 更新了数字
  })

2. 根因揭秘:PanGesture的"相对性"与"绝对性"

核心机制PanGesture返回的offsetX/Y相对于手势起点的偏移量 ,而非画布上的绝对坐标

坐标类型 含义 适用场景
event.offsetX onActionStart开始的累计偏移量 拖动滑块、自由拖拽
月亮位置 需要画布上的绝对坐标​ (x, y) 固定轨迹的跟随

冲突过程

  1. 手指滑动,offsetX正确变化。

  2. 代码仅更新了角度状态currentAngle

  3. 月亮图片的position属性未与currentAngle绑定,导致UI无法刷新。

二、解决方案:极坐标转换公式

1. 核心思路:角度 → 坐标

要让月亮沿半圆运动,必须将手势偏移量 转换为角度 ,再通过极坐标公式计算出月亮的绝对坐标

极坐标公式

复制代码
// 已知圆心 (centerX, centerY),半径 radius,角度 angle
x = centerX + radius * Math.cos(angle * Math.PI / 180);
y = centerY + radius * Math.sin(angle * Math.PI / 180);

2. 完整实现:ETS版月亮滑动

关键点 :使用@State同时管理角度与坐标,并在onActionUpdate中实时计算。

复制代码
import display from '@ohos.display';

@Entry
@Component
struct MoonPhaseSlider {
  // 1. 状态管理:角度 + 坐标
  @State currentAngle: number = 0; // 角度(-90° 到 90°)
  @State moonX: number = 0;
  @State moonY: number = 0;

  // 2. 几何参数(需在aboutToAppear中初始化)
  private centerX: number = 0; // 圆心X(屏幕一半)
  private centerY: number = 200; // 圆心Y
  private radius: number = 150; // 半圆半径

  // 3. 手势配置
  private panOption: PanGestureOptions = {
    distance: 5 // 触发距离阈值
  };

  aboutToAppear() {
    // 获取屏幕宽度,计算圆心
    let displayClass = display.getDefaultDisplaySync();
    this.centerX = displayClass.width / 2;
    
    // 初始化月亮位置(起点:最左侧,角度-90°)
    this.currentAngle = -90;
    this.updateMoonPosition();
  }

  // 4. 核心:根据角度更新坐标
  updateMoonPosition() {
    // 角度转弧度
    let radian = this.currentAngle * Math.PI / 180;
    
    // 极坐标公式计算
    this.moonX = this.centerX + this.radius * Math.cos(radian);
    this.moonY = this.centerY + this.radius * Math.sin(radian);
  }

  build() {
    Column() {
      // 5. 月亮图片(必须绑定动态坐标)
      Image($r('app.media.moon'))
        .width(60)
        .height(60)
        .position({ x: this.moonX, y: this.moonY }) // ✅ 关键:绑定State

      // 6. 手势区域(透明覆盖层)
      Blank()
        .height(300)
        .width('100%')
        .backgroundColor(Color.Transparent)
        .gesture(
          PanGesture(this.panOption)
            .onActionUpdate((event: GestureEvent) => {
              // 7. 根据手势偏移量计算新角度
              let newAngle = this.currentAngle + event.offsetX / 5; // 除以5降低灵敏度
              
              // 限制角度范围(-90° 到 90°)
              newAngle = Math.max(-90, Math.min(90, newAngle));
              
              // 8. 更新状态(触发UI刷新)
              this.currentAngle = newAngle;
              this.updateMoonPosition(); // 必须调用!
              
              // 同步更新日期
              this.updateDateByAngle(newAngle);
            })
        )
    }
  }
}

3. 避坑指南:月亮滑动的"三必须"

步骤 关键操作 缺失后果
坐标绑定 position属性必须绑定@State变量 月亮不动
实时计算 onActionUpdate中必须调用updateMoonPosition 坐标不更新
范围限制 角度必须限制在[-90, 90] 月亮飞出轨迹

三、进阶:贝塞尔曲线与物理惯性

1. 平滑轨迹:二次贝塞尔曲线

如果希望月亮滑动更"丝滑",可使用Curve动画插值,而非直接设置坐标。

复制代码
// 在updateMoonPosition中使用动画
animateTo({
  duration: 100,
  curve: Curve.EaseOut
}, () => {
  this.moonX = /* 计算值 */;
  this.moonY = /* 计算值 */;
})

2. 物理惯性:onActionEnd

在手势结束时,根据event.velocity(速度)模拟惯性滑动,提升体验。

复制代码
PanGesture(this.panOption)
  .onActionEnd((event: GestureEvent) => {
    // 根据event.velocityX计算惯性滑动的距离
    let inertiaDistance = event.velocityX * 0.1;
    // ...继续更新角度和位置
  })

四、总结:手势驱动的"映射"法则

  1. PanGesture返回的是偏移量offsetX/Y是相对于起点的差值,不能直接 赋值给position

  2. 必须建立数学映射 :通过极坐标公式将手势偏移量转换为画布上的绝对坐标。

  3. UI刷新机制 :修改@State变量后,必须显式调用坐标更新函数,ArkUI不会自动推导。

通过这套"角度-坐标"转换公式,你的月亮图片将彻底告别"原地踏步",实现真正的半圆轨迹跟随

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任。

相关推荐
叶~小兮2 小时前
K8S进阶核心综合学习笔记(持久化存储+特殊容器+调度管理)
笔记·学习·kubernetes
li星野2 小时前
位运算 & 数学 & 高频进阶九题通关(Python + C++)
c++·python·学习·算法
y = xⁿ3 小时前
Java并发八股学习日记
java·开发语言·学习
@codercjw4 小时前
工程图制图经验
学习
星幻元宇VR4 小时前
VR文旅大空间|沉浸式体验重塑文旅新场景
科技·学习·安全·vr·虚拟现实
Cat_Rocky5 小时前
K8S-Helm简单学习分享
学习·容器·kubernetes
NNYSJYKJ5 小时前
告别刷题无效与偏科:脑能模型解锁 K12 学习底层能力构建
学习
凌云若寒5 小时前
BarTender许可 | 关于PDF打印数量说明
学习·pdf·产品经理·制造·软件需求
一只机电自动化菜鸟5 小时前
一建机电备考笔记(36) 焊接技术—焊接方法与工艺(含考频+题型)
笔记·学习·职场和发展·生活·学习方法