👋 你好,欢迎来到我的博客!我是【菜鸟学鸿蒙】
   我是一名在路上的移动端开发者,正从传统“小码农”转向鸿蒙原生开发的进阶之旅。为了把学习过的知识沉淀下来,也为了和更多同路人互相启发,我决定把探索 HarmonyOS 的过程都记录在这里。
  
  🛠️ 主要方向:ArkTS 语言基础、HarmonyOS 原生应用(Stage 模型、UIAbility/ServiceAbility)、分布式能力与软总线、元服务/卡片、应用签名与上架、性能与内存优化、项目实战,以及 Android → 鸿蒙的迁移踩坑与复盘。
  🧭 内容节奏:从基础到实战——小示例拆解框架认知、专项优化手记、实战项目拆包、面试题思考与复盘,让每篇都有可落地的代码与方法论。
  💡 我相信:写作是把知识内化的过程,分享是让生态更繁荣的方式。
  
   如果你也想拥抱鸿蒙、热爱成长,欢迎关注我,一起交流进步!🚀

前言:动画不是“炫技”,是让用户觉得“这个 App 有灵魂”✨

我见过太多动画翻车现场:

  • 点一下按钮,界面“闪一下”就结束了(像没睡醒😪)
  • 列表滑动时还在做大范围重排动画(帧率当场跪下🧎)
  • 进场/退场动画写得像随机抽奖(用户都不敢点返回🙃)

你要记住一句大实话:

动画是“性能预算”里最容易超支的一项。
你越想“华丽”,越要“克制”。

1)属性动画 vs 转场动画:别混着用,不然你自己都绕晕

1.1 属性动画(Property Animation):让“某个属性”随时间变化

典型场景:

  • 按钮按下缩放(scale)
  • 透明度渐变(opacity)
  • 位移(translate)
  • 旋转(rotate)
  • 颜色渐变(color)
  • 高度/宽度变化(⚠️这个会牵扯布局,慎用)

✅ 优点:细粒度控制、交互反馈很爽
⚠️ 缺点:你一旦动画了“影响布局的属性”(比如 height/width/padding/margin),就可能触发频繁测量与布局,掉帧风险直线上升😤

1.2 转场动画(Transition):让“组件出现/消失/切换”更自然

典型场景:

  • 弹窗进出
  • 列表项插入/删除
  • 页面区域切换(加载态↔内容态)
  • 折叠面板展开/收起时内容进出

✅ 优点:表达结构变化更自然,用户不迷路
⚠️ 缺点:写不好会出现“突然闪现/突然消失”,像鬼片👻

2)Animation / Transition 使用:两条路线,别用错姿势

我这里用 ArkUI 常见的两类方式来讲:隐式动画(给属性挂 animation)显式动画(状态更新包在 animateTo)
写法在不同版本/模板会有细微差异,但思路完全通用。

2.1 隐式属性动画:最省心,但要管住手😅

隐式动画的核心就是:属性变了,框架替你补中间帧

@Entry
@Component
struct ImplicitAnimDemo {
  @State pressed: boolean = false

  build() {
    Column({ space: 16 }) {
      Text('隐式属性动画:按下会缩放+变淡')

      Button(this.pressed ? '松手~' : '按我一下')
        .scale({ x: this.pressed ? 0.96 : 1, y: this.pressed ? 0.96 : 1 })
        .opacity(this.pressed ? 0.75 : 1)
        .animation({
          duration: 180,
          curve: Curve.EaseOut
        })
        .onTouch((e) => {
          if (e.type === TouchType.Down) this.pressed = true
          if (e.type === TouchType.Up || e.type === TouchType.Cancel) this.pressed = false
        })
    }
    .padding(16)
  }
}

我个人的“暴躁提醒”😤:

  • 隐式动画别挂到“整个大容器”上(容易把不该动的也动了)
  • 别在列表项里对布局属性搞隐式动画(你会看到掉帧在向你招手👋)

2.2 显式属性动画:把“变化”包进动画块,更可控

显式动画适合:

  • 你想明确控制某次状态变化“必须动画”
  • 或想同一时刻协调多个属性一起动
@Entry
@Component
struct ExplicitAnimDemo {
  @State expanded: boolean = false

  build() {
    Column({ space: 12 }) {
      Text('显式动画:卡片展开/收起')

      Column() {
        Text('我是一张卡片')
          .fontSize(18)
          .margin({ bottom: 8 })

        if (this.expanded) {
          Text('展开后的内容:这里可以放描述、按钮、表单…')
            .opacity(0.85)
        }
      }
      .padding(12)
      .borderRadius(16)
      .height(this.expanded ? 180 : 88) // ⚠️高度变化会影响布局,注意范围别太大
      .backgroundColor(Color.White)
      .animation({ duration: 260, curve: Curve.EaseInOut })

      Button(this.expanded ? '收起' : '展开')
        .onClick(() => {
          // 思路:把状态变化集中发生(别在多处乱改)
          this.expanded = !this.expanded
        })
    }
    .padding(16)
    .backgroundColor(0xF6F7F9)
  }
}

2.3 Transition:组件进出场别“闪现”,要有仪式感✨

最经典的:加载态 → 内容态;错误态 → 重试态;弹层进出。

@Entry
@Component
struct TransitionDemo {
  @State showPanel: boolean = false

  build() {
    Column({ space: 16 }) {
      Button(this.showPanel ? '隐藏面板' : '显示面板')
        .onClick(() => this.showPanel = !this.showPanel)

      if (this.showPanel) {
        Column() {
          Text('我是一个进出场面板').fontSize(16)
          Text('别让我闪现好吗…').opacity(0.7)
        }
        .padding(12)
        .borderRadius(16)
        // 进入/退出用 transition(示意:位移+透明度)
        .transition({
          type: TransitionType.All,
          // 不同版本的字段名可能略不同,你按你工程的 Transition API 适配即可
        })
        .opacity(1)
      }
    }
    .padding(16)
  }
}

小技巧:转场尽量用 opacity/translate/scale 这类“合成友好”的属性,少动布局属性。

3)性能优化技巧:动画要顺,先学会“别作死”😅

3.1 优先动画“合成属性”,少动画“布局属性”

  • ✅ 推荐:opacityscaletranslaterotate
  • ⚠️ 谨慎:height/width/padding/margin(会触发布局测量与重排)

我的经验:

能用“缩放+透明度”做出来的效果,就别用“高度从 0 拉到 300”硬撑。
那不是动画,那是对渲染管线的精神攻击😤

3.2 限制动画影响范围:别一动就让整棵树重组

  • 把动画尽量放在小组件
  • 大页面拆分成多个子组件,减少重组面积
  • 列表里避免每一项都挂复杂动画(尤其是滚动时)

3.3 动画时别做重活:build 里别算大数据/别发网络

动画期间一旦主线程忙,帧就掉。掉帧一多,用户主观感受是:这个 App 不行(哪怕你功能写得再牛也白搭🙂)

3.4 状态更新要“节制”:高频状态变化=高频重组=帧率暴毙

  • 输入框联动动画别每次字符变化都触发全局动画
  • 拖拽跟手动画尽量用更轻的方式(必要时做节流)

4)复杂动画实战:做一个“点赞爆炸”按钮(缩放+旋转+粒子假象)🔥

来点能拿去装进项目的东西:一个点赞按钮,点击后:

  1. 按钮轻微缩放回弹
  2. 心形旋转一点点
  3. 周围冒出几颗“伪粒子”(用小圆点/小图标模拟,够用又省性能)

4.1 代码:LikeButton(可复用组件)

@Component
struct LikeButton {
  @State liked: boolean = false
  @State burst: boolean = false

  private triggerBurst() {
    this.burst = true
    // 粒子持续时间:和动画时长一致
    setTimeout(() => this.burst = false, 380)
  }

  build() {
    Stack() {
      // “伪粒子层”
      if (this.burst) {
        ForEach([0, 1, 2, 3, 4, 5], (i: number) => {
          // 6 个点往不同方向飞:用 rotate + translate 模拟
          Circle()
            .width(6)
            .height(6)
            .fill(Color.Red)
            .opacity(0.9)
            .translate({
              x: 0,
              y: -22
            })
            .rotate({ angle: i * 60 })
            .animation({ duration: 380, curve: Curve.EaseOut })
        })
      }

      // 主按钮
      Text(this.liked ? '❤️' : '🤍')
        .fontSize(34)
        .rotate({ angle: this.liked ? -8 : 0 })
        .scale({ x: this.liked ? 1.12 : 1, y: this.liked ? 1.12 : 1 })
        .animation({
          duration: 220,
          curve: this.liked ? Curve.Spring : Curve.EaseOut
        })
    }
    .width(64)
    .height(64)
    .borderRadius(32)
    .backgroundColor(0xFFFFFF)
    .onClick(() => {
      // 状态切换 + 触发 burst
      this.liked = !this.liked
      if (this.liked) this.triggerBurst()
    })
  }
}

4.2 使用方式:直接塞进页面

@Entry
@Component
struct LikeDemoPage {
  build() {
    Column({ space: 16 }) {
      Text('复杂动画实战:点赞爆炸(但别真把帧率炸了🤣)')
        .fontSize(16)

      LikeButton()

      Text('写动画的底线:好看、可控、不卡。')
        .opacity(0.7)
    }
    .padding(16)
    .backgroundColor(0xF6F7F9)
  }
}

这套为什么“看起来复杂但不太吃性能”?

  • 主要用 scale/rotate/translate/opacity(合成属性友好)
  • 粒子数量固定少(6 个)
  • 生命周期短(380ms 自动消失)
  • 没有在动画里触发布局重排的大范围变化

收尾:动画写到最后,拼的不是花活,是“克制”😌

你如果只记住一件事:

把动画当成“用户体验的语气词”,而不是“性能的敌人”。
轻一点、准一点、少一点,反而更高级✨

📝 写在最后

如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!

我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!

感谢你的阅读,我们下篇文章再见~👋

✍️ 作者:某个被流“治愈”过的 移动端 老兵
📅 日期:2025-11-05
🧵 本文原创,转载请注明出处。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐