Flutter for OpenHarmony三方库适配实战:animations 动画库详解

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于flutter3.27.5开发


一、animations 库概述

动画是提升用户体验的重要手段,流畅的过渡动画能让应用更加生动自然。在 Flutter for OpenHarmony 应用开发中,animations 是一个功能强大的预构建动画库,提供了 Material Design 规范中的四种核心过渡模式,帮助开发者快速实现高质量的页面转场效果。

📋 animations 库特点

animations 库基于 Material Design 的 Motion System 规范实现,提供了以下核心特性:

容器转换(Container Transform):适用于有容器关系的元素之间的过渡,如卡片展开为详情页、列表项展开为全屏页面等。这种动画通过共享容器元素,创建两个界面之间的视觉连接。

共享轴(Shared Axis):适用于有空间或导航关系的元素过渡,如向导流程、步骤导航等。支持水平(X轴)、垂直(Y轴)和缩放(Z轴)三种方向,通过共享变换强化元素间的关系。

淡入淡出(Fade Through):适用于没有强关联关系的元素过渡,如底部导航栏切换、刷新操作等。先淡出旧元素,再淡入新元素,同时新元素带有缩放效果以强调新内容。

淡入淡出(Fade):适用于屏幕范围内元素的进入和退出,如对话框、菜单、Snackbar 等。元素在屏幕中心淡入淡出,适合临时性 UI 组件。

支持平台对比

平台 支持情况 说明
Android ✅ 完全支持 原生支持所有动画效果
iOS ✅ 完全支持 原生支持所有动画效果
Web ✅ 完全支持 原生支持所有动画效果
Windows ✅ 完全支持 原生支持所有动画效果
macOS ✅ 完全支持 原生支持所有动画效果
Linux ✅ 完全支持 原生支持所有动画效果
OpenHarmony ✅ 完全支持 纯 Dart 实现,无需平台适配

💡 重要说明:animations 是一个纯 Dart 库,不依赖平台原生代码,因此在 OpenHarmony 平台上可以开箱即用,无需任何额外配置或适配工作。


二、安装与配置

2.1 添加依赖

在项目的 pubspec.yaml 文件中添加 animations 依赖:

yaml 复制代码
dependencies:
  animations:
    git:
      url: https://atomgit.com/openharmony-tpc/flutter_packages.git
      path: packages/animations

然后执行以下命令获取依赖:

bash 复制代码
flutter pub get

2.2 兼容性信息

项目 版本要求
Flutter SDK >= 3.7.0
Dart SDK >= 2.19.0
OpenHarmony API 12+

2.3 权限要求

由于 animations 是纯 Dart 库,不涉及平台原生功能,因此不需要配置任何权限


三、核心 API 详解

3.1 OpenContainer - 容器转换动画

OpenContainer 是实现容器转换动画的核心组件,它可以在关闭状态和打开状态之间进行平滑过渡。

基本用法
dart 复制代码
import 'package:animations/animations.dart';

OpenContainer(
  closedBuilder: (BuildContext context, VoidCallback action) {
    return Container(
      height: 120,
      child: ListTile(
        leading: Icon(Icons.photo),
        title: Text('点击查看详情'),
        subtitle: Text('这是一个容器转换示例'),
        onTap: action,
      ),
    );
  },
  openBuilder: (BuildContext context, CloseContainerActionCallback action) {
    return Scaffold(
      appBar: AppBar(title: Text('详情页面')),
      body: Center(
        child: ElevatedButton(
          onPressed: () => action(),
          child: Text('返回'),
        ),
      ),
    );
  },
);
OpenContainer 属性详解

closedBuilder 是一个回调函数,用于构建容器关闭状态下的 Widget。该回调接收两个参数:BuildContextVoidCallback。其中 VoidCallback 用于触发容器打开动画,通常绑定到 Widget 的点击事件上。

openBuilder 是一个回调函数,用于构建容器打开状态下的 Widget。该回调同样接收两个参数:BuildContextCloseContainerActionCallbackCloseContainerActionCallback 用于触发容器关闭动画,调用时可以传入一个返回值。

closedColor 定义容器关闭状态下的背景颜色,默认为白色。当容器打开时,颜色会从 closedColor 过渡到 middleColor,再过渡到 openColor

openColor 定义容器打开状态下的背景颜色,默认为白色。容器关闭时,颜色会反向过渡。

middleColor 定义过渡过程中的中间颜色,仅在 ContainerTransitionType.fadeThrough 模式下使用。默认使用主题的 canvasColor

closedElevation 定义容器关闭状态下的阴影高度,默认为 1.0。打开时会过渡到 openElevation

openElevation 定义容器打开状态下的阴影高度,默认为 4.0。

closedShape 定义容器关闭状态下的形状,默认为圆角矩形(圆角半径 4.0)。打开时会过渡到 openShape

openShape 定义容器打开状态下的形状,默认为无圆角的矩形。

transitionType 定义过渡动画类型,可选值为 ContainerTransitionType.fadeContainerTransitionType.fadeThroughfade 模式下新元素直接淡入覆盖旧元素,fadeThrough 模式下旧元素先淡出,新元素再淡入。

transitionDuration 定义过渡动画的持续时间,默认为 300 毫秒。

tappable 定义容器是否可点击,默认为 true。设置为 false 时,需要手动调用 action 回调来触发动画。

onClosed 是一个回调函数,当容器关闭时触发,可以接收 openBuilder 中传递的返回值。

完整示例
dart 复制代码
class ContainerTransformDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('容器转换动画')),
      body: ListView.builder(
        itemCount: 10,
        itemBuilder: (context, index) {
          return OpenContainer(
            transitionType: ContainerTransitionType.fadeThrough,
            closedShape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
            ),
            closedElevation: 2,
            closedBuilder: (context, action) {
              return InkWell(
                onTap: action,
                child: Container(
                  padding: EdgeInsets.all(16),
                  child: Row(
                    children: [
                      Container(
                        width: 60,
                        height: 60,
                        color: Colors.blue[100],
                        child: Icon(Icons.image),
                      ),
                      SizedBox(width: 16),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text('项目 ${index + 1}',
                              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                            SizedBox(height: 4),
                            Text('点击查看详细信息'),
                          ],
                        ),
                      ),
                      Icon(Icons.arrow_forward_ios),
                    ],
                  ),
                ),
              );
            },
            openBuilder: (context, action) {
              return DetailPage(index: index, onClose: action);
            },
            onClosed: (result) {
              if (result != null) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('返回值: $result')),
                );
              }
            },
          );
        },
      ),
    );
  }
}

class DetailPage extends StatelessWidget {
  final int index;
  final CloseContainerActionCallback onClose;

  const DetailPage({required this.index, required this.onClose});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('详情页 ${index + 1}'),
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          onPressed: () => onClose(returnValue: '来自详情页的返回值'),
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('这是第 ${index + 1} 个项目的详情页'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => onClose(returnValue: '用户点击了按钮'),
              child: Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

3.2 SharedAxisTransition - 共享轴过渡动画

SharedAxisTransition 用于实现具有空间或导航关系的元素过渡,支持水平、垂直和缩放三种方向。

SharedAxisTransitionType 枚举

SharedAxisTransitionType.horizontal 表示水平方向(X轴)过渡,适用于向导流程、分步表单等场景。元素会沿水平方向滑入滑出。

SharedAxisTransitionType.vertical 表示垂直方向(Y轴)过渡,适用于步骤导航、层级导航等场景。元素会沿垂直方向滑入滑出。

SharedAxisTransitionType.scaled 表示缩放方向(Z轴)过渡,适用于父子导航、层级切换等场景。元素会通过缩放效果进入或退出。

在 PageTransitionsTheme 中使用
dart 复制代码
MaterialApp(
  theme: ThemeData(
    pageTransitionsTheme: PageTransitionsTheme(
      builders: {
        TargetPlatform.android: SharedAxisPageTransitionsBuilder(
          transitionType: SharedAxisTransitionType.horizontal,
        ),
        TargetPlatform.iOS: SharedAxisPageTransitionsBuilder(
          transitionType: SharedAxisTransitionType.horizontal,
        ),
      },
    ),
  ),
  home: HomePage(),
);
在 PageTransitionSwitcher 中使用
dart 复制代码
class SharedAxisDemo extends StatefulWidget {
  @override
  _SharedAxisDemoState createState() => _SharedAxisDemoState();
}

class _SharedAxisDemoState extends State<SharedAxisDemo> {
  int _selectedIndex = 0;
  final List<Widget> _pages = [
    PageWidget(color: Colors.blue, title: '页面 1'),
    PageWidget(color: Colors.red, title: '页面 2'),
    PageWidget(color: Colors.green, title: '页面 3'),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('共享轴过渡')),
      body: PageTransitionSwitcher(
        transitionBuilder: (child, animation, secondaryAnimation) {
          return SharedAxisTransition(
            animation: animation,
            secondaryAnimation: secondaryAnimation,
            transitionType: SharedAxisTransitionType.horizontal,
            child: child,
          );
        },
        child: _pages[_selectedIndex],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: (index) => setState(() => _selectedIndex = index),
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.business), label: '业务'),
          BottomNavigationBarItem(icon: Icon(Icons.school), label: '学习'),
        ],
      ),
    );
  }
}

class PageWidget extends StatelessWidget {
  final Color color;
  final String title;

  const PageWidget({required this.color, required this.title});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: color,
      child: Center(
        child: Text(title, style: TextStyle(fontSize: 24, color: Colors.white)),
      ),
    );
  }
}
SharedAxisTransition 属性详解

animation 是主动画控制器,控制新页面的进入动画。当值从 0 到 1 时,新页面进入。

secondaryAnimation 是辅助动画控制器,控制旧页面的退出动画。当值从 0 到 1 时,旧页面退出。

transitionType 定义过渡类型,使用 SharedAxisTransitionType 枚举指定方向。

fillColor 定义过渡过程中的背景填充颜色,默认使用主题的 canvasColor

child 是要显示的子组件。


3.3 FadeThroughTransition - 淡入淡出过渡动画

FadeThroughTransition 适用于没有强关联关系的元素过渡,特点是先淡出旧元素,再淡入新元素,同时新元素带有轻微缩放效果。

在 PageTransitionsTheme 中使用
dart 复制代码
MaterialApp(
  theme: ThemeData(
    pageTransitionsTheme: PageTransitionsTheme(
      builders: {
        TargetPlatform.android: FadeThroughPageTransitionsBuilder(),
        TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(),
      },
    ),
  ),
  home: HomePage(),
);
在 PageTransitionSwitcher 中使用
dart 复制代码
class FadeThroughDemo extends StatefulWidget {
  @override
  _FadeThroughDemoState createState() => _FadeThroughDemoState();
}

class _FadeThroughDemoState extends State<FadeThroughDemo> {
  int _selectedIndex = 0;
  final List<Color> _colors = [Colors.blue, Colors.red, Colors.yellow];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('淡入淡出过渡')),
      body: PageTransitionSwitcher(
        transitionBuilder: (child, animation, secondaryAnimation) {
          return FadeThroughTransition(
            animation: animation,
            secondaryAnimation: secondaryAnimation,
            child: child,
          );
        },
        child: Container(
          key: ValueKey<int>(_selectedIndex),
          color: _colors[_selectedIndex],
          child: Center(
            child: Text('页面 ${_selectedIndex + 1}',
              style: TextStyle(fontSize: 24)),
          ),
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: (index) => setState(() => _selectedIndex = index),
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.business), label: '业务'),
          BottomNavigationBarItem(icon: Icon(Icons.school), label: '学习'),
        ],
      ),
    );
  }
}
FadeThroughTransition 属性详解

animation 是主动画控制器,控制新元素的淡入和缩放动画。

secondaryAnimation 是辅助动画控制器,控制旧元素的淡出动画。

fillColor 定义过渡过程中的背景填充颜色,默认使用主题的 canvasColor

child 是要显示的子组件。


3.4 FadeScaleTransition - 淡入缩放过渡动画

FadeScaleTransition 适用于屏幕范围内元素的进入和退出,如对话框、菜单、Snackbar 等。元素会在淡入淡出的同时进行缩放。

基本用法
dart 复制代码
class FadeScaleDemo extends StatefulWidget {
  @override
  _FadeScaleDemoState createState() => _FadeScaleDemoState();
}

class _FadeScaleDemoState extends State<FadeScaleDemo> {
  bool _showDialog = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('淡入缩放过渡')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => setState(() => _showDialog = true),
              child: Text('显示对话框'),
            ),
            SizedBox(height: 20),
            if (_showDialog)
              FadeScaleTransition(
                animation: AnimationController(
                  duration: Duration(milliseconds: 300),
                  vsync: this,
                )..forward(),
                child: AlertDialog(
                  title: Text('提示'),
                  content: Text('这是一个使用 FadeScaleTransition 的对话框'),
                  actions: [
                    TextButton(
                      onPressed: () => setState(() => _showDialog = false),
                      child: Text('关闭'),
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
    );
  }
}
FadeScaleTransition 属性详解

animation 是动画控制器,控制元素的淡入淡出和缩放效果。当值从 0 到 1 时,元素淡入并放大;从 1 到 0 时,元素淡出并缩小。

child 是要显示的子组件。


3.5 PageTransitionSwitcher - 页面过渡切换器

PageTransitionSwitcher 是一个类似于 AnimatedSwitcher 的组件,专门用于实现页面之间的过渡动画。

基本用法
dart 复制代码
PageTransitionSwitcher(
  duration: Duration(milliseconds: 300),
  reverse: _isReverse, // 是否反向播放动画
  transitionBuilder: (child, animation, secondaryAnimation) {
    return FadeThroughTransition(
      animation: animation,
      secondaryAnimation: secondaryAnimation,
      child: child,
    );
  },
  child: CurrentPage(key: ValueKey(pageId)),
);
PageTransitionSwitcher 属性详解

duration 定义过渡动画的持续时间。

reverse 定义是否反向播放动画,通常用于返回操作时设置为 true。

transitionBuilder 是一个回调函数,用于构建过渡动画。接收三个参数:child(当前子组件)、animation(主动画)、secondaryAnimation(辅助动画)。

child 是当前要显示的子组件。当 child 发生变化时(通过 key 判断),会触发过渡动画。

layoutBuilder 用于自定义子组件的布局方式,默认使用 Stack 布局。


四、实战案例

4.1 卡片展开详情页

dart 复制代码
class CardExpandDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('卡片展开效果')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: GridView.builder(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            crossAxisSpacing: 16,
            mainAxisSpacing: 16,
          ),
          itemCount: 6,
          itemBuilder: (context, index) {
            return OpenContainer(
              closedBuilder: (context, action) {
                return InkWell(
                  onTap: action,
                  child: Container(
                    decoration: BoxDecoration(
                      color: Colors.primaries[index % Colors.primaries.length],
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.image, size: 48, color: Colors.white),
                        SizedBox(height: 8),
                        Text('卡片 ${index + 1}',
                          style: TextStyle(color: Colors.white, fontSize: 16)),
                      ],
                    ),
                  ),
                );
              },
              openBuilder: (context, action) {
                return Scaffold(
                  appBar: AppBar(
                    title: Text('详情 ${index + 1}'),
                    backgroundColor: Colors.primaries[index % Colors.primaries.length],
                  ),
                  body: Container(
                    color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.2),
                    child: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Icon(Icons.image, size: 100),
                          SizedBox(height: 20),
                          Text('这是卡片 ${index + 1} 的详情页面',
                            style: TextStyle(fontSize: 20)),
                          SizedBox(height: 20),
                          ElevatedButton(
                            onPressed: () => Navigator.pop(context),
                            child: Text('返回'),
                          ),
                        ],
                      ),
                    ),
                  ),
                );
              },
              closedShape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              closedElevation: 4,
            );
          },
        ),
      ),
    );
  }
}

4.2 向导流程页面

dart 复制代码
class WizardFlowDemo extends StatefulWidget {
  @override
  _WizardFlowDemoState createState() => _WizardFlowDemoState();
}

class _WizardFlowDemoState extends State<WizardFlowDemo> {
  int _currentStep = 0;
  final int _totalSteps = 3;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('向导流程'),
        leading: _currentStep > 0
          ? IconButton(
              icon: Icon(Icons.arrow_back),
              onPressed: () => setState(() => _currentStep--),
            )
          : null,
      ),
      body: PageTransitionSwitcher(
        reverse: _isReverse,
        transitionBuilder: (child, animation, secondaryAnimation) {
          return SharedAxisTransition(
            animation: animation,
            secondaryAnimation: secondaryAnimation,
            transitionType: SharedAxisTransitionType.horizontal,
            child: child,
          );
        },
        child: StepPage(
          key: ValueKey(_currentStep),
          step: _currentStep,
          onNext: _currentStep < _totalSteps - 1
            ? () => setState(() { _isReverse = false; _currentStep++; })
            : null,
          onPrevious: _currentStep > 0
            ? () => setState(() { _isReverse = true; _currentStep--; })
            : null,
        ),
      ),
    );
  }

  bool _isReverse = false;
}

class StepPage extends StatelessWidget {
  final int step;
  final VoidCallback? onNext;
  final VoidCallback? onPrevious;

  const StepPage({
    required this.step,
    this.onNext,
    this.onPrevious,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.primaries[step % Colors.primaries.length].withOpacity(0.3),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('步骤 ${step + 1}', style: TextStyle(fontSize: 32)),
            SizedBox(height: 20),
            Text('这是向导的第 ${step + 1} 步'),
            SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                if (onPrevious != null)
                  ElevatedButton(
                    onPressed: onPrevious,
                    child: Text('上一步'),
                  ),
                SizedBox(width: 20),
                if (onNext != null)
                  ElevatedButton(
                    onPressed: onNext,
                    child: Text('下一步'),
                  ),
                if (onNext == null)
                  ElevatedButton(
                    onPressed: () => Navigator.pop(context),
                    child: Text('完成'),
                  ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

五、最佳实践

5.1 选择合适的过渡类型

根据场景选择合适的过渡动画:

  • 容器转换:适用于有明确容器关系的元素,如卡片展开、列表项展开等
  • 共享轴:适用于有空间或导航关系的元素,如向导流程、步骤导航等
  • 淡入淡出:适用于没有强关联关系的元素,如底部导航切换、刷新等
  • 淡入缩放:适用于临时性 UI 组件,如对话框、菜单、Snackbar 等

5.2 动画时长建议

Material Design 规范建议的动画时长:

  • 简单过渡:100-200 毫秒
  • 复杂过渡:200-400 毫秒
  • 大范围过渡:300-500 毫秒

5.3 性能优化

使用 PageTransitionSwitcher 时,确保为每个子组件设置唯一的 ValueKey,以便正确触发过渡动画。避免在过渡动画中执行耗时操作,以免影响动画流畅度。


六、常见问题

Q1:OpenContainer 中的 Widget 不能使用 GlobalKey?

在过渡过程中,关闭状态和打开状态的 Widget 会同时存在于 Widget 树中,因此不能使用相同的 GlobalKey。如果需要使用 Key,请确保两个状态的 Key 不同。

Q2:如何自定义过渡动画曲线?

可以通过自定义 AnimationController 来实现自定义动画曲线:

dart 复制代码
AnimationController(
  duration: Duration(milliseconds: 300),
  animationBehavior: AnimationBehavior.preserve,
  vsync: this,
)..drive(CurveTween(curve: Curves.easeInOut));

Q3:OpenContainer 打开后如何返回数据?

openBuilder 中调用 action 时传入返回值,然后在 onClosed 回调中接收:

dart 复制代码
OpenContainer(
  openBuilder: (context, action) {
    return DetailPage(
      onSelected: (data) => action(returnValue: data),
    );
  },
  onClosed: (result) {
    if (result != null) {
      print('返回数据: $result');
    }
  },
);

七、总结

animations 库为 Flutter for OpenHarmony 开发提供了开箱即用的高质量动画效果。作为纯 Dart 库,它在鸿蒙平台上无需任何适配工作,可以直接使用所有功能。通过合理运用容器转换、共享轴、淡入淡出等过渡模式,可以显著提升应用的用户体验和视觉品质。


八、完整代码示例

以下是一个完整的可运行示例,展示了 animations 库的所有核心功能:

pubspec.yaml

yaml 复制代码
name: animations_demo
description: Flutter for OpenHarmony animations 演示项目
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  animations:
    git:
      url: https://gitcode.com/openharmony-tpc/flutter_packages.git
      path: packages/animations

flutter:
  uses-material-design: true

main.dart

dart 复制代码
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Animations Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Animations 动画库演示'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        children: [
          _buildMenuItem(
            context,
            '容器转换动画',
            'OpenContainer - 卡片展开详情页',
            Icons.open_in_full,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const ContainerTransformDemo()),
            ),
          ),
          _buildMenuItem(
            context,
            '共享轴过渡',
            'SharedAxisTransition - 向导流程',
            Icons.swap_horiz,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const SharedAxisDemo()),
            ),
          ),
          _buildMenuItem(
            context,
            '淡入淡出过渡',
            'FadeThroughTransition - 底部导航切换',
            Icons.layers,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const FadeThroughDemo()),
            ),
          ),
          _buildMenuItem(
            context,
            '淡入缩放过渡',
            'FadeScaleTransition - 对话框动画',
            Icons.fullscreen,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const FadeScaleDemo()),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildMenuItem(
    BuildContext context,
    String title,
    String subtitle,
    IconData icon,
    VoidCallback onTap,
  ) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: ListTile(
        leading: Icon(icon, size: 32, color: Theme.of(context).primaryColor),
        title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
        subtitle: Text(subtitle),
        trailing: const Icon(Icons.arrow_forward_ios),
        onTap: onTap,
      ),
    );
  }
}

class ContainerTransformDemo extends StatelessWidget {
  const ContainerTransformDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('容器转换动画'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: GridView.builder(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            crossAxisSpacing: 16,
            mainAxisSpacing: 16,
          ),
          itemCount: 6,
          itemBuilder: (context, index) {
            return OpenContainer(
              transitionType: ContainerTransitionType.fadeThrough,
              closedShape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              closedElevation: 4,
              closedBuilder: (context, action) {
                return InkWell(
                  onTap: action,
                  child: Container(
                    decoration: BoxDecoration(
                      color: Colors.primaries[index % Colors.primaries.length],
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        const Icon(Icons.image, size: 48, color: Colors.white),
                        const SizedBox(height: 8),
                        Text(
                          '卡片 ${index + 1}',
                          style: const TextStyle(color: Colors.white, fontSize: 16),
                        ),
                      ],
                    ),
                  ),
                );
              },
              openBuilder: (context, action) {
                return Scaffold(
                  appBar: AppBar(
                    title: Text('详情 ${index + 1}'),
                    backgroundColor: Colors.primaries[index % Colors.primaries.length],
                  ),
                  body: Container(
                    color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.2),
                    child: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          const Icon(Icons.image, size: 100),
                          const SizedBox(height: 20),
                          Text(
                            '这是卡片 ${index + 1} 的详情页面',
                            style: const TextStyle(fontSize: 20),
                          ),
                          const SizedBox(height: 20),
                          ElevatedButton(
                            onPressed: () => Navigator.pop(context),
                            child: const Text('返回'),
                          ),
                        ],
                      ),
                    ),
                  ),
                );
              },
            );
          },
        ),
      ),
    );
  }
}

class SharedAxisDemo extends StatefulWidget {
  const SharedAxisDemo({super.key});

  @override
  State<SharedAxisDemo> createState() => _SharedAxisDemoState();
}

class _SharedAxisDemoState extends State<SharedAxisDemo> {
  int _currentStep = 0;
  final int _totalSteps = 3;
  bool _isReverse = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('共享轴过渡 - 向导流程'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        leading: _currentStep > 0
            ? IconButton(
                icon: const Icon(Icons.arrow_back),
                onPressed: () => setState(() {
                  _isReverse = true;
                  _currentStep--;
                }),
              )
            : null,
      ),
      body: PageTransitionSwitcher(
        reverse: _isReverse,
        transitionBuilder: (child, animation, secondaryAnimation) {
          return SharedAxisTransition(
            animation: animation,
            secondaryAnimation: secondaryAnimation,
            transitionType: SharedAxisTransitionType.horizontal,
            child: child,
          );
        },
        child: StepPage(
          key: ValueKey(_currentStep),
          step: _currentStep,
          onNext: _currentStep < _totalSteps - 1
              ? () => setState(() {
                    _isReverse = false;
                    _currentStep++;
                  })
              : null,
          onPrevious: _currentStep > 0
              ? () => setState(() {
                    _isReverse = true;
                    _currentStep--;
                  })
              : null,
        ),
      ),
    );
  }
}

class StepPage extends StatelessWidget {
  final int step;
  final VoidCallback? onNext;
  final VoidCallback? onPrevious;

  const StepPage({
    required this.step,
    this.onNext,
    this.onPrevious,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.primaries[step % Colors.primaries.length].withOpacity(0.3),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('步骤 ${step + 1}', style: const TextStyle(fontSize: 32)),
            const SizedBox(height: 20),
            Text('这是向导的第 ${step + 1} 步'),
            const SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                if (onPrevious != null)
                  ElevatedButton(
                    onPressed: onPrevious,
                    child: const Text('上一步'),
                  ),
                const SizedBox(width: 20),
                if (onNext != null)
                  ElevatedButton(
                    onPressed: onNext,
                    child: const Text('下一步'),
                  ),
                if (onNext == null)
                  ElevatedButton(
                    onPressed: () => Navigator.pop(context),
                    child: const Text('完成'),
                  ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class FadeThroughDemo extends StatefulWidget {
  const FadeThroughDemo({super.key});

  @override
  State<FadeThroughDemo> createState() => _FadeThroughDemoState();
}

class _FadeThroughDemoState extends State<FadeThroughDemo> {
  int _selectedIndex = 0;
  final List<Color> _colors = [Colors.blue, Colors.red, Colors.green];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('淡入淡出过渡'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: PageTransitionSwitcher(
        transitionBuilder: (child, animation, secondaryAnimation) {
          return FadeThroughTransition(
            animation: animation,
            secondaryAnimation: secondaryAnimation,
            child: child,
          );
        },
        child: Container(
          key: ValueKey<int>(_selectedIndex),
          color: _colors[_selectedIndex],
          child: Center(
            child: Text(
              '页面 ${_selectedIndex + 1}',
              style: const TextStyle(fontSize: 24, color: Colors.white),
            ),
          ),
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: (index) => setState(() => _selectedIndex = index),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.business), label: '业务'),
          BottomNavigationBarItem(icon: Icon(Icons.school), label: '学习'),
        ],
      ),
    );
  }
}

class FadeScaleDemo extends StatefulWidget {
  const FadeScaleDemo({super.key});

  @override
  State<FadeScaleDemo> createState() => _FadeScaleDemoState();
}

class _FadeScaleDemoState extends State<FadeScaleDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  bool _showDialog = false;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _toggleDialog() {
    setState(() {
      _showDialog = !_showDialog;
      if (_showDialog) {
        _controller.forward();
      } else {
        _controller.reverse();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('淡入缩放过渡'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: _toggleDialog,
              child: Text(_showDialog ? '关闭对话框' : '显示对话框'),
            ),
            const SizedBox(height: 20),
            if (_showDialog)
              FadeScaleTransition(
                animation: _controller,
                child: AlertDialog(
                  title: const Text('提示'),
                  content: const Text('这是一个使用 FadeScaleTransition 的对话框'),
                  actions: [
                    TextButton(
                      onPressed: _toggleDialog,
                      child: const Text('关闭'),
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
    );
  }
}

九、参考资源

相关推荐
AI_零食2 小时前
开源鸿蒙跨平台Flutter开发:生物力学与力量周期-臂力训练矩阵架构
学习·flutter·ui·华为·矩阵·开源·harmonyos
独特的螺狮粉2 小时前
开源鸿蒙跨平台Flutter开发:地震震源探测系统-地震波形与波干涉渲染架构
开发语言·flutter·华为·架构·开源·harmonyos
2501_921930832 小时前
Flutter for OpenHarmony三方库适配实战:share_plus 分享功能
flutter
小雨天気.2 小时前
Flutter 框架跨平台鸿蒙开发 - 反向社交应用
flutter·华为·harmonyos·鸿蒙
世人万千丶2 小时前
开源鸿蒙跨平台Flutter开发:幼儿园成语序列与海马体印迹锚定引擎-突触链式网络渲染架构
学习·flutter·开源·harmonyos·鸿蒙
小雨天気.2 小时前
Flutter 框架跨平台鸿蒙开发 - 情绪过山车应用
flutter·华为·harmonyos·鸿蒙
Utopia^2 小时前
Flutter 框架跨平台鸿蒙开发 - 默契挑战
flutter·华为·harmonyos
浮芷.3 小时前
Flutter 框架跨平台鸿蒙开发 - 人生轨迹预测应用
flutter·华为·harmonyos
世人万千丶3 小时前
开源鸿蒙跨平台深度解析:Flutter Pigeon 跨平台官方示例适配全流程与底层故障溯源
学习·flutter·华为·开源·harmonyos·鸿蒙系统