方式一:RefreshIndicator+ListView实现
import 'package:flutter/material.dart';
class SimpleRefreshDemoPage extends StatefulWidget {
const SimpleRefreshDemoPage({super.key});
@override
State<StatefulWidget> createState() {
return _SimpleRefreshDemoPage();
}
}
class _SimpleRefreshDemoPage extends State<SimpleRefreshDemoPage> {
final int pageSize = 50;
bool disposed = false;
List<String> dataList = [];
final ScrollController _scrollController = ScrollController();
final GlobalKey<RefreshIndicatorState> refreshKey = GlobalKey();
Future<void> onRefresh() async {
await Future.delayed(const Duration(seconds: 4));
dataList.clear();
for (int i = 0; i < pageSize; i++) {
dataList.add("refresh");
}
if (disposed) {
return;
}
setState(() {});
}
Future<void> loadMore() async {
await Future.delayed(const Duration(seconds: 4));
for (int i = 0; i < pageSize; i++) {
dataList.add("loadmore");
}
if (disposed) {
return;
}
setState(() {});
}
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
loadMore();
}
});
Future.delayed(const Duration(seconds: 0), () {
refreshKey.currentState?.show();
});
}
@override
void dispose() {
disposed = true;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("SimpleRefreshDemoPage"),
),
body: RefreshIndicator(
key: refreshKey,
onRefresh: onRefresh,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (index == dataList.length) {
return Container(
margin: const EdgeInsets.all(10),
child: const Align(
child: CircularProgressIndicator(),
),
);
}
return Card(
child: Container(
height: 60,
alignment: Alignment.centerLeft,
child: Text("Item ${dataList[index]} $index"),
),
);
},
itemCount: (dataList.length >= pageSize)
? dataList.length + 1
: dataList.length,
controller: _scrollController,
),
),
);
}
}
如何实现下拉刷新
RefreshIndicator
RefreshIndicator 是 Flutter 中用于实现下拉刷新功能的一个组件。RefreshIndicator 包裹一个可滚动的子组件,如 `ListView` 或 `GridView`,当用户下拉到列表顶部时,会触发刷新操作。上面的例子中RefreshIndicator包裹在了ListView。
`onRefresh` 是一个返回 `Future` 的异步函数,用于执行刷新操作。
关键属性
- onRefresh`: 必须实现的回调函数,定义刷新时的操作。
- `child`: 需要包裹的可滚动子组件。
- `color`: 刷新指示器的进度条颜色。
- `backgroundColor`: 刷新指示器的背景色。
- `displacement`: 指示器开始显示时与顶部的距离。
如何实现下拉加载
ScrollController
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
loadMore();
}
});
如何检测用户是否滚动到了 `ScrollView` 的底部?
_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent
_scrollController.position.pixels` :表示当前滚动的位置。
`_scrollController.position.maxScrollExtent` :表示可滚动区域的最大值。
上面两个值相等时,说明用户已经滚动到了底部。
`bool disposed` 字段作用
在上面的代码中,`bool disposed` 字段用于指示 `State` 对象是否已经被销毁。当 `dispose` 方法被调用时,`disposed` 被设置为 `true`。这个字段主要用于在异步操作完成后,确保不会调用已经被销毁的 `State` 对象的 `setState` 方法。
作用分析
1.防止在销毁后调用 `setState`:
- 异步操作(如 `Future.delayed`)在完成后,可能会尝试调用 `setState` 来更新 UI。然而,如果在异步操作执行期间,用户已经导航离开了当前页面,导致 `State` 对象被销毁,那么调用 `setState` 就会抛出异常,因为 `State` 已经不再是活动的。
- 通过检查 `disposed` 字段,可以在调用 `setState` 前确认 `State` 是否仍然有效,从而避免在组件销毁后对其进行不必要的更新。
2.提高代码的稳健性:
- 这种模式是一种防御性编程的方法,确保应用程序在面对不确定的异步执行时仍然是稳定的。
@override
void dispose() {
disposed = true;
super.dispose();
}
Future<void> onRefresh() async {
await Future.delayed(const Duration(seconds: 4));
dataList.clear();
for (int i = 0; i < pageSize; i++) {
dataList.add("refresh");
}
if (disposed) {
return;
}
setState(() {});
}
Future<void> loadMore() async {
await Future.delayed(const Duration(seconds: 4));
for (int i = 0; i < pageSize; i++) {
dataList.add("loadmore");
}
if (disposed) {
return;
}
setState(() {});
}
在 `onRefresh` 和 `loadMore` 方法中,`disposed` 被用来检查 `State` 是否已经被销毁。如果 `disposed` 为 `true`,则不再调用 `setState`,从而避免可能的异常。
3.总结
使用 `disposed` 字段可以有效地防止在组件销毁后进行不必要或有害的 UI 更新。这种模式特别适用于涉及异步操作的 Flutter 应用开发,确保代码在处理 `State` 生命周期时更加健壮。
在 Flutter 中,`dispose` 方法是一个非常重要的生命周期方法,用于在不再需要一个对象时释放资源。通常,`dispose` 方法用于清理那些可能导致内存泄漏的资源,如控制器、监听器和订阅等。
`dispose` 方法的常规用途
`dispose` 方法在 `StatefulWidget` 的状态对象中被重写。它在以下情况下被调用:
- Widget 被从树中永久移除时。
- 需要释放资源以避免内存泄漏时。
`dispose` 方法的典型用法
1.释放控制器:
- 常用于释放 `AnimationController`、`TextEditingController`、`PageController` 等控制器。
2.取消订阅:
- 用于取消流(stream)的订阅,以防止内存泄漏。
3.移除监听器:
- 移除添加到某些对象上的监听器,例如 `ScrollController` 的监听器。
重要注意事项
- 调用 `super.dispose()`:在重写 `dispose` 方法时,确保在最后调用 `super.dispose()`。这会调用父类的 `dispose` 方法,完成必要的清理。
- 确保资源释放:所有在 `initState` 或其他地方创建的需要手动管理的资源,都应在 `dispose` 中被正确释放。
- 流的取消:对于任何流的订阅,一定要在 `dispose` 中调用 `cancel` 方法。
通过正确使用 `dispose` 方法,你可以确保你的应用程序以更高效的方式管理内存,避免可能的内存泄漏问题。