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

Flutter GridView分页异常:加载第二页后无法回滚至第一页

解决GridView.builder分页后无法滚动回第一页的问题

我帮你定位到了问题的核心,咱们一步步来修复:

问题根源

你在加载下一页数据时犯了两个关键错误:

  1. 清空了原有数据:API调用函数getSearchResult里的search_result_list.clear();会把第一页的20条数据全部删掉,导致加载第二页后列表只剩1条数据,自然滚不回第一页。
  2. 错误的数据添加逻辑:你声明了tList但没往里面填充新数据,反而用search_result_list.addAll(tList);——这相当于啥也没加,反而把旧数据清了,雪上加霜。

另外,你还缺少是否还有更多数据的判断,会导致滚动到底部时重复触发无效请求。

修复步骤与代码修改

1. 补充必要变量

先在State类里加两个状态变量:

bool isLoading = false;
bool hasMore = true; // 标记是否还有更多数据可以加载

2. 修正API数据加载逻辑

修改getSearchResult函数,移除清空原有数据的代码,正确处理新数据的追加:

Future<void> getSearchResult(int index) async {
  print("size" + index.toString());
  // 只有未加载中且还有更多数据时,才发起请求
  if (!isLoading && hasMore) { 
    setState(() {
      isLoading = true;
    });
    List<PrefrenceModel> tList = []; // 用来存储新获取的一页数据
    try {
      var response = await http.get(
        "${Urls.baseUrl}${Urls.CategorySearch}?latitude=${Constants.latitude}&longitude=${Constants.longitude}&radius=100&locale=en&keyword=${slug}&page=${index.toString()}&type=item",
        headers: {
          "Content-Type": "application/json",
          "Authorization": "Bearer ${userToken}",
          "Accept": "application/json"
        },
      );
      if (response.statusCode == 200) {
        Map<String, dynamic> value = json.decode(response.body);
        var status = value['success'];
        var message = value['message'];
        last_page = value['last_page'];
        
        if (status == true) {
          var data = value['data']['data'];
          if (data.length > 0) {
            // 把新获取的数据填充到tList
            for (int i = 0; i < data.length; i++) {
              var obj = data[i];
              tList.add(PrefrenceModel(
                obj['id'].toString(),
                obj['description'].toString(),
                obj['valid_from'].toString(),
                obj['valid_to'].toString(),
                obj['thumbs']['md'].toString(),
                obj['status'].toString(),
                "",
                "",
                obj['name'].toString(),
                "",
                obj['flyer_slug'].toString(),
                obj['type'].toString(),
                obj['isClipped'],
                obj['url'].toString(),
                obj['price'].toString(),
                obj['flyer_page_id'].toString(),
                obj['id'].toString(),
                obj['shop_name'].toString(),
                obj['shop_logo'].toString(),
                obj['brand_name'].toString(),
                obj['brand_logo'].toString(),
              ));
            }
            setState(() {
              isLoading = false;
              search_result_list.addAll(tList); // 把新数据追加到原有列表后面
              page++;
              // 判断是否还有更多数据可加载
              if (page > last_page) {
                hasMore = false;
              }
            });
          } else {
            setState(() {
              isLoading = false;
              hasMore = false;
            });
            final snackBar = SnackBar(content: Text("No Data Available"));
            _scaffoldKey.currentState?.showSnackBar(snackBar);
          }
        } else {
          setState(() {
            isLoading = false;
          });
          print("Error...");
          final snackBar = SnackBar(content: Text(message));
          _scaffoldKey.currentState?.showSnackBar(snackBar);
        }
      } else {
        setState(() {
          isLoading = false;
        });
        var value = json.decode(response.body);
        var message = value['message'];
        final snackBar = SnackBar(content: Text(message));
        _scaffoldKey.currentState?.showSnackBar(snackBar);
      }
    } catch (e) {
      setState(() {
        isLoading = false;
      });
      print(e.toString());
    }
  }
}

3. 修正GridView的itemCount与加载指示器逻辑

修改GridView.builder的itemCount,当没有更多数据时,不再显示加载指示器:

GridView.builder(
  controller: _sccontroller,
  // 只有还有更多数据时,才多留一个加载指示器的位置
  itemCount: hasMore ? search_result_list.length + 1 : search_result_list.length,
  physics: const AlwaysScrollableScrollPhysics(),
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    childAspectRatio: 5.2 / 8.0,
    crossAxisCount: 2,
    crossAxisSpacing: 3.0,
    mainAxisSpacing: 3.0,
  ),
  itemBuilder: (BuildContext context, int index) {
    // 只有还有更多数据且是最后一个位置时,才显示加载指示器
    if (hasMore && index == search_result_list.length) {
      return Center(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: CircularProgressIndicator(),
        ),
      );
    } else {
      // 原有Card渲染逻辑保持不变
      return Container(
        child: Padding(
          padding: const EdgeInsets.only(top:10),
          child: GestureDetector(
            onTap: () { },
            child: Container(
              child: Card(
                elevation: 2,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(2),
                ),
                child: Container(
                  width: 300,
                  child: Stack(
                    children: [
                      Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Padding(
                            padding: EdgeInsets.all(2),
                            child: Image.network(
                              Urls.baseImageUrl + search_result_list[index].thumb_path,
                              fit: BoxFit.fill,
                              height: 180,
                              width: 180,
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.all(5),
                            child: Align(
                              child: Text(
                                search_result_list[index].name,
                                style: TextStyle(
                                  fontSize: 11, fontWeight: FontWeight.bold), 
                                textAlign: TextAlign.left,
                              ),
                              alignment: Alignment.centerLeft,
                            )),
                        ],
                      ),
                    ]
                  )),
              ),
            ),
          ),
        ),
      );
    }
  },
)

4. 优化滚动监听逻辑

在initState的滚动监听里,加上hasMore判断,避免无效触发:

@override
void initState() {
  super.initState();
  Future token = SharedPrefrence().getToken();
  token.then((data) async {
    userToken = data;
    this.getSearchResult(page);
    _sccontroller.addListener(() {
      // 只有还有更多数据且滚动到底部时,才加载下一页
      if (_sccontroller.position.pixels == _sccontroller.position.maxScrollExtent && hasMore) {
        getSearchResult(page);
      }
    });
  });
  setState(() {
    title = name;
  });
}

为什么ListView没问题?

其实ListView里如果用了同样的错误逻辑,也会出现一样的问题——可能你在ListView的实现里没加search_result_list.clear();,或者数据处理逻辑不同,才没暴露这个问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 18:12:30