关注

Flutter for OpenHarmony:三方库实战 animations 页面过渡动画详解

在这里插入图片描述

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


🎯 前言:为什么需要页面过渡动画?

在移动应用开发中,页面切换动画是提升用户体验的重要手段。

实际痛点

  • 📱 生硬的页面跳转:默认的页面切换缺乏过渡,用户体验差
  • 🎨 缺乏连贯性:用户不清楚新页面与旧页面的关系
  • 视觉断层:突然的内容变化让用户感到不适
  • 🎭 缺少品质感:简单的切换无法体现应用的专业性
  • 🔄 元素跳跃:相同元素在不同页面间位置突变

实际场景需求

  • 场景一:电商应用中,商品卡片平滑展开成详情页
  • 场景二:社交应用中,头像点击后放大显示个人资料
  • 场景三:新闻应用中,文章列表到详情的流畅过渡
  • 场景四:相册应用中,缩略图到大图的自然转换
  • 场景五:设置页面中,选项卡之间的平滑切换

animations 是解决这些问题的完美方案!它提供了:

  • 🎬 容器转换动画:元素平滑展开/收缩
  • 🔄 共享轴过渡:页面沿轴线滑动切换
  • 💫 淡入淡出:优雅的透明度过渡
  • 🎯 Material Design 规范:符合设计规范的动画
  • 高性能:优化的动画性能
  • 🎨 可定制:支持自定义动画参数

🚀 核心能力一览

功能特性详细说明OpenHarmony 支持
容器转换OpenContainer 动画
共享轴过渡SharedAxisTransition
淡入淡出FadeThroughTransition
淡出动画FadeScaleTransition
自定义参数动画时长、曲线等
模拟器测试完美支持模拟器
跨平台一致所有平台行为一致

动画类型说明

动画类型说明适用场景
OpenContainer容器转换动画列表到详情、卡片展开
SharedAxisTransition共享轴过渡标签页切换、步骤导航
FadeThroughTransition淡入淡出过渡内容替换、状态切换
FadeScaleTransition淡出缩放对话框、弹窗

⚙️ 环境准备

第一步:添加依赖

📄 pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  
  # 添加 animations 依赖(OpenHarmony 适配版本)
  animations:
    git:
      url: https://atomgit.com/openharmony-tpc/flutter_packages.git
      path: packages/animations
      ref: br_animations-v2.0.11_ohos

执行命令:

flutter pub get

第二步:导入包

在 Dart 文件中导入:

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

📚 基础用法:核心功能

示例1:OpenContainer 容器转换动画

最常用的动画,实现列表到详情的平滑过渡:

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('容器转换动画'),
      ),
      body: ListView.builder(
        itemCount: 10,
        padding: const EdgeInsets.all(16),
        itemBuilder: (context, index) {
          return Padding(
            padding: const EdgeInsets.only(bottom: 16),
            child: OpenContainer(
              closedElevation: 2,
              closedShape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              closedColor: Colors.blue.shade50,
              openColor: Colors.white,
              transitionDuration: const Duration(milliseconds: 500),
              closedBuilder: (context, action) {
                // 关闭状态:列表项
                return ListTile(
                  leading: CircleAvatar(
                    backgroundColor: Colors.blue,
                    child: Text('${index + 1}'),
                  ),
                  title: Text('商品 ${index + 1}'),
                  subtitle: const Text('点击查看详情'),
                  trailing: const Icon(Icons.arrow_forward_ios, size: 16),
                );
              },
              openBuilder: (context, action) {
                // 打开状态:详情页
                return DetailPage(index: index);
              },
            ),
          );
        },
      ),
    );
  }
}

class DetailPage extends StatelessWidget {
  final int index;
  
  const DetailPage({super.key, required this.index});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('商品 ${index + 1} 详情'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.shopping_bag,
              size: 100,
              color: Colors.blue,
            ),
            const SizedBox(height: 20),
            Text(
              '商品 ${index + 1}',
              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            const Text('这是商品的详细信息'),
          ],
        ),
      ),
    );
  }
}

核心要点

  • closedBuilder:关闭状态的UI(列表项)
  • openBuilder:打开状态的UI(详情页)
  • transitionDuration:动画时长
  • 自动处理打开/关闭动画

示例2:SharedAxisTransition 共享轴过渡

适用于标签页切换、步骤导航:

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

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

class _SharedAxisDemoState extends State<SharedAxisDemo> {
  int _currentIndex = 0;
  
  final List<String> _pages = ['首页', '分类', '购物车', '我的'];
  final List<IconData> _icons = [
    Icons.home,
    Icons.category,
    Icons.shopping_cart,
    Icons.person,
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('共享轴过渡'),
      ),
      body: PageTransitionSwitcher(
        duration: const Duration(milliseconds: 300),
        transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
          return SharedAxisTransition(
            animation: primaryAnimation,
            secondaryAnimation: secondaryAnimation,
            transitionType: SharedAxisTransitionType.horizontal,
            child: child,
          );
        },
        child: _buildPage(_currentIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        items: List.generate(
          _pages.length,
          (index) => BottomNavigationBarItem(
            icon: Icon(_icons[index]),
            label: _pages[index],
          ),
        ),
      ),
    );
  }

  Widget _buildPage(int index) {
    return Container(
      key: ValueKey(index),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(_icons[index], size: 100, color: Colors.blue),
            const SizedBox(height: 20),
            Text(
              _pages[index],
              style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
            ),
          ],
        ),
      ),
    );
  }
}

SharedAxisTransitionType 类型

  • horizontal:水平滑动
  • vertical:垂直滑动
  • scaled:缩放过渡

示例3:FadeThroughTransition 淡入淡出过渡

适用于内容替换、状态切换:

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

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

class _FadeThroughDemoState extends State<FadeThroughDemo> {
  int _selectedIndex = 0;
  
  final List<String> _items = ['图片', '视频', '音乐', '文档'];
  final List<IconData> _icons = [
    Icons.image,
    Icons.video_library,
    Icons.music_note,
    Icons.description,
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('淡入淡出过渡'),
      ),
      body: Column(
        children: [
          // 选项卡
          Container(
            height: 60,
            child: ListView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: _items.length,
              padding: const EdgeInsets.all(8),
              itemBuilder: (context, index) {
                final isSelected = index == _selectedIndex;
                return Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 4),
                  child: ChoiceChip(
                    label: Text(_items[index]),
                    selected: isSelected,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() {
                          _selectedIndex = index;
                        });
                      }
                    },
                  ),
                );
              },
            ),
          ),
          
          // 内容区域
          Expanded(
            child: PageTransitionSwitcher(
              duration: const Duration(milliseconds: 300),
              transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
                return FadeThroughTransition(
                  animation: primaryAnimation,
                  secondaryAnimation: secondaryAnimation,
                  child: child,
                );
              },
              child: _buildContent(_selectedIndex),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildContent(int index) {
    return Container(
      key: ValueKey(index),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(_icons[index], size: 120, color: Colors.blue),
            const SizedBox(height: 20),
            Text(
              _items[index],
              style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Text(
              '这是${_items[index]}的内容区域',
              style: const TextStyle(color: Colors.grey),
            ),
          ],
        ),
      ),
    );
  }
}

示例4:FadeScaleTransition 淡出缩放

适用于对话框、弹窗:

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('淡出缩放'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            showModal(
              context: context,
              configuration: const FadeScaleTransitionConfiguration(),
              builder: (context) {
                return AlertDialog(
                  title: const Text('提示'),
                  content: const Text('这是一个使用淡出缩放动画的对话框'),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('取消'),
                    ),
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('确定'),
                    ),
                  ],
                );
              },
            );
          },
          child: const Text('显示对话框'),
        ),
      ),
    );
  }
}

示例5:自定义动画参数

自定义动画时长、曲线等参数:

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('自定义动画参数'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // 快速动画
          _buildAnimationCard(
            context,
            title: '快速动画',
            subtitle: '200ms',
            duration: const Duration(milliseconds: 200),
            color: Colors.red,
          ),
          
          // 标准动画
          _buildAnimationCard(
            context,
            title: '标准动画',
            subtitle: '300ms',
            duration: const Duration(milliseconds: 300),
            color: Colors.blue,
          ),
          
          // 慢速动画
          _buildAnimationCard(
            context,
            title: '慢速动画',
            subtitle: '500ms',
            duration: const Duration(milliseconds: 500),
            color: Colors.green,
          ),
        ],
      ),
    );
  }

  Widget _buildAnimationCard(
    BuildContext context, {
    required String title,
    required String subtitle,
    required Duration duration,
    required Color color,
  }) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16),
      child: OpenContainer(
        transitionDuration: duration,
        closedElevation: 2,
        closedShape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
        closedColor: color.withOpacity(0.1),
        openColor: Colors.white,
        closedBuilder: (context, action) {
          return ListTile(
            leading: CircleAvatar(
              backgroundColor: color,
              child: const Icon(Icons.timer, color: Colors.white),
            ),
            title: Text(title),
            subtitle: Text(subtitle),
            trailing: const Icon(Icons.arrow_forward_ios, size: 16),
          );
        },
        openBuilder: (context, action) {
          return Scaffold(
            appBar: AppBar(
              title: Text(title),
              backgroundColor: color,
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.timer, size: 100, color: color),
                  const SizedBox(height: 20),
                  Text(
                    title,
                    style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 10),
                  Text('动画时长: $subtitle'),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

🎯 完整示例:动画展示应用

下面是一个功能完整的动画展示应用,展示了所有核心功能:

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

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

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

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

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Animations 动画展示'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildDemoCard(
            context,
            title: '容器转换',
            subtitle: 'OpenContainer',
            icon: Icons.open_in_new,
            color: Colors.blue,
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const ContainerTransformDemo(),
                ),
              );
            },
          ),
          
          _buildDemoCard(
            context,
            title: '共享轴过渡',
            subtitle: 'SharedAxisTransition',
            icon: Icons.swap_horiz,
            color: Colors.green,
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const SharedAxisDemo(),
                ),
              );
            },
          ),
          
          _buildDemoCard(
            context,
            title: '淡入淡出',
            subtitle: 'FadeThroughTransition',
            icon: Icons.blur_on,
            color: Colors.orange,
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const FadeThroughDemo(),
                ),
              );
            },
          ),
          
          _buildDemoCard(
            context,
            title: '淡出缩放',
            subtitle: 'FadeScaleTransition',
            icon: Icons.zoom_out,
            color: Colors.purple,
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const FadeScaleDemo(),
                ),
              );
            },
          ),
          
          _buildDemoCard(
            context,
            title: '自定义参数',
            subtitle: '动画时长、曲线',
            icon: Icons.tune,
            color: Colors.teal,
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const CustomAnimationDemo(),
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  Widget _buildDemoCard(
    BuildContext context, {
    required String title,
    required String subtitle,
    required IconData icon,
    required Color color,
    required VoidCallback onTap,
  }) {
    return Card(
      margin: const EdgeInsets.only(bottom: 16),
      elevation: 2,
      child: ListTile(
        contentPadding: const EdgeInsets.all(16),
        leading: Container(
          width: 50,
          height: 50,
          decoration: BoxDecoration(
            color: color.withOpacity(0.2),
            borderRadius: BorderRadius.circular(12),
          ),
          child: Icon(icon, color: color, size: 28),
        ),
        title: Text(
          title,
          style: const TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.bold,
          ),
        ),
        subtitle: Text(subtitle),
        trailing: const Icon(Icons.arrow_forward_ios, size: 16),
        onTap: onTap,
      ),
    );
  }
}

// ContainerTransformDemo 类(见示例1)
class ContainerTransformDemo extends StatelessWidget {
  const ContainerTransformDemo({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('容器转换动画'),
      ),
      body: ListView.builder(
        itemCount: 10,
        padding: const EdgeInsets.all(16),
        itemBuilder: (context, index) {
          return Padding(
            padding: const EdgeInsets.only(bottom: 16),
            child: OpenContainer(
              closedElevation: 2,
              closedShape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              closedColor: Colors.blue.shade50,
              openColor: Colors.white,
              transitionDuration: const Duration(milliseconds: 500),
              closedBuilder: (context, action) {
                return ListTile(
                  leading: CircleAvatar(
                    backgroundColor: Colors.blue,
                    child: Text('${index + 1}'),
                  ),
                  title: Text('商品 ${index + 1}'),
                  subtitle: const Text('点击查看详情'),
                  trailing: const Icon(Icons.arrow_forward_ios, size: 16),
                );
              },
              openBuilder: (context, action) {
                return DetailPage(index: index);
              },
            ),
          );
        },
      ),
    );
  }
}

class DetailPage extends StatelessWidget {
  final int index;
  
  const DetailPage({super.key, required this.index});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('商品 ${index + 1} 详情'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.shopping_bag,
              size: 100,
              color: Colors.blue,
            ),
            const SizedBox(height: 20),
            Text(
              '商品 ${index + 1}',
              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            const Text('这是商品的详细信息'),
          ],
        ),
      ),
    );
  }
}

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

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

class _SharedAxisDemoState extends State<SharedAxisDemo> {
  int _currentIndex = 0;
  
  final List<String> _pages = ['首页', '分类', '购物车', '我的'];
  final List<IconData> _icons = [
    Icons.home,
    Icons.category,
    Icons.shopping_cart,
    Icons.person,
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('共享轴过渡'),
      ),
      body: PageTransitionSwitcher(
        duration: const Duration(milliseconds: 300),
        transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
          return SharedAxisTransition(
            animation: primaryAnimation,
            secondaryAnimation: secondaryAnimation,
            transitionType: SharedAxisTransitionType.horizontal,
            child: child,
          );
        },
        child: _buildPage(_currentIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        items: List.generate(
          _pages.length,
          (index) => BottomNavigationBarItem(
            icon: Icon(_icons[index]),
            label: _pages[index],
          ),
        ),
      ),
    );
  }

  Widget _buildPage(int index) {
    return Container(
      key: ValueKey(index),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(_icons[index], size: 100, color: Colors.blue),
            const SizedBox(height: 20),
            Text(
              _pages[index],
              style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
            ),
          ],
        ),
      ),
    );
  }
}

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

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

class _FadeThroughDemoState extends State<FadeThroughDemo> {
  int _selectedIndex = 0;
  
  final List<String> _items = ['图片', '视频', '音乐', '文档'];
  final List<IconData> _icons = [
    Icons.image,
    Icons.video_library,
    Icons.music_note,
    Icons.description,
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('淡入淡出过渡'),
      ),
      body: Column(
        children: [
          // 选项卡
          SizedBox(
            height: 60,
            child: ListView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: _items.length,
              padding: const EdgeInsets.all(8),
              itemBuilder: (context, index) {
                final isSelected = index == _selectedIndex;
                return Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 4),
                  child: ChoiceChip(
                    label: Text(_items[index]),
                    selected: isSelected,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() {
                          _selectedIndex = index;
                        });
                      }
                    },
                  ),
                );
              },
            ),
          ),
          
          // 内容区域
          Expanded(
            child: PageTransitionSwitcher(
              duration: const Duration(milliseconds: 300),
              transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
                return FadeThroughTransition(
                  animation: primaryAnimation,
                  secondaryAnimation: secondaryAnimation,
                  child: child,
                );
              },
              child: _buildContent(_selectedIndex),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildContent(int index) {
    return Container(
      key: ValueKey(index),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(_icons[index], size: 120, color: Colors.blue),
            const SizedBox(height: 20),
            Text(
              _items[index],
              style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Text(
              '这是${_items[index]}的内容区域',
              style: const TextStyle(color: Colors.grey),
            ),
          ],
        ),
      ),
    );
  }
}

// FadeScaleDemo 类
class FadeScaleDemo extends StatelessWidget {
  const FadeScaleDemo({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('淡出缩放'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            showModal(
              context: context,
              configuration: const FadeScaleTransitionConfiguration(),
              builder: (context) {
                return AlertDialog(
                  title: const Text('提示'),
                  content: const Text('这是一个使用淡出缩放动画的对话框'),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('取消'),
                    ),
                    TextButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('确定'),
                    ),
                  ],
                );
              },
            );
          },
          child: const Text('显示对话框'),
        ),
      ),
    );
  }
}

// CustomAnimationDemo 类
class CustomAnimationDemo extends StatelessWidget {
  const CustomAnimationDemo({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('自定义动画参数'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // 快速动画
          _buildAnimationCard(
            context,
            title: '快速动画',
            subtitle: '200ms',
            duration: const Duration(milliseconds: 200),
            color: Colors.red,
          ),
          
          // 标准动画
          _buildAnimationCard(
            context,
            title: '标准动画',
            subtitle: '300ms',
            duration: const Duration(milliseconds: 300),
            color: Colors.blue,
          ),
          
          // 慢速动画
          _buildAnimationCard(
            context,
            title: '慢速动画',
            subtitle: '500ms',
            duration: const Duration(milliseconds: 500),
            color: Colors.green,
          ),
        ],
      ),
    );
  }

  Widget _buildAnimationCard(
    BuildContext context, {
    required String title,
    required String subtitle,
    required Duration duration,
    required Color color,
  }) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16),
      child: OpenContainer(
        transitionDuration: duration,
        closedElevation: 2,
        closedShape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
        closedColor: color.withOpacity(0.1),
        openColor: Colors.white,
        closedBuilder: (context, action) {
          return ListTile(
            leading: CircleAvatar(
              backgroundColor: color,
              child: const Icon(Icons.timer, color: Colors.white),
            ),
            title: Text(title),
            subtitle: Text(subtitle),
            trailing: const Icon(Icons.arrow_forward_ios, size: 16),
          );
        },
        openBuilder: (context, action) {
          return Scaffold(
            appBar: AppBar(
              title: Text(title),
              backgroundColor: color,
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.timer, size: 100, color: color),
                  const SizedBox(height: 20),
                  Text(
                    title,
                    style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 10),
                  Text('动画时长: $subtitle'),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

完整示例说明

  1. 主页导航:展示所有动画类型
  2. 容器转换:列表到详情的平滑过渡
  3. 共享轴过渡:标签页之间的滑动切换
  4. 淡入淡出:内容替换的优雅过渡
  5. 淡出缩放:对话框的弹出动画
  6. 自定义参数:不同时长的动画对比

📊 API 详解

OpenContainer 核心参数

OpenContainer({
  required OpenContainerBuilder closedBuilder,  // 关闭状态构建器
  required OpenContainerBuilder openBuilder,    // 打开状态构建器
  bool tappable = true,                         // 是否可点击
  Duration transitionDuration = const Duration(milliseconds: 300),  // 动画时长
  Color closedColor = Colors.white,             // 关闭状态颜色
  Color openColor = Colors.white,               // 打开状态颜色
  double closedElevation = 1.0,                 // 关闭状态阴影
  double openElevation = 4.0,                   // 打开状态阴影
  ShapeBorder? closedShape,                     // 关闭状态形状
  ShapeBorder? openShape,                       // 打开状态形状
  Clip clipBehavior = Clip.antiAlias,           // 裁剪行为
})

SharedAxisTransition 核心参数

SharedAxisTransition({
  required Animation<double> animation,          // 主动画
  required Animation<double> secondaryAnimation, // 次动画
  required SharedAxisTransitionType transitionType,  // 过渡类型
  required Widget child,                         // 子组件
  bool fillColor = true,                         // 是否填充颜色
})

SharedAxisTransitionType 类型

  • horizontal:水平滑动
  • vertical:垂直滑动
  • scaled:缩放过渡

FadeThroughTransition 核心参数

FadeThroughTransition({
  required Animation<double> animation,          // 主动画
  required Animation<double> secondaryAnimation, // 次动画
  required Widget child,                         // 子组件
  bool fillColor = true,                         // 是否填充颜色
})

PageTransitionSwitcher 核心参数

PageTransitionSwitcher({
  required Widget child,                         // 子组件
  required PageTransitionSwitcherTransitionBuilder transitionBuilder,  // 过渡构建器
  Duration duration = const Duration(milliseconds: 300),  // 动画时长
  Duration reverseDuration = const Duration(milliseconds: 300),  // 反向时长
})

💡 最佳实践

1. 选择合适的动画类型

// ✅ 列表到详情:使用 OpenContainer
OpenContainer(
  closedBuilder: (context, action) => ListItem(),
  openBuilder: (context, action) => DetailPage(),
)

// ✅ 标签页切换:使用 SharedAxisTransition
SharedAxisTransition(
  transitionType: SharedAxisTransitionType.horizontal,
  // ...
)

// ✅ 内容替换:使用 FadeThroughTransition
FadeThroughTransition(
  // ...
)

2. 合理设置动画时长

// ✅ 快速交互:200-300ms
OpenContainer(
  transitionDuration: const Duration(milliseconds: 300),
  // ...
)

// ✅ 复杂动画:400-500ms
OpenContainer(
  transitionDuration: const Duration(milliseconds: 500),
  // ...
)

// ❌ 避免过长的动画
OpenContainer(
  transitionDuration: const Duration(seconds: 2),  // 太慢
  // ...
)

3. 使用 Key 确保动画正确

// ✅ 正确:使用 ValueKey
PageTransitionSwitcher(
  child: Container(
    key: ValueKey(_currentIndex),  // 重要!
    child: _buildPage(_currentIndex),
  ),
)

// ❌ 错误:没有 Key
PageTransitionSwitcher(
  child: _buildPage(_currentIndex),  // 动画可能不触发
)

4. 优化性能

// ✅ 正确:使用 const 构造函数
OpenContainer(
  closedBuilder: (context, action) {
    return const MyWidget();  // const
  },
  // ...
)

// ✅ 正确:避免在动画中重建复杂组件
class MyWidget extends StatelessWidget {
  const MyWidget({super.key});
  
  
  Widget build(BuildContext context) {
    // 组件会被缓存
    return const ExpensiveWidget();
  }
}

5. 处理返回导航

// ✅ 正确:OpenContainer 自动处理返回
OpenContainer(
  closedBuilder: (context, action) => ListItem(),
  openBuilder: (context, action) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.close),
          onPressed: action,  // 使用 action 关闭
        ),
      ),
      // ...
    );
  },
)

🐛 常见问题与解决方案

问题1:动画不流畅

现象

  • 动画卡顿
  • 掉帧明显

解决方案

// 1. 减少动画时长
OpenContainer(
  transitionDuration: const Duration(milliseconds: 250),  // 更短
  // ...
)

// 2. 使用 const 构造函数
closedBuilder: (context, action) {
  return const MyWidget();  // const
}

// 3. 避免在动画中进行耗时操作
openBuilder: (context, action) {
  return FutureBuilder(
    future: _loadData(),  // 异步加载
    builder: (context, snapshot) {
      // ...
    },
  );
}

问题2:动画没有触发

现象

  • PageTransitionSwitcher 没有动画
  • 内容直接切换

解决方案

// ✅ 正确:使用 ValueKey
PageTransitionSwitcher(
  child: Container(
    key: ValueKey(_currentIndex),  // 必须有 Key
    child: _buildPage(_currentIndex),
  ),
)

// ✅ 正确:确保 child 改变
setState(() {
  _currentIndex = newIndex;  // 触发重建
});

问题3:颜色闪烁

现象

  • 动画过程中出现白色闪烁
  • 背景色不连贯

解决方案

// ✅ 正确:设置一致的颜色
OpenContainer(
  closedColor: Colors.blue.shade50,
  openColor: Colors.blue.shade50,  // 相同颜色
  // ...
)

// ✅ 正确:使用透明色
SharedAxisTransition(
  fillColor: false,  // 不填充颜色
  // ...
)

🎓 总结

通过本文,你已经掌握了:

✅ animations 的核心概念和优势
✅ OpenContainer 容器转换动画
✅ SharedAxisTransition 共享轴过渡
✅ FadeThroughTransition 淡入淡出
✅ FadeScaleTransition 淡出缩放
✅ 自定义动画参数
✅ 完整的动画展示应用
✅ 最佳实践和常见问题解决方案

animations 让页面过渡变得流畅而专业!通过合理使用不同类型的动画,可以大大提升应用的用户体验和品质感。无论是电商应用、社交应用还是新闻应用,都能从中受益。


🔗 相关资源


转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2501_92193083/article/details/158393745

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--