
概述在 Flutter 开发中页面之间的过渡动画是提升用户体验的重要手段。Hero 动画是一种特殊的过渡动画它可以让同一个元素在不同页面之间平滑过渡创造出连贯的视觉效果。本章将详细介绍 Hero 动画的使用方法和各种实际应用场景。Hero 动画核心概念什么是 Hero 动画Hero 动画是 Flutter 中的一种页面过渡动画它允许我们将一个组件从一个页面飞到另一个页面。这个组件在源页面和目标页面中都需要有相同的 tagFlutter 会自动处理过渡效果。工作原理在源页面中用 Hero 组件包裹需要过渡的元素在目标页面中用相同 tag 的 Hero 组件包裹对应的元素当导航时Flutter 会自动创建过渡动画动画完成后目标页面的元素显示在最终位置基本用法// 源页面Hero(tag:hero-tag,child:Image.network(https://picsum.photos/200/200),)// 目标页面Hero(tag:hero-tag,child:Image.network(https://picsum.photos/400/400),)关键参数tag: Hero 标签必须在源页面和目标页面中相同child: 需要过渡的子组件createRectTween: 自定义过渡路径flightShuttleBuilder: 自定义过渡期间的组件实际应用场景场景一图片详情页过渡classHeroImagePageextendsStatelessWidget{constHeroImagePage({super.key});overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText(图片列表)),body:GridView.builder(gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:2,),itemCount:10,itemBuilder:(context,index){StringimageUrlhttps://picsum.photos/300/300?random$index;returnGestureDetector(onTap:(){Navigator.push(context,MaterialPageRoute(builder:(context)ImageDetailPage(imageUrl:imageUrl,index:index),),);},child:Hero(tag:image-$index,child:Image.network(imageUrl,fit:BoxFit.cover),),);},),);}}classImageDetailPageextendsStatelessWidget{finalStringimageUrl;finalint index;constImageDetailPage({super.key,requiredthis.imageUrl,requiredthis.index});overrideWidgetbuild(BuildContextcontext){returnScaffold(backgroundColor:Colors.black,body:Center(child:Hero(tag:image-$index,child:Image.network(imageUrl),),),);}}图片过渡要点使用唯一的 tag 标识每个图片源页面显示缩略图目标页面显示大图自动实现缩放和平移过渡场景二卡片详情页过渡classHeroCardPageextendsStatelessWidget{constHeroCardPage({super.key});overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText(卡片列表)),body:ListView.builder(itemCount:5,itemBuilder:(context,index){returnGestureDetector(onTap:(){Navigator.push(context,MaterialPageRoute(builder:(context)CardDetailPage(index:index),),);},child:Card(margin:constEdgeInsets.all(16),child:Hero(tag:card-$index,child:ListTile(leading:constIcon(Icons.star),title:Text(卡片$index),subtitle:constText(点击查看详情),),),),);},),);}}classCardDetailPageextendsStatelessWidget{finalint index;constCardDetailPage({super.key,requiredthis.index});overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text(卡片详情$index)),body:Center(child:Hero(tag:card-$index,child:Container(width:300,height:200,decoration:BoxDecoration(color:Colors.blue,borderRadius:BorderRadius.circular(16),),child:constCenter(child:Text(详情内容,style:TextStyle(color:Colors.white,fontSize:24),),),),),),);}}卡片过渡要点Hero 包裹整个 ListTile目标页面显示更大的卡片实现卡片展开效果场景三多个 Hero 同时过渡classMultiHeroPageextendsStatelessWidget{constMultiHeroPage({super.key});overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText(多元素过渡)),body:Center(child:GestureDetector(onTap:(){Navigator.push(context,MaterialPageRoute(builder:(context)constMultiHeroDetailPage()),);},child:Row(mainAxisAlignment:MainAxisAlignment.center,children:const[Hero(tag:icon-hero,child:Icon(Icons.favorite,size:40,color:Colors.red),),SizedBox(width:20),Hero(tag:text-hero,child:Text(点击过渡,style:TextStyle(fontSize:24)),),],),),),);}}classMultiHeroDetailPageextendsStatelessWidget{constMultiHeroDetailPage({super.key});overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText(多元素详情)),body:Column(mainAxisAlignment:MainAxisAlignment.center,children:const[Hero(tag:icon-hero,child:Icon(Icons.favorite,size:100,color:Colors.red),),SizedBox(height:40),Hero(tag:text-hero,child:Text(详情内容,style:TextStyle(fontSize:36)),),],),);}}多元素过渡要点多个 Hero 组件可以同时过渡每个 Hero 有独立的 tag各自独立动画自定义 Hero 动画自定义过渡路径Hero(tag:custom-hero,createRectTween:(begin,end){returnCustomRectTween(begin:begin!,end:end!);},child:constIcon(Icons.star),)classCustomRectTweenextendsRectTween{CustomRectTween({requiredsuper.begin,requiredsuper.end});overrideRectlerp(double t){double easedTCurves.easeInOut.transform(t);returnRect.fromLTRB(lerpDouble(begin!.left,end!.left,easedT)!,lerpDouble(begin!.top,end!.top,easedT)!,lerpDouble(begin!.right,end!.right,easedT)!,lerpDouble(begin!.bottom,end!.bottom,easedT)!,);}}自定义路径要点创建自定义 RectTween使用 Curves 控制动画曲线实现自定义的插值逻辑自定义过渡组件Hero(tag:custom-shuttle,flightShuttleBuilder:(flightContext,animation,flightDirection,fromHeroContext,toHeroContext,){returnRotationTransition(turns:animation,child:constIcon(Icons.star),);},child:constIcon(Icons.star),)自定义过渡要点flightShuttleBuilder 控制过渡期间的组件animation 参数控制动画进度flightDirection 指示过渡方向Hero 动画与 PageRouteBuilder自定义页面路由Navigator.push(context,PageRouteBuilder(pageBuilder:(context,animation,secondaryAnimation){returnconstDetailPage();},transitionsBuilder:(context,animation,secondaryAnimation,child){returnFadeTransition(opacity:animation,child:child,);},),);自定义路由要点使用 PageRouteBuilder 创建自定义路由pageBuilder 创建目标页面transitionsBuilder 创建过渡动画Hero 与自定义路由结合Navigator.push(context,PageRouteBuilder(pageBuilder:(context,animation,secondaryAnimation){returnconstDetailPage();},transitionsBuilder:(context,animation,secondaryAnimation,child){returnHero(tag:shared-hero,child:child,);},),);结合要点在 transitionsBuilder 中使用 Hero实现更复杂的过渡效果性能优化建议避免复杂的 Hero 子组件// 不好的做法复杂子组件Hero(tag:complex-hero,child:Container(child:Column(children:[Image.network(...),Text(...),Icon(...),],),),)// 好的做法简单子组件Hero(tag:simple-hero,child:Image.network(...),)使用 RepaintBoundaryHero(tag:repaint-hero,child:RepaintBoundary(child:Image.network(...),),)限制 Hero 数量// 不好的做法过多 HeroGridView.builder(itemBuilder:(context,index)Hero(tag:item-$index,child:ItemWidget(),),)// 好的做法只在需要时使用GridView.builder(itemBuilder:(context,index)ItemWidget(),)总结Hero 动画是 Flutter 中实现页面间共享元素过渡的核心组件通过它我们可以实现图片过渡从缩略图到大图的平滑过渡实现卡片过渡卡片展开效果多元素同时过渡多个元素独立动画自定义过渡效果自定义路径和组件掌握 Hero 动画的使用能够让我们创建出更加流畅和连贯的页面过渡效果。参考资料Flutter Hero 官方文档PageRouteBuilder 官方文档RectTween 官方文档