我正在尝试实现无限滚动功能。
我尝试ListView
在 a 上使用 insideNotificationListener
来检测滚动事件,但我看不到表明滚动是否到达视图底部的事件。
实现这一目标的最佳方法是什么?
我正在尝试实现无限滚动功能。
我尝试ListView
在 a 上使用 insideNotificationListener
来检测滚动事件,但我看不到表明滚动是否到达视图底部的事件。
实现这一目标的最佳方法是什么?
通常有两种方法可以做到这一点。
1.使用ScrollController
// Create a variable
final _controller = ScrollController();
@override
void initState() {
super.initState();
// Setup the listener.
_controller.addListener(() {
if (_controller.position.atEdge) {
bool isTop = _controller.position.pixels == 0;
if (isTop) {
print('At the top');
} else {
print('At the bottom');
}
}
});
}
用法:
ListView(controller: _controller) // Assign the controller.
2.使用NotificationListener
NotificationListener<ScrollEndNotification>(
onNotification: (scrollEnd) {
final metrics = scrollEnd.metrics;
if (metrics.atEdge) {
bool isTop = metrics.pixels == 0;
if (isTop) {
print('At the top');
} else {
print('At the bottom');
}
}
return true;
},
child: ListView.builder(
physics: ClampingScrollPhysics(),
itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
itemCount: 20,
),
)
您可以使用 aListView.builder
创建包含无限项的滚动列表。itemBuilder
当新细胞出现时,您将根据需要被召唤。
如果您想收到有关滚动事件的通知,以便从网络加载更多数据,您可以传递一个controller
参数并使用addListener
将侦听器附加到ScrollController
. 的position
可以ScrollController
用来判断滚动是否接近底部。
_scrollController = new ScrollController();
_scrollController.addListener(
() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = 200.0; // or something else..
if ( maxScroll - currentScroll <= delta) { // whatever you determine here
//.. load more
}
}
);
科林的应该被接受的答案....
我想为collin jackson 提供的答案添加示例。参考以下片段
var _scrollController = ScrollController();
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
// Perform your task
}
});
仅当列表中的最后一个项目可见时才会触发。
一个更简单的方法是这样的:
NotificationListener<ScrollEndNotification>(
onNotification: onNotification,
child: <a ListView or Wrap or whatever widget you need>
)
并创建一种检测位置的方法:
bool onNotification(ScrollEndNotification t) {
if (t.metrics.pixels >0 && t.metrics.atEdge) {
log('I am at the end');
} else {
log('I am at the start')
}
return true;
}
t.metrics.pixel
当用户在顶部滚动时为 0,当确定滚动时大于 0。
t.metrics.atEdge
是true
当用户在顶部或带有滚动条的末尾时,
该log
方法来自包import 'dart:developer';
我觉得这个答案是对 Esteban 的一个补充(带有扩展方法和节流阀),但它也是一个有效的答案,所以这里是:
Dart 最近(不确定)有一个很好的特性,方法扩展,它允许我们onBottomReach
像下面的一部分一样编写方法ScrollController
:
import 'dart:async';
import 'package:flutter/material.dart';
extension BottomReachExtension on ScrollController {
void onBottomReach(VoidCallback callback,
{double sensitivity = 200.0, Duration throttleDuration}) {
final duration = throttleDuration ?? Duration(milliseconds: 200);
Timer timer;
addListener(() {
if (timer != null) {
return;
}
// I used the timer to destroy the timer
timer = Timer(duration, () => timer = null);
// see Esteban Díaz answer
final maxScroll = position.maxScrollExtent;
final currentScroll = position.pixels;
if (maxScroll - currentScroll <= sensitivity) {
callback();
}
});
}
}
这是一个使用示例:
// if you're declaring the extension in another file, don't forget to import it here.
class Screen extends StatefulWidget {
Screen({Key key}) : super(key: key);
@override
_ScreenState createState() => _ScreenState();
}
class _ScreenState extends State<Screen> {
ScrollController_scrollController;
@override
void initState() {
super.initState();
_scrollController = ScrollController()
..onBottomReach(() {
// your code goes here
}, sensitivity: 200.0, throttleDuration: Duration(milliseconds: 500));
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
注意:如果你使用方法扩展,你需要配置一些东西,见“如何启用 Dart 扩展方法”
我使用不同的方法进行无限滚动。我将ChangeNotifie r 类用于变量更改侦听器。如果变量发生变化,它会触发事件并最终命中 API。
class DashboardAPINotifier extends ChangeNotifier {
bool _isLoading = false;
get getIsLoading => _isLoading;
set setLoading(bool isLoading) => _isLoading = isLoading;
}
初始化 DashboardAPINotifier 类。
@override
void initState() {
super.initState();
_dashboardAPINotifier = DashboardAPINotifier();
_hitDashboardAPI(); // init state
_dashboardAPINotifier.addListener(() {
if (_dashboardAPINotifier.getIsLoading) {
print("loading is true");
widget._page++; // For API page
_hitDashboardAPI(); //Hit API
} else {
print("loading is false");
}
});
}
现在最好的部分是当您必须使用 API 时。如果您正在使用SliverList
,那么您必须在什么时候使用 API。
SliverList(delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
Widget listTile = Container();
if (index == widget._propertyList.length - 1 &&
widget._propertyList.length <widget._totalItemCount) {
listTile = _reachedEnd();
} else {
listTile = getItem(widget._propertyList[index]);
}
return listTile;
},
childCount: (widget._propertyList != null)? widget._propertyList.length: 0,
addRepaintBoundaries: true,
addAutomaticKeepAlives: true,
),
)
_reachEnd() method take care to hit the api. It trigger the `_dashboardAPINotifier._loading`
// Function that initiates a refresh and returns a CircularProgressIndicator - Call when list reaches its end
Widget _reachedEnd() {
if (widget._propertyList.length < widget._totalItemCount) {
_dashboardAPINotifier.setLoading = true;
_dashboardAPINotifier.notifyListeners();
return const Padding(
padding: const EdgeInsets.all(20.0),
child: const Center(
child: const CircularProgressIndicator(),
),
);
} else {
_dashboardAPINotifier.setLoading = false;
_dashboardAPINotifier.notifyListeners();
print("No more data found");
Utils.getInstance().showSnackBar(_globalKey, "No more data found");
}
}
注意:在您的 API 响应后,您需要通知侦听器,
setState(() {
_dashboardAPINotifier.setLoading = false;
_dashboardAPINotifier.notifyListeners();
}
final ScrollController controller = ScrollController();
void _listener() {
double maxPosition = controller.position.maxScrollExtent;
double currentPosition = controller.position.pixels;
/// You can change this value . It's a default value for the
/// test if the difference between the great value and the current value is smaller
/// or equal
double difference = 10.0;
/// bottom position
if ( maxPosition - currentPosition <= difference )
/// top position
else
if(mounted)
setState(() {});
}
@override
void initState() {
super.initState();
controller.addListener(_listener);
}
您可以使用包scroll_edge_listener。
它带有一个非常有用的偏移和去抖动时间配置。用 a 包裹你的滚动视图ScrollEdgeListener
并附加一个监听器。而已。
ScrollEdgeListener(
edge: ScrollEdge.end,
edgeOffset: 400,
continuous: false,
debounce: const Duration(milliseconds: 500),
dispatch: true,
listener: () {
debugPrint('listener called');
},
child: ListView(
children: const [
Placeholder(),
Placeholder(),
Placeholder(),
Placeholder(),
],
),
),