You need to enable JavaScript to run this app. Flutter基于sqflite如何实现列表分页懒加载(每次加载10条)
优惠活动
大模型
产品
解决方案
定价
更多

Flutter基于sqflite如何实现列表分页懒加载(每次加载10条)

Flutter 结合sqflite实现列表分页懒加载方案

核心改造思路

传统全量查询1000条数据会造成页面启动卡顿、内存占用过高,我们通过数据库层分页查询+页面层滚动监听的组合实现需求:启动时加载10条,滚动靠近当前列表底部时自动追加加载下一批10条,直到所有数据加载完成。


第一步:改造DatabaseHelper,新增分页查询能力

你现有的queryAllRows方法会一次性拉取全表所有数据,完全不适合大数据量场景,需要在帮助类中新增支持偏移量、条数限制的查询方法:

// 新增分页查询方法
Future<List<Map<String, dynamic>>> queryPagedRows(int offset, int limit) async {
  Database db = await instance.database;
  // 必须按主键id排序,保证分页数据顺序稳定,避免重复/漏查
  return await db.query(
    table,
    orderBy: '$colId ASC',
    limit: limit,
    offset: offset,
  );
}

附带修正你现有代码的一处逻辑bug:原deleteEmp方法的参数错误,sqflite的delete方法第一个参数需要传入表名,而非拼好的SQL语句,修正后的写法:

Future<int> deleteEmp(int id) async {
  Database db = await instance.database;
  return await db.delete(table, where: '$colId = ?', whereArgs: [id]);
}

第二步:页面层实现懒加载状态管控

在_firstScreen的State类中,替换原有零散的状态变量,加入滚动控制器、加载锁、分页偏移量等必要状态:

  • 新增状态定义
    // 固定每页加载10条,匹配需求
    static const int _pageSize = 10;
    // 滚动控制器,用于监听列表滚动位置
    final ScrollController _scrollController = ScrollController();
    // 加载锁:防止重复触发请求造成数据重复加载
    bool _isLoading = false;
    // 当前已加载数据的偏移位置
    int _loadedOffset = 0;
    // 数据库总数据量,用于判断是否加载完全部数据
    int _totalDataCount = 0;
    List<Emp> empList = [];
    
  • 初始化与资源释放
    把原来写在build方法里的懒加载判断逻辑移到initState,避免build触发时重复初始化:
    @override
    void initState() {
      super.initState();
      // 首次进入页面加载初始数据
      _initData();
      // 绑定滚动监听
      _scrollController.addListener(_scrollListener);
    }
    
    @override
    void dispose() {
      // 销毁时释放控制器,避免内存泄漏
      _scrollController.dispose();
      super.dispose();
    }
    
  • 初始数据加载逻辑
    启动时先查询数据库总条数,再加载第一页10条数据:
    Future<void> _initData() async {
      if (_isLoading) return;
      setState(() => _isLoading = true);
      // 查询全表总条数
      _totalDataCount = await dbHelper.queryRowCount();
      // 加载第一页10条数据
      final firstPageRows = await dbHelper.queryPagedRows(0, _pageSize);
      empList.clear();
      empList.addAll(firstPageRows.map((row) => Emp.fromMap(row)));
      _loadedOffset = empList.length;
      setState(() => _isLoading = false);
    }
    
  • 滚动监听逻辑
    当列表滚动到距离底部200像素位置时,触发下一页加载,同时做好边界判断:
    void _scrollListener() {
      // 边界拦截:正在加载、已经加载完全部数据时不触发请求
      if (_isLoading || _loadedOffset >= _totalDataCount) return;
      // 滚动位置距离底部不足200px时触发加载(阈值可根据实际UI调整)
      if (_scrollController.position.pixels >=
          _scrollController.position.maxScrollExtent - 200) {
        _loadNextPage();
      }
    }
    
  • 下一页加载逻辑
    从当前已加载的偏移位置开始查询10条新数据,追加到原有列表中,不要清空原有数据:
    Future<void> _loadNextPage() async {
      setState(() => _isLoading = true);
      // 按当前偏移量查询下一批数据
      final nextPageRows = await dbHelper.queryPagedRows(_loadedOffset, _pageSize);
      empList.addAll(nextPageRows.map((row) => Emp.fromMap(row)));
      _loadedOffset = empList.length;
      setState(() => _isLoading = false);
    }
    

第三步:适配ListView渲染逻辑

改造原有getListView方法,绑定滚动控制器,在列表底部追加加载提示位:

ListView getListView() {
  return ListView.builder(
    controller: _scrollController,
    // itemCount在原有列表长度基础上+1,给底部加载提示预留位置
    itemCount: empList.length + 1,
    itemBuilder: (BuildContext context, int position) {
      // 渲染列表底部的提示区域
      if (position == empList.length) {
        // 已经加载完全部数据时不渲染提示
        if (_loadedOffset >= _totalDataCount) {
          return SizedBox.shrink();
        }
        // 加载中显示转圈提示
        return const Padding(
          padding: EdgeInsets.all(16.0),
          child: Center(child: CircularProgressIndicator()),
        );
      }
      // 正常渲染员工列表项
      final emp = empList[position];
      final _byteImage = Base64Decoder().convert(emp.path);
      return Card(
        color: Colors.white,
        elevation: 2.0,
        child: ListTile(
          leading: CircleAvatar(
            backgroundColor: Colors.white,
            child: Image.memory(_byteImage, fit: BoxFit.fill),
          ),
          title: Text(emp.name),
          subtitle: Text(emp.age),
        ),
      );
    },
  );
}

原有代码的其他适配调整

  • 把原来的updateListView方法替换为重置逻辑,给刷新按钮、新增员工后的列表刷新使用:
    void updateListView() async {
      _loadedOffset = 0;
      empList.clear();
      await _initData();
    }
    
  • 修正新增员工按钮的逻辑:原代码在Navigator.push之后直接调用updateListView,由于路由跳转是阻塞的,这行代码根本不会执行。调整为等从新增页面返回后再触发刷新:
    onPressed: () async {
      debugPrint('add pressed');
      // 等路由返回后再刷新列表
      await Navigator.push(context, MaterialPageRoute(builder: (context) {
        return secondScreeen();
      }));
      // 返回页面后重置加载第一页数据
      updateListView();
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.05 09:36:07