52

我正在尝试实现无限滚动功能。

我尝试ListView在 a 上使用 insideNotificationListener来检测滚动事件,但我看不到表明滚动是否到达视图底部的事件。

实现这一目标的最佳方法是什么?

4

9 回答 9

113

通常有两种方法可以做到这一点。

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,
  ),
)
于 2019-02-05T16:44:03.383 回答
36

您可以使用 aListView.builder创建包含无限项的滚动列表。itemBuilder当新细胞出现时,您将根据需要被召唤。

如果您想收到有关滚动事件的通知,以便从网络加载更多数据,您可以传递一个controller参数并使用addListener将侦听器附加到ScrollController. 的position可以ScrollController用来判断滚动是否接近底部。

于 2017-09-23T12:56:18.960 回答
22
_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
            }
        }
    );

科林的应该被接受的答案....

于 2018-07-30T04:35:24.477 回答
12

我想为collin jackson 提供的答案添加示例。参考以下片段

    var _scrollController = ScrollController();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
        // Perform your task
      }
    });

仅当列表中的最后一个项目可见时才会触发。

于 2018-12-13T15:00:37.317 回答
5

一个更简单的方法是这样的:

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.atEdgetrue当用户在顶部带有滚动条的末尾时,
log方法来自包import 'dart:developer';

于 2020-11-09T13:19:31.827 回答
3

我觉得这个答案是对 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 扩展方法”

于 2020-01-24T03:58:06.267 回答
1

我使用不同的方法进行无限滚动。我将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();
        }
于 2018-09-04T05:54:54.737 回答
1
  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);
 }
于 2020-11-27T12:29:50.167 回答
1

您可以使用包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(),
    ],
  ),
),
于 2022-02-02T00:43:01.343 回答