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

在Flutter中如何基于page id实现自动无限滚动分页功能

Flutter 无限自动滚动分页实现方案

以下是基于你原有代码修改后的可运行实现:

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

// 请自行替换为你的全局变量
const mainUrl = "你的接口前缀地址/";
const accessToken = "你的授权登录token";

class Test extends StatefulWidget {
  Test({Key? key}) : super(key: key);

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

class _Test extends State<Test> {
  // 存储已加载的全量列表数据
  List<LeadFollowupss> leadList = [];
  String _value = 'total';
  int page = 1;
  // 加载状态标识
  bool loading = false;
  // 是否还有更多可加载数据
  bool hasMore = true;
  ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    // 首次加载第一页数据
    _loadData();
    // 给滚动控制器添加滚动监听
    _scrollController.addListener(_onScroll);
  }

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

  // 滚动事件监听逻辑
  void _onScroll() {
    // 正在加载中/无更多数据时直接拦截
    if (loading || !hasMore) return;
    // 滚动到距离底部不足200像素时触发加载下一页
    if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) {
      _loadMoreData();
    }
  }

  // 首次加载数据逻辑
  Future<void> _loadData() async {
    setState(() {
      loading = true;
    });
    try {
      TotalLeadsListss wrapper = TotalLeadsListss();
      final result = await wrapper.getAllLeadsListss(_value, page);
      setState(() {
        leadList = result.leadFollowupss ?? [];
        // 可根据你接口实际每页返回条数调整阈值,返回条数少于阈值说明无更多数据
        hasMore = (result.leadFollowupss?.length ?? 0) >= 10;
        loading = false;
      });
    } catch (e) {
      setState(() {
        loading = false;
      });
      debugPrint("数据加载失败:$e");
    }
  }

  // 加载下一页数据逻辑
  Future<void> _loadMoreData() async {
    setState(() {
      loading = true;
    });
    try {
      // 页码自增
      page++;
      TotalLeadsListss wrapper = TotalLeadsListss();
      final result = await wrapper.getAllLeadsListss(_value, page);
      setState(() {
        final newList = result.leadFollowupss ?? [];
        leadList.addAll(newList);
        hasMore = newList.length >= 10;
        loading = false;
      });
    } catch (e) {
      // 加载失败回滚页码
      page--;
      setState(() {
        loading = false;
      });
      debugPrint("更多数据加载失败:$e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    // 首次加载中状态
    if (loading && leadList.isEmpty) {
      return Center(child: Image.asset("你的加载图资源路径"));
    }
    // 无数据空状态
    if (leadList.isEmpty) {
      return Center(child: Image.asset("你的空状态图资源路径"));
    }
    return ListView.builder(
        controller: _scrollController,
        // 有更多数据时尾部多展示一个加载指示器位
        itemCount: leadList.length + (hasMore ? 1 : 0),
        itemBuilder: (BuildContext context, int index) {
          // 最后一项渲染加载指示器
          if (index == leadList.length) {
            return const Padding(
              padding: EdgeInsets.symmetric(vertical: 16),
              child: Center(child: CircularProgressIndicator()),
            );
          }
          final item = leadList[index];
          return Container(
              margin: const EdgeInsets.only(left: 8.0, right: 8.0, top: 3.0, bottom: 3.0),
              child: GestureDetector(
                  onTap: () {},
                  child: Card(
                      color: Colors.grey,
                      elevation: 2.0,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(5.0),
                      ),
                      child: ListTile(
                        contentPadding: const EdgeInsets.only(left: 8.0, right: 8.0),
                        title: Text(
                          item.name ?? "",
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                          softWrap: false,
                          style: const TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600),
                        ),
                        subtitle: Text(
                          item.nextMeeting.toString(),
                          style: const TextStyle(),
                        ),
                      ))));
        });
  }
}

/// 接口请求封装类
class TotalLeadsListss {
  Future<getTotalLeadsListss> getAllLeadsListss(_value, page) async {
    final response = await http.post(Uri.parse('${mainUrl}lead-list'), headers: {
      'Accept': 'application/json',
      HttpHeaders.authorizationHeader: 'Bearer $accessToken',
    }, body: {
      'type': '$_value',
      'page': '$page',
    });
    if (response.statusCode == 200) {
      return getTotalLeadsListss.fromJson(json.decode(response.body));
    } else {
      throw Exception('No records found');
    }
  }
}

class getTotalLeadsListss {
  bool? status;
  String? msg;
  List<LeadFollowupss>? leadFollowupss;

  getTotalLeadsListss({this.status, this.msg, this.leadFollowupss});

  getTotalLeadsListss.fromJson(Map<String, dynamic> json) {
    status = json['status'];
    msg = json['message'];
    if (json['data'] != null) {
      leadFollowupss = <LeadFollowupss>[];
      json['data'].forEach((v) {
        leadFollowupss!.add(LeadFollowupss.fromJson(v));
      });
    }
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['status'] = status;
    data['message'] = msg;
    if (leadFollowupss != null) {
      data['data'] = leadFollowupss!.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

class LeadFollowupss {
  String? id, name, nextMeeting;

  LeadFollowupss({this.id, this.name, this.nextMeeting});

  LeadFollowupss.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    name = json['business_name'];
    nextMeeting = json['next_meeting'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['id'] = id;
    data['business_name'] = name;
    data['next_meeting'] = nextMeeting;
    return data;
  }
}

核心调整说明

  • 新增leadList变量存储已加载的全量数据,避免每次请求全量刷新列表
  • 为滚动控制器添加监听,滚动到接近底部位置自动触发下一页加载
  • 新增hasMore变量判断是否还有剩余数据,避免无意义的重复请求
  • 列表尾部新增加载指示器,优化分页加载时的用户体验
  • 修复原代码语法错误,适配Flutter空安全规范
  • 拆分首次加载、加载更多逻辑,代码结构更清晰易维护

注意:请根据你的接口实际每页返回条数调整hasMore的判断阈值,比如接口每页返回20条就将判断条件改为>=20即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 21:24:04