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

Flutter中如何避免ListView Builder加载新数据时刷新整个列表

解决方案:实现列表增量加载且避免全量刷新

核心问题分析

你的列表刷新问题主要来自两个点:

  1. setState触发后,ListView.builder会重新构建所有可见列表项,而CustomBottomBigCard中的FutureBuilder每次都会重新执行传入的imgUrl Future,导致图片反复加载、卡片闪烁。
  2. 当前的加载触发逻辑没有防重复请求控制,滚动时可能多次调用fetchData。

以下是针对性的修改方案:


1. 缓存图片请求Future,避免重复执行

将CustomBottomBigCard改为Stateful组件,在initState中缓存图片URL的Future,防止每次卡片构建时重新发起请求:

class CustomBottomBigCard extends StatefulWidget {
  const CustomBottomBigCard({
    Key? key,
    required this.watchID,
    required this.screenWidth,
    required this.walletWatch,
    required this.imgUrlFuture,
  }) : super(key: key);

  final int watchID;
  final double screenWidth;
  final WalletWatch walletWatch;
  final Future<String> imgUrlFuture;

  @override
  State<CustomBottomBigCard> createState() => _CustomBottomBigCardState();
}

class _CustomBottomBigCardState extends State<CustomBottomBigCard> {
  late Future<String> _cachedImgFuture;

  @override
  void initState() {
    super.initState();
    // 仅在初始化时缓存Future,避免重复执行
    _cachedImgFuture = widget.imgUrlFuture;
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.of(context).pushNamed(WatchScreen.id, arguments: Watch(
          watchId: widget.walletWatch.watchId,
          condition: widget.walletWatch.condition,
          numberOfShares: widget.walletWatch.numberOfShares,
          retailPrice: widget.walletWatch.retailPrice,
          initialPrice: widget.walletWatch.initialPrice,
          actualPrice: widget.walletWatch.actualPrice,
          dialcolor: widget.walletWatch.dialcolor,
          year: widget.walletWatch.year,
          imageuri: widget.walletWatch.imageuri,
          description: widget.walletWatch.description,
          modelTypeId: widget.walletWatch.modelTypeId,
          modelType: widget.walletWatch.modelType,
        ));
      },
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 7),
        padding: const EdgeInsets.only(top: 10, bottom: 10),
        decoration: BoxDecoration(
          color: Colors.white,
          border: Border.all(color: Colors.black26, width: 1),
          boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 2, offset: Offset(3, 3))],
          borderRadius: const BorderRadius.all(Radius.circular(7)),
        ),
        child: Row(
          children: [
            Container(
              padding: EdgeInsets.symmetric(horizontal: widget.screenWidth * 0.04),
              child: Column(
                children: [
                  FutureBuilder<String>(
                    future: _cachedImgFuture, // 使用缓存的Future
                    builder: (context, snapshot) {
                      if (snapshot.connectionState == ConnectionState.waiting) {
                        return const CircularProgressIndicator();
                      } else if (snapshot.hasData) {
                        return ClipRRect(
                          borderRadius: const BorderRadius.all(Radius.circular(7)),
                          child: Image.network(
                            snapshot.data!,
                            width: widget.screenWidth * 0.3,
                            height: widget.screenWidth * 0.3,
                            fit: BoxFit.cover,
                          ),
                        );
                      } else if (snapshot.hasError) {
                        return Text('Error: ${snapshot.error}');
                      } else {
                        return const Text('Image not found');
                      }
                    },
                  ),
                  SizedBox(height: widget.screenWidth * 0.05),
                  Container(
                    padding: const EdgeInsets.all(5),
                    decoration: BoxDecoration(
                      borderRadius: const BorderRadius.all(Radius.circular(3)),
                      color: (widget.walletWatch.increaseRate >= 0) ? Colors.lightGreen : Colors.red,
                    ),
                    child: Text('${widget.walletWatch.increaseRate}%'),
                  ),
                ],
              ),
            ),
            const SizedBox(width: 10),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  widget.walletWatch.modelType.model.brandname,
                  style: TextStyle(
                    color: Colors.black38,
                    height: 1,
                    fontSize: widget.screenWidth * 0.05,
                    fontFamily: 'Bebas',
                  ),
                ),
                Text(
                  widget.walletWatch.modelType.model.modelname,
                  style: TextStyle(
                    color: Colors.black87,
                    height: 1,
                    fontSize: widget.screenWidth * 0.055,
                    fontFamily: 'Bebas',
                  ),
                ),
                Text('Reference: ${widget.walletWatch.modelType.reference}'),
                Text('Retail Price: ${formatAmountFromDouble(widget.walletWatch.retailPrice)}€'),
                SizedBox(height: widget.screenWidth * 0.02),
                Text('Owned Shares: ${widget.walletWatch.owned}/${widget.walletWatch.numberOfShares}'),
                Text('Initial Price: ${formatAmountFromDouble(widget.walletWatch.initialPrice)}€'),
                Text('Actual Price: ${formatAmountFromDouble(widget.walletWatch.actualPrice)}€'),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

2. 优化加载触发逻辑,防止重复请求

修改fetchData方法和列表构建逻辑,增加加载状态控制,避免滚动时多次发起请求:

// 修改状态类中的fetchData方法
Future<void> fetchData() async {
  if (_loading) return; // 已有请求正在进行时直接返回
  setState(() => _loading = true);
  
  try {
    var req = await getUserWalletWatches(12, _pageNumber, _numberOfPostsPerRequest);
    setState(() {
      _cards.addAll(req); // 用addAll替代循环add,提升效率
      _isLastPage = req.length < _numberOfPostsPerRequest;
      _pageNumber += 1;
    });
  } catch (e) {
    setState(() => _error = true);
  } finally {
    setState(() => _loading = false);
  }
}

// 修改buildCardsView方法
Widget buildCardsView() { 
   return ListView.builder(
        itemCount: _cards.length + (_isLastPage ? 0 : 1),
        itemBuilder: (context, index) {
          // 仅在到达列表末尾、非最后一页、且未加载时触发请求
          if (index == _cards.length && !_isLastPage && !_loading) {
            fetchData();
          }

          // 渲染加载占位项
          if (index == _cards.length) {
            return const Center(child: CircularProgressIndicator());
          }

          final WalletWatch card = _cards[index];
          return Padding(
              padding: const EdgeInsets.all(15.0),
              child: CustomBottomBigCard(
                  watchID: card.watchId,
                  screenWidth: widget.width,
                  walletWatch: card,
                  imgUrlFuture: getDownloadURL(card.imageuri),
              ),
          );
    );
  }

3. 可选:保持列表项状态,避免滑出后重建

如果希望列表项滑出视口再返回时不重新构建,可以给_CustomBottomBigCardState混入AutomaticKeepAliveClientMixin:

class _CustomBottomBigCardState extends State<CustomBottomBigCard> with AutomaticKeepAliveClientMixin {
  late Future<String> _cachedImgFuture;

  @override
  bool get wantKeepAlive => true; // 开启状态保持

  @override
  void initState() {
    super.initState();
    _cachedImgFuture = widget.imgUrlFuture;
  }

  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用super.build
    // 原有build逻辑不变
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 14:12:16