Flutter Hero 动画:页面间的无缝过渡

张开发
2026/4/6 2:36:46 15 分钟阅读

分享文章

Flutter Hero 动画:页面间的无缝过渡
Flutter Hero 动画页面间的无缝过渡让元素在页面间优雅地飞舞创造令人难忘的视觉体验。一、Hero 动画的魅力作为一名追求像素级还原的 UI 匠人我深知过渡动画的重要性。Hero 动画让用户在不同页面间感受到视觉上的连续性就像魔术一样一个元素从一个页面飞到另一个页面创造出流畅的叙事体验。二、基础实现1. 源页面import package:flutter/material.dart; class SourcePage extends StatelessWidget { override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(产品列表)), body: GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, ), itemCount: 6, itemBuilder: (context, index) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) DetailPage(index: index), ), ); }, child: Hero( tag: product-$index, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), image: DecorationImage( image: NetworkImage( https://picsum.photos/seed/$index/300/300, ), fit: BoxFit.cover, ), ), ), ), ); }, ), ); } }2. 目标页面class DetailPage extends StatelessWidget { final int index; DetailPage({required this.index}); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(产品详情)), body: SingleChildScrollView( child: Column( children: [ Hero( tag: product-$index, child: Container( height: 300, decoration: BoxDecoration( image: DecorationImage( image: NetworkImage( https://picsum.photos/seed/$index/600/400, ), fit: BoxFit.cover, ), ), ), ), Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 产品名称 $index, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), SizedBox(height: 16), Text( 这是产品 $index 的详细描述。包含了产品的各种特性和使用方法。, style: TextStyle(fontSize: 16), ), SizedBox(height: 24), ElevatedButton( onPressed: () { Navigator.pop(context); }, child: Text(返回), ), ], ), ), ], ), ), ); } }三、高级用法1. 自定义 Hero 动画class CustomHeroAnimation extends StatelessWidget { final String tag; final Widget child; CustomHeroAnimation({required this.tag, required this.child}); override Widget build(BuildContext context) { return Hero( tag: tag, createRectTween: (begin, end) { return MaterialRectArcTween(begin: begin, end: end); }, flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) { return ScaleTransition( scale: animation.drive( Tweendouble(begin: 0.8, end: 1.0).chain( CurveTween(curve: Curves.easeInOut), ), ), child: child, ); }, child: child, ); } }2. 多个 Hero 动画// 源页面 class SourcePage extends StatelessWidget { override Widget build(BuildContext context) { return GestureDetector( onTap: () Navigator.push( context, MaterialPageRoute(builder: (context) DetailPage()), ), child: Column( children: [ Hero( tag: image, child: Container( width: 100, height: 100, decoration: BoxDecoration( shape: BoxShape.circle, image: DecorationImage( image: NetworkImage(https://picsum.photos/200/200), fit: BoxFit.cover, ), ), ), ), Hero( tag: title, child: Material( type: MaterialType.transparency, child: Text( 产品标题, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), ), ], ), ); } } // 目标页面 class DetailPage extends StatelessWidget { override Widget build(BuildContext context) { return Column( children: [ Hero( tag: image, child: Container( width: 300, height: 300, decoration: BoxDecoration( shape: BoxShape.circle, image: DecorationImage( image: NetworkImage(https://picsum.photos/600/600), fit: BoxFit.cover, ), ), ), ), Hero( tag: title, child: Material( type: MaterialType.transparency, child: Text( 产品标题, style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), ), ), ), ], ); } }3. 共享元素转场// 路由配置 MaterialPageRoute( builder: (context) DetailPage(), settings: RouteSettings(name: /detail), fullscreenDialog: true, ); // 手势控制 class InteractiveHero extends StatefulWidget { override _InteractiveHeroState createState() _InteractiveHeroState(); } class _InteractiveHeroState extends StateInteractiveHero { double _scale 1.0; override Widget build(BuildContext context) { return GestureDetector( onScaleUpdate: (details) { setState(() { _scale details.scale; }); }, onTap: () { Navigator.push( context, MaterialPageRoute(builder: (context) DetailPage()), ); }, child: Transform.scale( scale: _scale, child: Hero( tag: interactive, child: Container( width: 200, height: 200, color: Colors.blue, ), ), ), ); } }四、性能优化使用 const 构造器减少不必要的重建控制动画时长避免过长的动画使用 RepaintBoundary隔离重绘区域优化图片使用适当大小的图片五、最佳实践保持 tag 唯一每个 Hero 动画必须有唯一的 tag语义化标签使用有意义的 tag 命名测试边界情况确保动画在各种情况下都能正常工作考虑可访问性为动画提供适当的降级方案Hero 动画是页面间的桥梁让用户体验更加连贯流畅。#flutter #hero-animation #transition #ui #animation

更多文章