我正在尝试实现无限滚动功能。
我试过在NotificationListener
中使用ListView
来检测滚动事件,但是我找不到一个事件来判断滚动是否已经到达视图的底部。
有什么最好的方法可以实现这个功能呢?
我正在尝试实现无限滚动功能。
我试过在NotificationListener
中使用ListView
来检测滚动事件,但是我找不到一个事件来判断滚动是否已经到达视图的底部。
有什么最好的方法可以实现这个功能呢?
通常有两种方法来完成它。
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,
),
)
使用ListView.builder
可以创建一个无限滚动的列表,itemBuilder
会在新的单元格被显示时按需调用。
如果你想要在滚动事件发生时得到通知,以便从网络加载更多数据,可以传递一个controller
参数并使用addListener
将一个监听器附加到ScrollController
上。可以利用ScrollController
的position
来确定滚动是否接近底部。
_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的回答应该被接受....
我想为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';
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();
}
}
NotificationListener<ScrollNotification>(
onNotification: (notification) {
final metrices = notification.metrics;
if (metrices.atEdge && metrices.pixels == 0) {
//you are at top of list
}
if (metrices.pixels == metrices.minScrollExtent) {
//you are at top of list
}
if (metrices.atEdge && metrices.pixels > 0) {
//you are at end of list
}
if (metrices.pixels >= metrices.maxScrollExtent) {
//you are at end of list
}
return false;
},
child: ListView.builder());
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);
}
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");
}
});
}
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");
}
}
setState(() {
_dashboardAPINotifier.setLoading = false;
_dashboardAPINotifier.notifyListeners();
}
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(),
],
),
),