
欢迎加入开源鸿蒙跨平台社区: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'),
],
),
),
);
},
),
);
}
}
完整示例说明:
- 主页导航:展示所有动画类型
- 容器转换:列表到详情的平滑过渡
- 共享轴过渡:标签页之间的滑动切换
- 淡入淡出:内容替换的优雅过渡
- 淡出缩放:对话框的弹出动画
- 自定义参数:不同时长的动画对比
📊 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



