欢迎加入开源鸿蒙跨平台社区

你好!👋 欢迎来到这篇关于 animations 在 HarmonyOS (OpenHarmony) 上使用的实战教程。当前已经完成鸿蒙化适配的Flutter三方库均可在flutter_packages仓库下查看。
本文将手把手带你实现Material Design 过渡动画,让你的应用动效更加流畅优雅!🎬


📦 1. 引入依赖:pubspec.yaml

首先,我们需要在项目的配置文件中引入适配后的 animations 库。

修改文件pubspec.yaml

操作:在 dependencies 节点下添加 animations 的 git 依赖配置。

dependencies:
  flutter:
    sdk: flutter
  
  # 👇 新增:引入 animations 鸿蒙适配版本
  animations:
    git: 
      url: https://gitcode.com/openharmony-tpc/flutter_packages.git
      path: packages/animations
      ref: br_animations-v2.0.11_ohos

💡 提示:修改完成后,别忘了运行终端命令 flutter pub get 来下载依赖!

🎯 为什么选择 animations 库?

animations 是 Flutter 官方提供的 Material Design 过渡动画库,它封装了多种高质量的过渡效果。

与手写动画代码相比,animations 就像是一个 🎨 专业动画工具箱,让你无需从零编写复杂的动画控制器和过渡逻辑,直接使用现成的动画组件即可!

核心优势:

  • 符合 Material Design 规范:遵循官方设计指南
  • 开箱即用:无需编写动画控制器代码
  • 高性能:经过优化的动画实现
  • 易于定制:支持丰富的配置参数

👶 新手小课堂:animations 是什么?

animations 包提供了一系列预定义的 Material Design 过渡动画组件

想象一下,你需要实现一个从卡片展开到详情页的动画效果。传统方式需要:

  1. 创建 AnimationController
  2. 定义 Tween 动画
  3. 监听动画状态
  4. 处理手势交互
  5. 管理多个动画的协调

而使用 animations 库,只需要一个 OpenContainer 组件就能实现!就像使用 🎬 电影特效模板,点击应用就能获得专业级动画效果!

主要组件:

  • 🎭 OpenContainer:容器展开/收起动画
  • 🔄 PageTransitionSwitcher:页面切换动画容器
  • ↔️ SharedAxisTransition:共享轴过渡动画
  • 🌫️ FadeThroughTransition:淡入淡出过渡动画

🎬 2. 核心动画组件详解

2.1 🎭 OpenContainer - 容器变换动画

OpenContainer 实现了从一个"关闭"状态的容器到"打开"状态的全屏页面的过渡动画。

📊 OpenContainer 工作流程:

fade

fadeThrough

👆 用户点击容器

🎬 开始过渡动画

选择过渡类型

🌫️ 淡入效果

✨ 淡入淡出效果

📱 展开到详情页

🖼️ 显示完整内容

👈 返回手势/按钮

🔄 反向播放动画

📦 收起到原始容器

核心代码实现:

import 'package:animations/animations.dart';

OpenContainer(
  transitionType: ContainerTransitionType.fade, // 过渡类型
  transitionDuration: const Duration(milliseconds: 500), // 动画时长
  closedElevation: 4, // 关闭状态阴影
  closedShape: RoundedRectangleBorder( // 关闭状态形状
    borderRadius: BorderRadius.circular(12),
  ),
  closedColor: Colors.blue.shade100, // 关闭状态颜色
  openColor: Colors.white, // 打开状态颜色
  closedBuilder: (context, action) {
    // 👇 关闭状态的UI(列表项、卡片等)
    return Container(
      height: 120,
      padding: const EdgeInsets.all(20),
      child: const Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.image, size: 40, color: Colors.blue),
          SizedBox(height: 8),
          Text('点击查看详情'),
        ],
      ),
    );
  },
  openBuilder: (context, action) {
    // 👇 打开状态的UI(详情页)
    return Scaffold(
      appBar: AppBar(title: const Text('详情页')),
      body: const Center(child: Text('这是详情内容')),
    );
  },
)

参数说明:

  • 🎨 transitionType:过渡类型(fadefadeThrough
  • ⏱️ transitionDuration:动画持续时间
  • 📦 closedBuilder:关闭状态的构建器
  • 🖼️ openBuilder:打开状态的构建器
  • 🎯 closedElevation:关闭状态的阴影高度
  • 🔲 closedShape:关闭状态的形状

2.2 🔄 PageTransitionSwitcher - 页面切换动画

PageTransitionSwitcher 用于在多个子组件之间切换时提供平滑的过渡动画。

📊 PageTransitionSwitcher 切换流程:

key不同

📄 当前页面

检测页面变化

🎬 触发过渡动画

⬅️ 旧页面退出

➡️ 新页面进入

✅ 切换完成

📄 显示新页面

核心代码实现:

PageTransitionSwitcher(
  duration: const Duration(milliseconds: 300), // 动画时长
  reverse: false, // 是否反向播放
  transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
    // 👇 自定义过渡效果
    return SharedAxisTransition(
      animation: primaryAnimation,
      secondaryAnimation: secondaryAnimation,
      transitionType: SharedAxisTransitionType.horizontal,
      child: child,
    );
  },
  child: Container(
    key: ValueKey<int>(currentIndex), // 👈 关键:通过key触发切换
    child: const Text('当前页面内容'),
  ),
)

关键点:

  • 🔑 key:必须使用不同的 key 来触发动画
  • 🎨 transitionBuilder:自定义过渡动画构建器
  • ⏱️ duration:控制动画时长
  • 🔄 reverse:控制动画方向

2.3 ↔️ SharedAxisTransition - 共享轴过渡

SharedAxisTransition 实现沿共享轴的过渡效果,适合层级页面切换。

📊 共享轴动画原理:

horizontal

vertical

scaled

📱 开始动画

选择轴方向

↔️ 水平轴

↕️ 垂直轴

🔍 缩放轴

⬅️ 旧内容向左退出
➡️ 新内容从右进入

⬆️ 旧内容向上退出
⬇️ 新内容从下进入

🔽 旧内容缩小退出
🔼 新内容放大进入

✅ 完成切换

核心代码实现:

SharedAxisTransition(
  animation: animation, // 进入动画
  secondaryAnimation: secondaryAnimation, // 退出动画
  transitionType: SharedAxisTransitionType.horizontal, // 轴类型
  child: child, // 子组件
)

轴类型说明:

  • ↔️ horizontal:水平轴(适合左右切换)
  • ↕️ vertical:垂直轴(适合上下切换)
  • 🔍 scaled:缩放轴(适合层级切换)

2.4 🌫️ FadeThroughTransition - 淡入淡出过渡

FadeThroughTransition 实现内容淡出后新内容淡入的效果,适合同级页面切换。

📊 淡入淡出动画流程:

📄 显示内容A

🌫️ 淡出动画

⚪ 完全透明

✨ 淡入动画

📄 显示内容B

核心代码实现:

FadeThroughTransition(
  animation: animation, // 进入动画
  secondaryAnimation: secondaryAnimation, // 退出动画
  child: child, // 子组件
)

适用场景:

  • 📑 Tab 页签切换
  • 🎯 底部导航栏切换
  • 🎨 内容类型切换
  • 📊 数据视图切换

💻 3. 完整示例:lib/animations_demo.dart

接下来,我们创建一个完整的演示页面,展示所有动画组件的使用方法。

新建文件lib/animations_demo.dart

3.1 页面功能概览

我们的演示页面将实现以下功能:

  • 🎭 OpenContainer 演示:容器展开到详情页
  • 🔄 PageTransitionSwitcher 演示:页面内容切换
  • ↔️ SharedAxisTransition 演示:不同轴方向切换
  • 🌫️ FadeThroughTransition 演示:淡入淡出切换

3.2 导入依赖

import 'package:flutter/material.dart';
import 'package:animations/animations.dart';

3.3 OpenContainer 实现示例

实现效果:

  • 📦 点击卡片展开到详情页
  • 🎨 支持两种过渡类型选择
  • ⏱️ 自定义动画时长
class _AnimationsDemoPageState extends State<AnimationsDemoPage> {
  ContainerTransitionType _transitionType = ContainerTransitionType.fade;

  
  Widget build(BuildContext context) {
    return OpenContainer(
      transitionType: _transitionType,
      transitionDuration: const Duration(milliseconds: 500),
      closedElevation: 4,
      closedShape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
      ),
      closedColor: Colors.blue.shade100,
      openColor: Colors.white,
      closedBuilder: (context, action) {
        return Container(
          height: 120,
          padding: const EdgeInsets.all(20),
          child: const Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(Icons.image, size: 40, color: Colors.blue),
              SizedBox(height: 8),
              Text('点击查看详情'),
            ],
          ),
        );
      },
      openBuilder: (context, action) {
        return Scaffold(
          appBar: AppBar(title: const Text('详情页')),
          body: const Center(
            child: Text('这是详情内容'),
          ),
        );
      },
    );
  }
}

image-20260204221935544

3.4 PageTransitionSwitcher 实现示例

实现效果:

  • 🔄 支持前后翻页
  • ↔️ 水平轴滑动效果
  • 🎯 自动处理反向动画
class _PageTransitionDemo extends StatefulWidget {
  
  State<_PageTransitionDemo> createState() => _PageTransitionDemoState();
}

class _PageTransitionDemoState extends State<_PageTransitionDemo> {
  int _currentIndex = 0;
  bool _isReverse = false;

  void _changePage(int delta) {
    setState(() {
      _isReverse = delta < 0; // 👈 控制动画方向
      _currentIndex = (_currentIndex + delta) % pages.length;
    });
  }

  
  Widget build(BuildContext context) {
    return PageTransitionSwitcher(
      duration: const Duration(milliseconds: 300),
      reverse: _isReverse,
      transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
        return SharedAxisTransition(
          animation: primaryAnimation,
          secondaryAnimation: secondaryAnimation,
          transitionType: SharedAxisTransitionType.horizontal,
          child: child,
        );
      },
      child: Container(
        key: ValueKey<int>(_currentIndex), // 👈 关键:触发切换
        child: pages[_currentIndex],
      ),
    );
  }
}

image-20260204222019891

3.5 SharedAxisTransition 实现示例

实现效果:

  • ↔️ 支持横向/纵向切换
  • 🎨 动态切换轴方向
  • ✨ 平滑的进出动画
class _SharedAxisDemo extends StatefulWidget {
  
  State<_SharedAxisDemo> createState() => _SharedAxisDemoState();
}

class _SharedAxisDemoState extends State<_SharedAxisDemo> {
  SharedAxisTransitionType _transitionType = 
      SharedAxisTransitionType.horizontal;
  int _currentIndex = 0;

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 轴方向选择器
        SegmentedButton<SharedAxisTransitionType>(
          segments: const [
            ButtonSegment(
              value: SharedAxisTransitionType.horizontal,
              label: Text('横向'),
            ),
            ButtonSegment(
              value: SharedAxisTransitionType.vertical,
              label: Text('纵向'),
            ),
          ],
          selected: {_transitionType},
          onSelectionChanged: (newSelection) {
            setState(() {
              _transitionType = newSelection.first;
            });
          },
        ),
        // 动画容器
        PageTransitionSwitcher(
          transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
            return SharedAxisTransition(
              animation: primaryAnimation,
              secondaryAnimation: secondaryAnimation,
              transitionType: _transitionType,
              child: child,
            );
          },
          child: Container(
            key: ValueKey<int>(_currentIndex),
            child: Text('内容 ${_currentIndex + 1}'),
          ),
        ),
      ],
    );
  }
}

image-20260204222102413

3.6 FadeThroughTransition 实现示例

实现效果:

  • 🌫️ 平滑的淡入淡出
  • 🎯 适合 Tab 切换
  • ✨ 优雅的内容替换
class _FadeThroughDemo extends StatefulWidget {
  
  State<_FadeThroughDemo> createState() => _FadeThroughDemoState();
}

class _FadeThroughDemoState extends State<_FadeThroughDemo> {
  int _currentIndex = 0;

  
  Widget build(BuildContext context) {
    return PageTransitionSwitcher(
      duration: const Duration(milliseconds: 400),
      transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
        return FadeThroughTransition(
          animation: primaryAnimation,
          secondaryAnimation: secondaryAnimation,
          child: child,
        );
      },
      child: Container(
        key: ValueKey<int>(_currentIndex),
        child: tabs[_currentIndex],
      ),
    );
  }
}

image-20260204222148575


🚀 4. 配置应用入口:lib/main.dart

最后,我们在应用的主页添加入口,跳转到演示页面。

修改文件lib/main.dart

操作:

  1. 导入演示页面文件
  2. 在按钮列表中添加跳转按钮
// 1. 导入头文件
import 'animations_demo.dart'; 

// 2. 在 build 方法中添加跳转按钮
ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const AnimationsDemoPage(),
      ),
    );
  },
  child: const Text('Go to Animations Demo'),
),

⚠️ 5. 常见错误与解决方案

❌ 错误 1:PageTransitionSwitcher 不触发动画

😱 错误现象:

  • 切换内容时没有动画效果
  • 页面直接跳变

🔍 原因分析:

  • ❌ 子组件没有设置不同的 key
  • ❌ child 的引用没有改变

✅ 解决方案:

// ❌ 错误:没有使用 key
PageTransitionSwitcher(
  child: Container(
    child: Text('内容 $_index'),
  ),
)

// ✅ 正确:使用不同的 key
PageTransitionSwitcher(
  child: Container(
    key: ValueKey<int>(_index), // 👈 关键!
    child: Text('内容 $_index'),
  ),
)

❌ 错误 2:OpenContainer 点击无响应

😱 错误现象:

  • 点击 OpenContainer 没有任何反应
  • 无法打开详情页

🔍 原因分析:

  • closedBuilder 中的 widget 拦截了点击事件
  • ❌ 使用了 GestureDetector 等手势组件

✅ 解决方案:

// ❌ 错误:内部使用了 GestureDetector
OpenContainer(
  closedBuilder: (context, action) {
    return GestureDetector(
      onTap: () {}, // 这会拦截 OpenContainer 的点击
      child: Container(...),
    );
  },
  openBuilder: (context, action) => DetailPage(),
)

// ✅ 正确:不要在内部使用手势组件
OpenContainer(
  closedBuilder: (context, action) {
    return Container(...), // 直接返回内容
  },
  openBuilder: (context, action) => DetailPage(),
)

❌ 错误 3:动画卡顿或不流畅

😱 错误现象:

  • 动画播放时出现卡顿
  • 动画帧率不稳定

🔍 原因分析:

  • ❌ 动画时长设置过短或过长
  • ❌ builder 中进行了耗时操作
  • ❌ 同时播放多个复杂动画

✅ 解决方案:

1. 合理设置动画时长:

// ❌ 过短:动画太快,看不清
duration: const Duration(milliseconds: 100)

// ✅ 合适:流畅且舒适
duration: const Duration(milliseconds: 300)

// ❌ 过长:动画拖沓
duration: const Duration(milliseconds: 1000)

动画时长参考:

  • 快速切换:200-250ms(Tab、底部导航)
  • 🎯 标准动画:300-400ms(页面切换、卡片展开)
  • 🎬 复杂动画:500-600ms(全屏过渡、容器变换)

2. 优化 builder 性能:

// ❌ 错误:在 builder 中进行耗时操作
openBuilder: (context, action) {
  final data = heavyComputation(); // 耗时操作
  return DetailPage(data: data);
}

// ✅ 正确:提前计算好数据
openBuilder: (context, action) {
  return DetailPage(data: preComputedData);
}

❌ 错误 4:SharedAxisTransition 方向错误

😱 错误现象:

  • 动画方向与预期不符
  • 横向切换变成了纵向

🔍 原因分析:

  • transitionType 设置错误
  • reverse 参数使用不当

✅ 解决方案:

// 明确指定轴方向
SharedAxisTransition(
  animation: animation,
  secondaryAnimation: secondaryAnimation,
  transitionType: SharedAxisTransitionType.horizontal, // 👈 明确指定
  child: child,
)

方向选择建议:

  • ↔️ horizontal:左右切换(适合:相册、轮播、步骤)
  • ↕️ vertical:上下切换(适合:列表详情、弹出面板)
  • 🔍 scaled:缩放切换(适合:层级导航、弹窗)

❌ 错误 5:FadeThroughTransition 闪烁

😱 错误现象:

  • 切换时出现明显闪烁
  • 中间出现空白帧

🔍 原因分析:

  • ❌ 两个子组件高度不一致
  • ❌ 动画时长设置不当

✅ 解决方案:

// ✅ 确保容器高度一致
PageTransitionSwitcher(
  transitionBuilder: (child, animation, secondaryAnimation) {
    return FadeThroughTransition(
      animation: animation,
      secondaryAnimation: secondaryAnimation,
      child: child,
    );
  },
  child: SizedBox(
    height: 200, // 👈 固定高度
    key: ValueKey<int>(_index),
    child: content,
  ),
)

📋 6. 动画组件对比表

动画组件 适用场景 特点 推荐场景
🎭 OpenContainer 容器→详情页 展开/收起过渡 列表项详情、卡片展开
🔄 PageTransitionSwitcher 页面内容切换 自定义过渡容器 多页面切换、内容替换
↔️ SharedAxisTransition 层级页面切换 共享轴滑动 导航切换、步骤流程
🌫️ FadeThroughTransition 同级内容切换 淡入淡出 Tab切换、数据视图切换

选择建议:

  • 📱 列表→详情 → 使用 OpenContainer
  • 🔄 页面切换 → 使用 SharedAxisTransition
  • 🎯 Tab切换 → 使用 FadeThroughTransition
  • 🎨 自定义过渡 → 使用 PageTransitionSwitcher + 自定义动画

🎨 7. 性能优化建议

⚡ 动画性能优化

1. 避免在动画中重建复杂组件

// ❌ 不好:每次动画都重建列表
PageTransitionSwitcher(
  child: ListView.builder(
    key: ValueKey(_index),
    itemCount: 1000,
    itemBuilder: (context, index) => ExpensiveWidget(),
  ),
)

// ✅ 更好:使用 RepaintBoundary 隔离
PageTransitionSwitcher(
  child: RepaintBoundary(
    key: ValueKey(_index),
    child: CachedListView(),
  ),
)

2. 合理使用动画时长

// 根据动画复杂度调整时长
const Duration shortDuration = Duration(milliseconds: 200);  // 简单切换
const Duration mediumDuration = Duration(milliseconds: 300); // 标准动画
const Duration longDuration = Duration(milliseconds: 500);   // 复杂过渡

3. 减少动画层级

// ❌ 避免:嵌套多层动画
OpenContainer(
  openBuilder: (context, action) {
    return PageTransitionSwitcher( // 嵌套动画
      child: SharedAxisTransition(...),
    );
  },
)

// ✅ 推荐:使用单一动画
OpenContainer(
  openBuilder: (context, action) => DetailPage(),
)

🎉 结语

通过以上步骤,我们完成了 animations 在鸿蒙系统上的完整集成!🚀

📝 本教程涵盖内容:

  • ✅ 引入依赖配置
  • ✅ 四大核心动画组件详解
  • ✅ OpenContainer 容器变换动画
  • ✅ PageTransitionSwitcher 页面切换动画
  • ✅ SharedAxisTransition 共享轴动画
  • ✅ FadeThroughTransition 淡入淡出动画
  • ✅ 常见错误解决方案
  • ✅ 性能优化建议

✨ 核心要点回顾:

  • 🎯 选对组件:根据场景选择合适的动画组件
  • ⏱️ 合理时长:动画时长要符合用户预期(200-500ms)
  • 🔑 正确使用 key:PageTransitionSwitcher 必须使用不同的 key
  • 🎨 性能优化:避免在动画中进行耗时操作
  • 📱 Material Design:遵循官方设计规范

🚀 进阶学习方向:

  • 🎬 结合 Hero 动画实现共享元素过渡
  • 🎨 自定义过渡动画效果
  • ⚡ 优化大列表动画性能
  • 🎭 实现复杂的多步骤过渡动画
  • 📦 封装通用的动画组件库

📚 完整动画流程:

容器展开

页面切换

层级切换

Tab切换

📚 开始

📦 引入animations依赖

🎨 选择动画组件

确定使用场景

🎭 OpenContainer

🔄 PageTransitionSwitcher

↔️ SharedAxisTransition

🌫️ FadeThroughTransition

⚙️ 配置动画参数

🎬 实现动画效果

🔍 测试优化

🎉 完成

🌟 Material Design 动画原则:

  • 快速响应:动画应该快速启动(<100ms)
  • 🎯 明确目的:动画要有明确的开始和结束状态
  • 🎨 自然流畅:使用缓动曲线,避免线性动画
  • 📱 保持一致:相同类型的交互使用相同的动画
  • 🎭 有意义:动画应该帮助用户理解界面变化

快去运行你的鸿蒙应用试试吧!Happy Coding! 💻⚡️

Logo

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

更多推荐