You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 07:36:08