HarmonyOS7 显式动画与手势:拖拽回弹效果实现

文章目录

前言

"拖一下松手,东西自己弹回原位"------这种交互在你每天用的 app 里到处都是:下拉刷新、卡片左滑删除、悬浮球归边。

它其实是"手势 + 动画"的组合拳。这篇文章我带你从零写一个可拖拽、松手回弹 的小方块,把 PanGestureanimateTo 怎么配合讲透。代码不多,但套路是通用的。

先认识 PanGesture

PanGesture 是"拖拽手势",监听手指在屏幕上滑动。它有几个回调:开始拖、拖的过程中、松手。

代码实现

typescript 复制代码
@Local offsetX: number = 0
@Local offsetY: number = 0

build() {
  Stack() {
    Circle({ width: 60, height: 60 })
      .fill('#4A90E2')
      .offset({ x: this.offsetX, y: this.offsetY })   // 用 offset 控制位置
      .gesture(
        PanGesture()
          .onActionStart(() => { /* 手指按下开始拖 */ })
          .onActionUpdate((e) => {
            this.offsetX = e.offsetX   // 实时跟随手指
            this.offsetY = e.offsetY
          })
          .onActionEnd(() => { this.backToOrigin() })
      )
  }
  .width('100%')
  .height('100%')
}

逐行拆解:

  • .offset({ x, y }):给组件一个偏移量,正数向右/下。我们用它来"移动"方块,而不是改布局。
  • PanGesture().onActionUpdate((e) => ...)e.offsetX 是相对拖动起点的位移。把它赋给 offsetX,方块就实时跟着手指走。
  • .onActionEnd(...):手指松开时触发,在这里让方块弹回。

关键点:拖动时不要包 animateTo,否则会有延迟感。只有松手回弹时才需要动画。这是新手最容易搞反的地方。

松手回弹:animateTo 上场

typescript 复制代码
backToOrigin() {
  animateTo({ duration: 300, curve: Curve.Spring }, () => {
    this.offsetX = 0
    this.offsetY = 0
  })
}

松手后,用弹簧曲线把 offsetX/Y 动画回 0,方块就"嗖"地弹回起点。弹簧曲线让回弹有轻微过冲,手感更真实。

进阶:拖到边缘就吸附

很多场景不是弹回,而是"拖过一半就飞走,没过一半弹回"。加个判断:

核心代码

typescript 复制代码
.onActionEnd(() => {
  if (this.offsetX > 150) {
    // 拖够了,飞出屏幕右侧
    animateTo({ duration: 250 }, () => { this.offsetX = 400 })
  } else {
    // 没拖够,弹回
    animateTo({ duration: 250, curve: Curve.Spring }, () => { this.offsetX = 0 })
  }
})

offsetX > 150 是你自己定的"阈值",代表"用户拖得够远了,算他确认操作"。这种"阈值 + 二选一动画"的模式,在滑动删除、底部弹窗 dismiss 里都用得到。

一个完整可跑的例子

typescript 复制代码
@Entry
@ComponentV2
struct DragDemo {
  @Local x: number = 0
  @Local y: number = 0

  build() {
    Stack() {
      Circle({ width: 80, height: 80 })
        .fill('#FF6B6B')
        .offset({ x: this.x, y: this.y })
        .gesture(
          PanGesture()
            .onActionUpdate((e: GestureEvent) => {
              this.x = e.offsetX
              this.y = e.offsetY
            })
            .onActionEnd(() => {
              animateTo({ duration: 400, curve: curves.springMotion(0.5, 0.9) }, () => {
                this.x = 0
                this.y = 0
              })
            })
        )
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

跑起来:按住红圆拖动,松手它带弹性地归位。这就是所有"拖拽回弹"交互的最小骨架。

常见误区(小白必踩)

误区 说明
拖动时也包 animateTo 会导致跟手有延迟感。记住:拖动过程实时跟手(不动画),只有松手回弹才动画。
松手不回弹 onActionEnd 里要用 animateTo 把位置动画回原点或用弹簧曲线弹回。
用布局属性代替 offset 拖拽用 .offset() 控制偏移最顺,改 margin/position 反而麻烦。

下面这段代码可以直接复制到 DevEco Studio 里运行。建议你边读边敲,改一改文末「动手改一改」里的参数,亲眼看看效果。

完整可运行示例:拖拽回弹小球

显式动画配合手势,做一个「拖出去松手弹回」的跟手效果。

完整示例

typescript 复制代码
// PanGesture 跟手拖动 + animateTo 松手回弹
@Entry
@Component
struct DragBall {
  @State offsetX: number = 0
  @State offsetY: number = 0

  build() {
    Stack() {
      Circle({ width: 80, height: 80 })
        .fill('#007DFF')
        .translate({ x: this.offsetX, y: this.offsetY })
        .gesture(
          PanGesture()
            .onActionUpdate((e) => {     // 手指移动时实时跟手
              this.offsetX = e.offsetX
              this.offsetY = e.offsetY
            })
            .onActionEnd(() => {         // 松手:弹回原点
              animateTo({ duration: 400, curve: Curve.Spring }, () => {
                this.offsetX = 0
                this.offsetY = 0
              })
            })
        )
    }
    .width('100%').height('100%')
  }
}

你会看到什么:一个蓝色圆球。手指按住拖动,圆球实时跟随手指;松手后弹回中心位置,带弹簧回弹手感。

动手改一改

  • onActionEnd 里的目标改成 offsetX = e.offsetX(需要缓存),做成「拖到哪停哪」。
  • Curve.Spring 改成 Curve.Friction,对比回弹与摩擦停下的差异。
  • 给圆球加 .scale({ x: 1.1, y: 1.1 }),拖拽时放大,松手还原。

写在最后

手势 + 动画的套路就三步:手势里改位置(实时跟手)、松手时判断、用 animateTo 做过渡。记住"拖动不动画、回弹才动画",手感立刻专业。

建议你把阈值判断那段加上,体验一下"拖够飞走、没拖够弹回"------理解了这个,滑动删除你就能自己写出来了。

相关推荐
不才难以繁此生21 小时前
HarmonyOS RDB 索引实战:中式美食搜索和购物清单查询怎么避免扫全表
harmonyos·arkts·arkui·rdb·中式美食·relationalstore
大师兄66681 天前
HarmonyOS7 module.json5 配置全解:Ability 注册与权限
arkui·实战案例·harmonyos7
不才难以繁此生1 天前
ArkUI 筛选状态实战:中式美食列表页怎么让关键词、分类和排序不互相打架
harmonyos·arkts·状态管理·arkui·中式美食
不才难以繁此生3 天前
HarmonyOS ArkUI NavPathStack 实战:中式美食详情页多入口返回栈怎么设计
harmonyos·arkts·arkui·navigation·中式美食·navpathstack·路由栈
不才难以繁此生3 天前
HarmonyOS ResourceStr 实战:中式美食菜谱封面怎么统一注册、兜底和缓存
harmonyos·arkts·image·arkui·中式美食·resourcestr·图片缓存
不才难以繁此生4 天前
HarmonyOS 端侧应用实战:中式美食如何把资源、路由、存储和视频拆菜串成一条工程链路
harmonyos·arkts·arkui·中式美食·端侧应用
不才难以繁此生4 天前
ArkUI 列表性能实战|中式美食菜谱列表如何拆组件、控重绘和稳住滑动体验
harmonyos·arkts·状态管理·foreach·arkui·列表性能·组件拆分
TrisighT13 天前
ArkTS 的 @BuilderParam 你八成只用了皮毛——那个尾随闭包写法差点被我当 bug 删了
harmonyos·arkts·arkui
TrisighT14 天前
ArkTS 列表滚动时为什么会闪现旧数据?我扒了 LazyForEach 的复用逻辑
harmonyos·arkts·arkui