云计算百科
云计算领域专业知识百科平台

Flutter下拉刷新上拉加载终极指南

在 Flutter 里实现「下拉刷新 + 上拉加载更多」最常用、最省心的组合就是 RefreshIndicator(官方下拉)+ ScrollController(监听上拉)。下面给出完整可直接运行的最小示例,并补充 3 种常见需求变体,复制即可用。


一、最小可运行示例(ListView.separated 版)

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
const MyApp({super.key});

Widget build(BuildContext context) {
return MaterialApp(
title: 'Refresh&LoadMore',
theme: ThemeData(useMaterial3: true),
home: const HomePage(),
);
}
}

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

State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
final ScrollController _ctrl = ScrollController();
final List<int> _data = List.generate(15, (i) => i);
bool _isLoading = false; // 上拉加载状态
bool _noMore = false; // 数据到底标记


void initState() {
super.initState();
_ctrl.addListener(_onScroll);
}


void dispose() {
_ctrl.dispose();
super.dispose();
}

/* 核心:上拉检测 */
void _onScroll() {
if (_ctrl.position.pixels >= _ctrl.position.maxScrollExtent 100 &&
!_isLoading &&
!_noMore) {
_loadMore();
}
}

/* 下拉刷新 */
Future<void> _onRefresh() async {
await Future.delayed(const Duration(seconds: 1)); // 模拟网络
_noMore = false;
_data.clear();
_data.addAll(List.generate(15, (i) => i));
if (mounted) setState(() {});
}

/* 上拉加载 */
Future<void> _loadMore() async {
_isLoading = true;
if (mounted) setState(() {});
await Future.delayed(const Duration(seconds: 1));
final append = List.generate(10, (i) => _data.length + i);
_data.addAll(append);
_isLoading = false;
if (_data.length > 60) _noMore = true; // 模拟到底
if (mounted) setState(() {});
}


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('下拉刷新 + 上拉加载')),
body: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.separated(
controller: _ctrl,
padding: const EdgeInsets.all(12),
itemCount: _data.length + 1, // +1 给底部 loading / 到底提示
separatorBuilder: (_, __) => const Divider(height: 16),
itemBuilder: (context, index) {
if (index == _data.length) {
return _buildFooter(); // 底部组件
}
return ListTile(
tileColor: Colors.blueGrey.shade50,
title: Text('Item ${_data[index]}'),
);
},
),
),
);
}

/* 底部:加载中 / 无更多 / 空容器 */
Widget _buildFooter() {
if (_noMore) {
return const Padding(
padding: EdgeInsets.all(16),
child: Center(child: Text('—— 到底了 ——')),
);
}
if (_isLoading) {
return const Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
);
}
return const SizedBox.shrink();
}
}

运行效果: 下拉出现圆形进度 → 数据重置;滑到底部自动触发加载 → 底部出现小圆圈;加载到 60 条后提示“到底了”。


二、3 种常见变体

  • 使用 flutter_easyrefresh 第三方库(自带 Material/Cupertino 风格、无需自己算滚动)
  • dependencies:
    flutter_easyrefresh: ^4.0.0

    EasyRefresh(
    onRefresh: () async => ...,
    onLoad: () async => ...,
    child: ListView(...),
    )

  • 使用 CustomScrollView + Slivers 混合同步刷新
  • CustomScrollView(
    slivers: [
    CupertinoSliverRefreshControl(onRefresh: ...),
    SliverList(delegate: SliverChildBuilderDelegate(...)),
    if (_isLoading)
    const SliverToBoxAdapter(
    child: Center(child: CircularProgressIndicator())),
    ],
    )

  • 分页请求 + Repository 层 把 _onRefresh / _loadMore 里的延时换成真正的 Dio/http 请求,分页参数 page=1/page++,异常捕获、空页判断统一放在仓库层,UI 只关心状态。

  • 三、踩坑提醒

  • 一定要在 dispose() 里 removeListener 并 dispose() 掉 ScrollController,否则热重载会内存泄漏。
  • 加载更多触发阈值不要写死 maxScrollExtent,留 100~150 px 缓冲体验更好。
  • 当数据不足一屏时,ListView 不会滚动,也就不会触发 onScroll,此时可在首次加载完成后补足一页(比如后台直接返回 20 条),或主动显示“加载更多”按钮。
  • 如果页面同时存在 TabBarView + AutomaticKeepAliveClientMixin,需要把 ScrollController 放到 PageStorageKey 里保证切换 Tab 后位置不丢失。
  • 把上面模板直接复制进项目即可跑通,再根据业务替换网络请求、分页逻辑、空视图、错误视图即可。祝编码愉快!

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Flutter下拉刷新上拉加载终极指南
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!