Flutter Hero动画完全指南:打造流畅页面过渡
·
引言
Hero动画是Flutter中最具特色的动画效果之一,它允许元素在页面之间平滑过渡。通过Hero动画,我们可以创建令人印象深刻的用户体验,使页面切换更加流畅和直观。
一、Hero动画基础
1.1 什么是Hero动画
Hero动画是一种共享元素过渡效果,允许同一个Widget在两个页面之间平滑移动和变形。
// 源页面
Hero(
tag: 'image-hero',
child: Image.network('https://example.com/image.jpg'),
)
// 目标页面
Hero(
tag: 'image-hero',
child: Image.network('https://example.com/image.jpg'),
)
1.2 核心概念
| 概念 | 说明 |
|---|---|
| Hero | 共享元素Widget |
| tag | 唯一标识符,用于匹配两个页面的Hero |
| flightShuttleBuilder | 自定义过渡期间的Widget |
| placeholderBuilder | 源页面的占位符 |
| transitionOnUserGestures | 是否响应用户手势 |
1.3 基本用法
// 页面1
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: GestureDetector(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const DetailPage())),
child: Hero(
tag: 'avatar',
child: CircleAvatar(
backgroundImage: const NetworkImage('https://example.com/avatar.jpg'),
radius: 50,
),
),
),
);
}
}
// 页面2
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Detail')),
body: Center(
child: Hero(
tag: 'avatar',
child: CircleAvatar(
backgroundImage: const NetworkImage('https://example.com/avatar.jpg'),
radius: 100,
),
),
),
);
}
}
二、高级Hero动画
2.1 自定义飞行路径
Hero(
tag: 'custom-hero',
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
return ScaleTransition(
scale: animation.drive(
Tween<double>(begin: 1.0, end: 1.5).chain(CurveTween(curve: Curves.easeOut)),
),
child: const Icon(Icons.star, size: 100, color: Colors.yellow),
);
},
child: const Icon(Icons.star, size: 50, color: Colors.yellow),
)
2.2 形状变化动画
// 圆形到矩形的过渡
Hero(
tag: 'shape-transition',
child: Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(50),
),
),
)
// 目标页面
Hero(
tag: 'shape-transition',
child: Container(
width: 300,
height: 200,
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(16),
),
),
)
2.3 渐变背景过渡
Hero(
tag: 'gradient-hero',
child: Container(
width: 150,
height: 150,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.pink, Colors.purple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
),
),
)
三、复杂场景
3.1 多个Hero动画
// 源页面
Row(
children: [
Hero(tag: 'image', child: Image.network('https://example.com/img.jpg')),
Hero(tag: 'title', child: const Text('Title')),
],
)
// 目标页面
Column(
children: [
Hero(tag: 'image', child: Image.network('https://example.com/img.jpg')),
Hero(tag: 'title', child: const Text('Title')),
],
)
3.2 嵌套Hero动画
Hero(
tag: 'outer',
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Hero(
tag: 'inner',
child: Container(
width: 100,
height: 100,
color: Colors.white,
),
),
),
)
3.3 自定义过渡动画
Hero(
tag: 'custom-transition',
createRectTween: (begin, end) {
return CustomRectTween(begin: begin!, end: end!);
},
child: const Icon(Icons.flight),
)
class CustomRectTween extends RectTween {
CustomRectTween({required super.begin, required super.end});
@override
Rect lerp(double t) {
final curvedT = Curves.easeInOut.transform(t);
return Rect.lerp(begin, end, curvedT)!;
}
}
四、实战案例
4.1 图片画廊
class GalleryPage extends StatelessWidget {
const GalleryPage({super.key});
@override
Widget build(BuildContext context) {
final images = [
'https://example.com/img1.jpg',
'https://example.com/img2.jpg',
'https://example.com/img3.jpg',
];
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: images.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () => Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => DetailImagePage(url: images[index]),
transitionsBuilder: (_, animation, __, child) {
return FadeTransition(opacity: animation, child: child);
},
),
),
child: Hero(
tag: images[index],
child: Image.network(images[index], fit: BoxFit.cover),
),
);
},
);
}
}
4.2 卡片展开效果
class CardHero extends StatelessWidget {
const CardHero({super.key});
@override
Widget build(BuildContext context) {
return Hero(
tag: 'card',
child: Material(
elevation: 4,
borderRadius: BorderRadius.circular(16),
child: Container(
width: 200,
height: 150,
padding: const EdgeInsets.all(16),
child: const Column(
children: [
Text('Card Title', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('Card description text'),
],
),
),
),
);
}
}
4.3 圆形头像到全屏
// 源页面
Hero(
tag: 'profile',
child: const CircleAvatar(
backgroundImage: NetworkImage('https://example.com/profile.jpg'),
radius: 30,
),
)
// 目标页面
Hero(
tag: 'profile',
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.5,
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://example.com/profile.jpg'),
fit: BoxFit.cover,
),
),
),
)
五、性能优化
5.1 使用RepaintBoundary
Hero(
tag: 'optimized-hero',
child: RepaintBoundary(
child: Image.network('https://example.com/image.jpg'),
),
)
5.2 避免复杂Widget
// 避免:复杂Widget作为Hero child
Hero(
tag: 'bad-hero',
child: Container(
child: Column(
children: [/* 复杂内容 */],
),
),
)
// 推荐:使用简单Widget
Hero(
tag: 'good-hero',
child: Image.network('https://example.com/image.jpg'),
)
5.3 使用transitionOnUserGestures
Hero(
tag: 'gesture-hero',
transitionOnUserGestures: true,
child: const Icon(Icons.share),
)
六、最佳实践
6.1 标签命名规范
// 推荐:使用有意义的唯一标签
Hero(tag: 'product-image-${product.id}', child: ...)
// 避免:重复或无意义的标签
Hero(tag: 'image', child: ...)
6.2 过渡一致性
// 保持源和目标的内容一致
Hero(tag: 'avatar', child: Image.network(url))
// 目标页面也使用相同的图片
Hero(tag: 'avatar', child: Image.network(url))
6.3 测试动画效果
void main() {
testWidgets('Hero animation works', (tester) async {
await tester.pumpWidget(MaterialApp(home: const HomePage()));
await tester.tap(find.byType(Hero));
await tester.pumpAndSettle();
expect(find.byType(DetailPage), findsOneWidget);
});
}
七、总结
Hero动画是Flutter中强大的过渡效果,能够创建流畅的页面切换体验。通过合理使用Hero动画,可以提升应用的用户体验。
关键要点:
- 使用唯一的tag标识Hero
- 保持源和目标Widget的一致性
- 使用flightShuttleBuilder自定义过渡
- 考虑性能优化
- 测试动画效果
掌握Hero动画,将使你的Flutter应用更加生动和专业。
更多推荐
所有评论(0)