Flutter使用photo_manager实现相册Hero动画跳转时图片闪烁问题求助
问题原因
- 详情页图片源切换时机冲突:Hero动画执行期间,详情页异步加载完高清原图会立即替换初始的缩略图源,切换动作刚好发生在Hero动画结束的交接节点,即使开启了
gaplessPlayback,不同ImageProvider的解码切换也会触发视觉闪烁。 - 网格页缩略图重复加载:
GalleryThumbnail使用无缓存的FutureBuilder,每次组件重建(比如从详情页回退、网格滚动回收)都会重新请求缩略图,请求到数据前的空白帧会导致回退动画结束后闪烁。 - 退场动画图像源不一致:反向退回时详情页使用的是高清图源,和网格页的缩略图源不匹配,Hero交接时出现图像跳变。
修复方案
1. 新增缩略图全局缓存
避免每次组件重建都重复请求缩略图,先在全局定义缓存容器:
// 全局定义缩略图缓存,key为资源id final Map<String, Uint8List> _thumbCache = {};
修改GalleryThumbnail中Future的请求逻辑,优先读缓存:
future: () async { // 已有缓存直接返回 if (_thumbCache.containsKey(asset.id)) return _thumbCache[asset.id]; final Uint8List? bytes; if (Platform.isIOS) { bytes = await asset.thumbDataWithOption( ThumbOption.ios( width: 500, height: 500, deliveryMode: DeliveryMode.opportunistic, resizeMode: ResizeMode.fast, resizeContentMode: ResizeContentMode.fit, quality: 100), ); } else { bytes = await asset.thumbDataWithSize(250, 250); } // 请求完成存入缓存 if (bytes != null) _thumbCache[asset.id] = bytes; return bytes; }(),
2. 调整详情页图片切换时机
等Hero入场动画完全结束后再加载高清图,反向退场前切回缩略图保证源一致,修改DetailsPage的State代码:
class _DetailsPageState extends State<DetailsPage> { late ImageProvider _thumbProvider; ImageProvider? _originProvider; bool _animationCompleted = false; Future<void> loadFile() async { try { File? file = await widget.asset.file; // 动画未完成时不替换图片 if (file == null || !_animationCompleted) return; _originProvider = Image.file(file).image; setState(() {}); } catch (e) { print("error to load file : " + e.toString()); } } @override void initState() { super.initState(); _thumbProvider = Image.memory(widget.bytes).image; // 监听路由动画状态 WidgetsBinding.instance.addPostFrameCallback((_) { ModalRoute.of(context)?.animation?.addStatusListener((status) { // 入场动画完成后再加载高清图 if (status == AnimationStatus.completed) { _animationCompleted = true; loadFile(); } // 检测到退场动画,切回缩略图保证和网格页源一致 if (status == AnimationStatus.reverse) { setState(() { _originProvider = null; }); } }); }); } @override Widget build(BuildContext context) { double ratio = widget.asset.height / widget.asset.width; return Scaffold( body: Stack( children: [ Hero( createRectTween: (Rect? begin, Rect? end) { RectTween _rectTween = RectTween(begin: begin, end: end); return _rectTween; }, tag: widget.asset.id, child: Center( child: Image( image: _originProvider ?? _thumbProvider, width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height*ratio, gaplessPlayback: true, // 和网格页保持fit属性一致 fit: BoxFit.cover), ), ), const SafeArea( child: Padding( padding: EdgeInsets.all(8.0), child: BackButton(), )), ], ), ); } }
3. 可选优化:增大GridView缓存范围
避免快速滚动、回退时item被过早回收触发重绘:
GridView.builder( cacheExtent: 1000, // 新增缓存范围,可根据实际需求调整 itemCount: assetsList.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), // 其余原有逻辑不变 )
内容的提问来源于stack exchange,提问作者nicover
相关产品推荐
相关产品推荐

