如何在Flutter中实现Image从BoxFit.cover到BoxFit.contain的流畅Hero动画
问题原因
Flutter官方Hero组件默认仅对位置、尺寸属性做动画插值,不会自动处理BoxFit枚举的过渡逻辑。当图库页图片使用BoxFit.cover适配、预览页使用BoxFit.contain适配时,动画起止帧会直接切换适配规则,就会出现生硬跳转的问题。
修复方案
通过自定义Hero的flightShuttleBuilder参数接管动画过程的组件渲染,即可实现适配规则的平滑过渡,修改后的完整可运行代码如下:
// main.dart import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart' show timeDilation; const String url = 'https://images.unsplash.com/photo-1635335333546-41f848cea96c'; void main() { runApp( const MaterialApp( home: GalleryWidget(), ), ); } class GalleryWidget extends StatelessWidget { const GalleryWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { timeDilation = 10.0; return Scaffold( appBar: AppBar( title: const Text('Gallery'), ), body: Material( child: InkWell( child: Hero( tag: url, // 新增动画穿梭器自定义逻辑 flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) { final isPush = flightDirection == HeroFlightDirection.push; return AnimatedBuilder( animation: animation, builder: (context, child) { final progress = isPush ? animation.value : 1 - animation.value; return Image.network( url, fit: BoxFit.values.lerp(BoxFit.cover, BoxFit.contain, progress), ); }, ); }, child: Image.network( url, width: 100.0, height: 100.0, fit: BoxFit.cover, ), ), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const PhotoViewerWidget(url: url), fullscreenDialog: true, ), ); }, ), ), ); } } class PhotoViewerWidget extends StatelessWidget { final String url; const PhotoViewerWidget({Key? key, required this.url}) : super(key: key); @override Widget build(BuildContext context) { return Material( color: Colors.black87, child: InkWell( child: Hero( tag: url, child: Image.network( url, fit: BoxFit.contain, ), ), onTap: () => Navigator.of(context).pop(), ), ); } }
效果说明
上述代码通过BoxFit.values.lerp方法对适配模式做动画插值,配合AnimatedBuilder监听动画进度实时更新图片适配规则,动画过程不会再出现突兀的适配切换效果。如果需要后续添加缩放查看细节的能力,可在预览页嵌套InteractiveViewer组件实现。
内容的提问来源于stack exchange,提问作者Ryan Hoo
相关产品推荐
相关产品推荐

