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

Flutter中如何实现ListView分页加载数据(REST API方式)

Flutter 列表分页加载实现(对接Spring Boot REST API)

下面是第二种方案的具体实现步骤,核心是滚动触发分页请求+列表增量更新+加载状态管理:


1. 准备工作:后端接口约定

先确保你的Spring Boot REST API支持分页参数,通常接口会接收两个参数:

  • page:当前页码(从0开始计数)
  • size:每页数据条数(比如10)

接口返回格式建议包含:

  • content:当前页的数据列表
  • last:是否是最后一页(前端用来判断是否还有更多数据)
  • 可选:totalPages、totalElements 辅助判断

示例接口:GET /api/data?page={page}&size={size}


2. Flutter端实现步骤

2.1 定义数据模型和API请求工具

先创建对应后端返回的数据模型,以及封装分页请求方法:

// 后端分页响应模型
class PageResponse<T> {
  final List<T> content;
  final bool last;

  PageResponse({required this.content, required this.last});

  // 从JSON解析,这里假设你的数据模型是DataItem
  factory PageResponse.fromJson(List<dynamic> contentJson, bool last) {
    List<DataItem> content = contentJson.map((json) => DataItem.fromJson(json)).toList();
    return PageResponse(content: content, last: last);
  }
}

// 单个数据项模型
class DataItem {
  final int id;
  final String title;

  DataItem({required this.id, required this.title});

  factory DataItem.fromJson(Map<String, dynamic> json) {
    return DataItem(id: json['id'], title: json['title']);
  }
}

// API请求方法
Future<PageResponse<DataItem>> fetchData(int page, int size) async {
  final response = await http.get(
    Uri.parse('http://your-spring-boot-url/api/data?page=$page&size=$size'),
  );

  if (response.statusCode == 200) {
    final json = jsonDecode(response.body);
    return PageResponse.fromJson(json['content'], json['last']);
  } else {
    throw Exception('请求失败');
  }
}

2.2 实现带分页的StatefulWidget

核心是维护以下状态变量:

  • List<DataItem> _dataList:已加载的全部数据
  • int _currentPage:当前请求的页码
  • bool _isLoading:是否正在加载数据(防止重复请求)
  • bool _hasMore:是否还有更多数据可以加载

完整代码示例:

class PagedListView extends StatefulWidget {
  const PagedListView({super.key});

  @override
  State<PagedListView> createState() => _PagedListViewState();
}

class _PagedListViewState extends State<PagedListView> {
  final List<DataItem> _dataList = [];
  int _currentPage = 0;
  bool _isLoading = false;
  bool _hasMore = true;
  final _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    // 初始化加载第一页
    _fetchNextPage();
    // 监听滚动事件,触发加载更多
    _scrollController.addListener(() {
      // 当滚动到列表底部附近时触发加载
      if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200 &&
          !_isLoading &&
          _hasMore) {
        _fetchNextPage();
      }
    });
  }

  Future<void> _fetchNextPage() async {
    if (_isLoading) return;
    setState(() => _isLoading = true);

    try {
      final response = await fetchData(_currentPage, 10);
      setState(() {
        _dataList.addAll(response.content);
        _hasMore = !response.last;
        _currentPage++;
      });
    } catch (e) {
      // 这里可以添加错误提示,比如Toast或SnackBar
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('加载失败: $e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      itemCount: _dataList.length + (_hasMore ? 1 : 0), // 加载指示器占一个item
      itemBuilder: (context, index) {
        // 如果是最后一个item且还有更多数据,显示加载指示器
        if (index == _dataList.length) {
          return _isLoading
              ? const Padding(
                  padding: EdgeInsets.symmetric(vertical: 16),
                  child: Center(child: CircularProgressIndicator()),
                )
              : const SizedBox.shrink(); // 没有加载时隐藏
        }

        // 正常显示数据项
        final item = _dataList[index];
        return ListTile(
          title: Text(item.title),
          subtitle: Text('ID: ${item.id}'),
        );
      },
    );
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }
}

3. 关键细节说明

  • 防止重复请求:通过_isLoading状态控制,请求未完成时不触发新的请求
  • 滚动触发时机:设置maxScrollExtent - 200,让用户还没完全滑到底部就开始加载,提升体验
  • 无更多数据处理:当后端返回last: true时,设置_hasMore = false,不再显示加载指示器
  • 资源清理:记得在dispose里释放ScrollController,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 03:05:18