当前位置: 首页 > news >正文

Flutter Hero 动画完全指南

Flutter Hero 动画完全指南

引言

Hero 动画是 Flutter 中实现页面间元素过渡的强大工具,它允许元素在页面跳转时平滑地从一个页面"飞"到另一个页面。本文将深入探讨 Hero 动画的各种用法和高级技巧。

基础概念回顾

什么是 Hero 动画

Hero 动画是一种共享元素过渡效果,它让同一个 Widget 在两个页面之间平滑过渡。

基本语法

// 源页面 Hero( tag: 'hero-tag', child: Image.network('image.jpg'), ) // 目标页面 Hero( tag: 'hero-tag', child: Image.network('image.jpg'), )

高级技巧一:基本 Hero 动画

创建简单的 Hero 动画

class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('首页')), body: Center( child: Hero( tag: 'avatar', child: GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => DetailPage()), ); }, child: CircleAvatar( backgroundImage: NetworkImage('https://example.com/avatar.jpg'), radius: 50, ), ), ), ), ); } } class DetailPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('详情页')), body: Center( child: Hero( tag: 'avatar', child: CircleAvatar( backgroundImage: NetworkImage('https://example.com/avatar.jpg'), radius: 100, ), ), ), ); } }

自定义过渡效果

Hero( tag: 'custom-hero', createRectTween: (begin, end) { return CustomRectTween(begin: begin, end: end); }, child: MyWidget(), )

高级技巧二:Hero 动画样式

添加圆角

Hero( tag: 'rounded-image', child: ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network('image.jpg'), ), )

添加阴影

Hero( tag: 'shadow-hero', child: Container( decoration: BoxDecoration( boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 8, offset: Offset(2, 2), ), ], ), child: Image.network('image.jpg'), ), )

高级技巧三:多个 Hero 动画

并行过渡多个元素

// 源页面 Row( children: [ Hero( tag: 'image-hero', child: Image.network('image.jpg'), ), Hero( tag: 'text-hero', child: Text('标题'), ), ], ) // 目标页面 Column( children: [ Hero( tag: 'image-hero', child: Image.network('image.jpg'), ), Hero( tag: 'text-hero', child: Text('标题'), ), ], )

高级技巧四:Hero 动画控制器

使用 HeroController

MaterialApp( home: MyHomePage(), heroController: HeroController( createRectTween: (begin, end) { return MaterialRectArcTween(begin: begin, end: end); }, ), )

自定义过渡曲线

Hero( tag: 'curve-hero', flightShuttleBuilder: ( BuildContext flightContext, Animation<double> animation, HeroFlightDirection flightDirection, BuildContext fromHeroContext, BuildContext toHeroContext, ) { return ScaleTransition( scale: animation.drive( CurveTween(curve: Curves.easeInOut), ), child: Image.network('image.jpg'), ); }, child: Image.network('image.jpg'), )

实战案例:图片画廊

class GalleryPage extends StatelessWidget { final List<String> images = [ 'https://example.com/img1.jpg', 'https://example.com/img2.jpg', 'https://example.com/img3.jpg', ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('图片画廊')), body: GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), itemCount: images.length, itemBuilder: (context, index) { return GestureDetector( onTap: () { Navigator.push( context, PageRouteBuilder( pageBuilder: (_, __, ___) => DetailImagePage( imageUrl: images[index], index: index, ), ), ); }, child: Hero( tag: 'image-$index', child: Image.network( images[index], fit: BoxFit.cover, ), ), ); }, ), ); } } class DetailImagePage extends StatelessWidget { final String imageUrl; final int index; DetailImagePage({required this.imageUrl, required this.index}); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: Center( child: Hero( tag: 'image-$index', child: Image.network(imageUrl), ), ), ); } }

实战案例:卡片详情页

class CardListPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('卡片列表')), body: ListView.builder( itemCount: 10, itemBuilder: (context, index) { return Card( child: ListTile( leading: Hero( tag: 'avatar-$index', child: CircleAvatar( child: Text('${index + 1}'), ), ), title: Hero( tag: 'title-$index', child: Text('卡片 ${index + 1}'), ), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => CardDetailPage(index: index), ), ); }, ), ); }, ), ); } } class CardDetailPage extends StatelessWidget { final int index; CardDetailPage({required this.index}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Hero(tag: 'title-$index', child: Text('卡片 ${index + 1}'))), body: Center( child: Hero( tag: 'avatar-$index', child: CircleAvatar( radius: 80, child: Text('${index + 1}'), ), ), ), ); } }

实战案例:Hero 动画与页面路由

class CustomHeroRoute extends PageRouteBuilder { final Widget page; CustomHeroRoute({required this.page}) : super( pageBuilder: (context, animation, secondaryAnimation) => page, transitionsBuilder: (context, animation, secondaryAnimation, child) { return FadeTransition( opacity: animation, child: child, ); }, ); } // 使用 Navigator.push( context, CustomHeroRoute(page: DetailPage()), );

常见问题与解决方案

Q1:Hero 动画不生效?

A:确保两个页面的 Hero tag 相同:

// 正确 Hero(tag: 'same-tag', child: ...) // 错误 Hero(tag: 'tag1', child: ...) // 源页面 Hero(tag: 'tag2', child: ...) // 目标页面

Q2:如何自定义过渡路径?

A:使用 createRectTween:

Hero( tag: 'custom-path', createRectTween: (begin, end) { return MaterialRectCenterArcTween(begin: begin, end: end); }, child: ..., )

Q3:如何在 Hero 动画期间显示遮罩?

A:使用 flightShuttleBuilder:

Hero( tag: 'mask-hero', flightShuttleBuilder: (context, animation, flightDirection, from, to) { return Material( color: Colors.black54, child: Image.network('image.jpg'), ); }, child: Image.network('image.jpg'), )

最佳实践

1. 使用唯一的 tag

// 推荐 Hero(tag: 'user-avatar-${user.id}', child: ...) // 不推荐 Hero(tag: 'avatar', child: ...) // 多个元素使用相同 tag

2. 保持子 Widget 类型一致

// 推荐 Hero(tag: 'image', child: Image.network('url')) // 两边都是 Image // 不推荐 Hero(tag: 'image', child: Image.network('url')) // 源页面 Hero(tag: 'image', child: Container()) // 目标页面

3. 避免复杂的 Hero 子 Widget

// 推荐 Hero( tag: 'simple-hero', child: Image.network('url'), // 简单 Widget ) // 不推荐 Hero( tag: 'complex-hero', child: Container( child: Column( children: [...], // 复杂嵌套 ), ), )

总结

Flutter 的 Hero 动画是创建流畅页面过渡的强大工具。通过本文的学习,你应该能够:

  1. 创建基本的 Hero 动画
  2. 实现多个元素的并行过渡
  3. 自定义过渡效果和路径
  4. 处理常见问题
  5. 遵循最佳实践

掌握这些技巧,能够帮助你创建更加吸引人的用户体验。

http://www.jsqmd.com/news/814414/

相关文章:

  • 2026年西安画册印刷厂与活页环装定制深度横评:5大高新技术企业选购指南 - 年度推荐企业名录
  • 告别CNN!用PyG Temporal和GC-LSTM搞定动态社交网络的好友推荐(附完整代码)
  • PEG-b-PLA胶束定制服务:满足多场景纳米载体需求
  • 深度学习大师课 第 1 课:什么是深度学习?纯手写你的第一个神经网络
  • 特色体验拉满!2026安徽漂流推荐排行 四季运营/文化融合/网红打卡 - 极欧测评
  • 八大网盘直链解析完整指南:告别限速困扰,获取真实下载地址
  • 基于Next.js与Supabase构建AI智能体优先的问答竞技平台
  • 唯一客服 SCRM:独立部署的Golang企业微信SCRM源码
  • 魔兽争霸3游戏优化终极指南:3步解决帧率限制与界面显示问题
  • Android开源生态重构:从中心化控制到社区驱动的技术路径与挑战
  • 对接过百个医院项目,告诉你医院污水处理设备厂家怎么挑 - 速递信息
  • Midjourney提示词不再孤岛:如何用Notion AI自动结构化生成+同步至ComfyUI节点图+反向标注至Figma设计系统(含私有化部署避坑清单)
  • 2026年度国内流量计公司推荐权威排行榜:五大头部企业硬核实力全拆解 - 速递信息
  • 微信小程序逆向工程:wxappUnpacker技术深度解析与实战指南
  • 基于MCP协议与Gemini大模型构建智能命令行AI助手
  • 网盘直链下载助手终极指南:一键解锁八大平台高速下载限制
  • 东营油城筑家:郑春红与加西亚质感砖家装之选 - 品牌企业推荐师(官方)
  • 2026亲测!安亭正规美容院大揭秘,效果杠杠滴 - 速递信息
  • FPGA/CPLD调试实战:用嵌入式逻辑分析仪让高速数字信号“慢下来”
  • STM32F407的CAN中断到底怎么用?HAL库实战配置与常见回调函数避坑指南
  • Kubernetes智能运维助手:基于LLM的kube-copilot实战指南
  • Logisim-evolution终极指南:从数字电路新手到硬件设计高手
  • 2026年牛津布厂家推荐:东莞仁泰纺织/PVC/涤纶/尼龙/PU牛津布全品类供应 - 品牌企业推荐师(官方)
  • 在Azure DevOps Server中实现用户端原地址透传(X-Forward-For)
  • 手把手教你用Arduino UNO驱动LD3320语音模块(附完整代码与SPI避坑指南)
  • 如何优雅地从九大网盘获取真实下载地址:一个JavaScript工具的深度解析
  • Kibana启动失败?别慌!从版本兼容到防火墙,保姆级排查手册(附最新兼容性列表)
  • 2026年西安活页环装画册定制指南:源头工厂vs传统印刷厂的深度横评与选型方案 - 年度推荐企业名录
  • opencv 去畸变
  • Web3开发者技能图谱:从智能合约到dApp全栈实战指南