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

CustomScrollView嵌套SliverGrid滚动时频繁重建优化问题求助

解决SliverGrid滚动时网格项频繁重建的问题

问题描述

在CustomScrollView中嵌套SliverGrid.builder后,滚动时网格项会频繁重建,导致体验不佳(滚动过程中图片反复加载闪烁)。核心代码如下:

CustomScrollView(
  slivers: [
    // 其他Sliver组件
    SliverGrid.builder(
      itemCount: n,
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        crossAxisSpacing: 8,
        mainAxisSpacing: 8,
      ),
      itemBuilder: (context, index) {
        return Image(
          image: PhotoProvider(photoId[index]),
          fit: BoxFit.cover,
        );
      }
    ),
  ],
)

其中PhotoProvider是自定义图片提供者,用于从Firebase Storage获取图片。尝试给CustomScrollView添加cacheExtent后,滚动出现卡顿;查看Chrome Network标签发现,滚动时会生成请求,但图片实际从浏览器缓存获取。

解决方案

1. 给网格项添加唯一Key,避免误重建

SliverGrid.builder默认按位置复用组件,但缺少唯一Key时容易触发不必要的重建。给每个Image组件绑定以photoId为标识的ValueKey:

itemBuilder: (context, index) {
  final currentPhotoId = photoId[index];
  return Image(
    key: ValueKey(currentPhotoId),
    image: PhotoProvider(currentPhotoId),
    fit: BoxFit.cover,
  );
}

2. 优化自定义PhotoProvider的缓存逻辑

在PhotoProvider内部实现内存缓存,确保相同photoId的图片不会重复发起请求:

class PhotoProvider extends ImageProvider<PhotoProvider> {
  final String photoId;
  static final _memoryCache = <String, ImageStreamCompleter>{};

  PhotoProvider(this.photoId);

  @override
  Future<PhotoProvider> obtainKey(ImageConfiguration configuration) {
    return SynchronousFuture(this);
  }

  @override
  ImageStreamCompleter loadImage(PhotoProvider key, ImageDecoderCallback decode) {
    // 优先从缓存读取
    if (_memoryCache.containsKey(key.photoId)) {
      return _memoryCache[key.photoId]!;
    }
    // 从Firebase Storage加载图片的逻辑
    final completer = MultiFrameImageStreamCompleter(
      codec: _fetchFirebaseImage(key.photoId, decode),
      scale: 1.0,
    );
    // 存入缓存
    _memoryCache[key.photoId] = completer;
    return completer;
  }

  // 实现_fetchFirebaseImage方法,从Firebase获取图片并解码
  Future<Codec> _fetchFirebaseImage(String photoId, ImageDecoderCallback decode) async {
    // 补充Firebase Storage的图片获取逻辑
    final url = await FirebaseStorage.instance.ref(photoId).getDownloadURL();
    final response = await http.get(Uri.parse(url));
    return decode(response.bodyBytes);
  }
}

3. 替换为成熟的缓存图片库(推荐)

如果自定义Provider的缓存逻辑维护成本高,可使用cached_network_image包,它内置内存+磁盘双层缓存,能有效减少重复请求和闪烁:

  1. 添加依赖:
dependencies:
  cached_network_image: ^3.3.0
  1. 替换原有Image组件:
import 'package:cached_network_image/cached_network_image.dart';
import 'package:firebase_storage/firebase_storage.dart';

// ...

itemBuilder: (context, index) {
  return FutureBuilder<String>(
    future: FirebaseStorage.instance.ref(photoId[index]).getDownloadURL(),
    builder: (context, snapshot) {
      if (snapshot.hasData) {
        return CachedNetworkImage(
          key: ValueKey(snapshot.data!),
          imageUrl: snapshot.data!,
          fit: BoxFit.cover,
          placeholder: (context, url) => Container(color: Colors.grey[200]),
        );
      }
      return Container(color: Colors.grey[200]);
    },
  );
}

4. 合理设置cacheExtent

避免设置过大的cacheExtent导致内存过载,可尝试设置为屏幕高度的1.5-2倍,平衡预加载和内存占用:

CustomScrollView(
  cacheExtent: MediaQuery.of(context).size.height * 1.5,
  slivers: [/* ... */],
)

内容的提问来源于stack exchange,提问作者user2232305

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 02:06:18