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

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( Tween<double>(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 State<InteractiveHero> { 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, ), ), ), ); } }

四、性能优化

  1. 使用 const 构造器:减少不必要的重建
  2. 控制动画时长:避免过长的动画
  3. 使用 RepaintBoundary:隔离重绘区域
  4. 优化图片:使用适当大小的图片

五、最佳实践

  1. 保持 tag 唯一:每个 Hero 动画必须有唯一的 tag
  2. 语义化标签:使用有意义的 tag 命名
  3. 测试边界情况:确保动画在各种情况下都能正常工作
  4. 考虑可访问性:为动画提供适当的降级方案

Hero 动画是页面间的桥梁,让用户体验更加连贯流畅。

#flutter #hero-animation #transition #ui #animation

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

相关文章:

  • OpenClaw+Kimi-VL-A3B-Thinking:个人博客自动化图文更新
  • MySQL 8.0新特性高频面试题 30 道(超详细答案)
  • 爱毕业aibye发布六大领先学术平台,提供智能改写和高效写作支持,加速科研进程
  • PSoC Creator 4.4 + CapSense调参避坑指南:从Tuner工具到稳定触摸的5个关键步骤
  • 深入解析TMC2660驱动芯片:SPI接口与步进电机精准控制实践
  • 光谱特征选择实战:UVE算法原理、实现与避坑指南
  • 别再用chmod 777了!Linux文件权限管理的5个专业姿势(从Permission denied说开去)
  • 行业知名的半导体行业展会哪个比较好?半导体行业展会甄选 - 品牌2026
  • 从习题到实践:用谢希仁《计算机网络原理》第二章核心概念解析真实网络场景
  • 千问3.5-27B镜像调优指南:提升OpenClaw任务执行稳定性
  • OpenClaw安全方案:Qwen3-4B-Thinking-2507-GPT-5-Codex-Distill-GGUF本地化处理敏感数据
  • SEO引擎排名优化的重要性是什么
  • 告别打印乱码与错位:手把手教你配置SAP Smartforms的CNSAPWIN打印机格式
  • 星图平台OpenClaw镜像体验:Qwen3-32B+RTX4090D免安装方案
  • 从零到一:基于gemma-3-12b-it的OpenClaw自动化测试框架搭建
  • 【Delft3D FM数据后处理系列】1. 从Map.nc到精美网格图:Matlab与Surfer的进阶可视化实践
  • 芯片研发里的AI Agent总在跑偏?问题出在这里
  • Flutter 导航深度解析:从入门到精通
  • OpenClaw多实例管理:Phi-3-vision-128k-instruct专用网关的隔离部署
  • SAP增强中多线程STARTING NEW TASK实现BAPI事务提交的实践指南
  • 深入解析Google AutoService:组件化通信的轻量级解决方案
  • 二维码识别性能优化:UniApp中canvas截取与qrcode.js的黄金参数配置
  • Linux文件系统探秘:当你删除一个文件时,inode位图究竟发生了什么变化?
  • 别再为OSGB发愁了!手把手教你用Cesium + 3DTiles在Vue3/Vite项目中搭建三维场景
  • 从一次系统宕机说起:深入FPGA异步复位释放的“亚稳态”陷阱与救赎之路
  • SEO_为什么你的SEO效果不好?关键原因分析
  • 手把手拆解:从浮栅晶体管到你的SD卡,Flash闪存‘写1擦0’全流程保姆级图解
  • Python实战:5分钟搞定MODIS数据NDVI提取(附完整代码)
  • seo咨询服务需要多长时间_seo咨询服务如何进行技术优化
  • 西门子1200系列Modbus RTU通讯及485通讯轮询的实践案例