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

Flutter使用FutureBuilder渲染API返回数据时实现懒加载的方法咨询

Flutter 分页懒加载实现方案(原FutureBuilder全量加载改造)

改造核心思路

  • 废弃原有一次性请求所有分页数据的getPublicationAll方法,改为按页按需加载
  • 在State中维护本地数据列表、当前加载页码、总页数、加载状态等本地变量,替代FutureBuilder的一次性异步状态
  • 为GridView添加滚动监听器,当用户滚动到列表底部时自动触发下一页数据请求
  • 加载下一页时在列表底部显示加载中提示,无更多数据时隐藏加载提示

完整改造后代码

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:statistik_malang/constants.dart';
import 'package:statistik_malang/details_screen.dart';
import 'package:statistik_malang/model_publikasi.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const PublikasiSecondPage());
}

// 类名改为大驼峰,符合Dart代码规范
class PublikasiSecondPage extends StatefulWidget {
  const PublikasiSecondPage({Key? key}) : super(key: key);

  @override
  _PublikasiSecondPageState createState() => _PublikasiSecondPageState();
}

class _PublikasiSecondPageState extends State<PublikasiSecondPage> {
  // 存储已经加载完成的所有数据
  List<Publikasimod> publications = [];
  // 当前加载到的页码,初始为1
  int currentPage = 1;
  // 接口返回的总页数,初始为0,第一页加载完成后赋值
  int totalPages = 0;
  // 是否正在加载数据,防止重复请求
  bool isLoading = false;
  // 滚动控制器,监听滚动位置
  final ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    // 初始化加载第一页数据
    _loadPageData(page: 1);
    // 添加滚动监听
    _scrollController.addListener(_onScroll);
  }

  @override
  void dispose() {
    // 销毁控制器,防止内存泄漏
    _scrollController.dispose();
    super.dispose();
  }

  // 滚动监听回调
  void _onScroll() {
    // 已经滚动到底部、还有更多数据、且当前没有在加载中,才触发下一页加载
    if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent * 0.9 &&
        currentPage < totalPages &&
        !isLoading) {
      _loadPageData(page: currentPage + 1);
    }
  }

  // 加载指定页码的数据
  Future<void> _loadPageData({required int page}) async {
    setState(() {
      isLoading = true;
    });
    try {
      // 替换为你的真实接口地址,注意拼接page参数
      String apiURL = "link of api?page=$page";
      var apiResult = await http.get(Uri.parse(apiURL));
      var jsonObject = json.decode(apiResult.body);
      List<dynamic> responseData = (jsonObject as Map<String, dynamic>)["data"];

      // 第一页加载时要拿到总页数
      if (page == 1) {
        totalPages = responseData[0]["pages"];
        publications.clear();
      }

      // 解析当前页的10条数据
      List<dynamic> currentPageList = responseData[1];
      for (var item in currentPageList) {
        publications.add(Publikasimod.createPublikasimod(item));
      }

      setState(() {
        currentPage = page;
      });
    } catch (e) {
      // 加载失败提示,可以根据需求自定义
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("加载失败,请检查网络连接")),
      );
    } finally {
      setState(() {
        isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Daftar Publikasi Terbaru"),
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    // 初始加载第一页时的加载状态
    if (publications.isEmpty && isLoading) {
      return const Center(child: CircularProgressIndicator());
    }
    // 加载完成但无数据
    if (publications.isEmpty) {
      return const Center(child: Text("暂无数据"));
    }
    // 数据加载完成,渲染GridView
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Expanded(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: kDefaultPaddin, vertical: 20),
            child: GridView.builder(
              controller: _scrollController,
              // 最后多一个item,用来显示加载中或者无更多数据提示
              itemCount: publications.length + 1,
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                mainAxisSpacing: kDefaultPaddin,
                crossAxisSpacing: kDefaultPaddin,
                childAspectRatio: 0.55,
              ),
              itemBuilder: (context, index) {
                // 到了最后一个item,显示加载状态
                if (index == publications.length) {
                  return _buildLoadMoreWidget();
                }
                // 渲染正常的书籍条目
                final item = publications[index];
                return ItemBuku(
                  id: 1,
                  title: item.title,
                  cover: item.cover,
                  date: item.rl_date,
                  press: () => Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => DetailsScreen(product: item),
                    ),
                  ),
                );
              },
            ),
          ),
        )
      ],
    );
  }

  // 底部加载更多组件
  Widget _buildLoadMoreWidget() {
    if (isLoading) {
      return const Padding(
        padding: EdgeInsets.all(16.0),
        child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
      );
    }
    if (currentPage >= totalPages) {
      return const Padding(
        padding: EdgeInsets.all(16.0),
        child: Center(child: Text("已加载全部内容")),
      );
    }
    return const SizedBox.shrink();
  }
}

// 类名改为大驼峰,符合Dart规范
class ItemBuku extends StatelessWidget {
  final String title;
  final String date;
  final String cover;
  final Function press;
  final int id;
  const ItemBuku({
    Key? key,
    required this.title,
    required this.date,
    required this.id,
    required this.cover,
    required this.press,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => press.call(),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Container(
              padding: const EdgeInsets.all(kDefaultPaddin),
              height: 230,
              width: 169,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(16),
                image: DecorationImage(
                  fit: BoxFit.fill,
                  image: NetworkImage(cover),
                ),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: kDefaultPaddin / 4),
            child: Text(
              title,
              style: const TextStyle(fontWeight: FontWeight.bold),
            ),
          ),
          Text(
            date,
            style: Theme.of(context).textTheme.caption,
          )
        ],
      ),
    );
  }
}

注意事项

  • 请将代码中link of api?page=$page替换为你的真实接口地址,确保分页参数和接口要求一致
  • 代码中已经按照Dart官方规范修改了类名命名(大驼峰),如果你的项目中其他地方引用了原有类名,请注意同步修改
  • 滚动触发下一页的阈值设置为滚动到90%位置就触发,你可以根据实际使用体验调整_onScroll方法中的比例值

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 02:45:03