我有一个可滚动的ListView,其中项目的数量可以动态变化。每当一个新项目被添加到列表的末尾时,我希望以编程的方式将ListView滚动到末尾。(例如,类似聊天消息列表的东西,可以在最后添加新消息)

我的猜测是,我需要在我的State对象中创建一个ScrollController,并手动将其传递给ListView构造函数,这样我就可以稍后在控制器上调用animateTo() / jumpTo()方法。然而,由于我不容易确定最大滚动偏移量,因此似乎不可能简单地执行scrollToEnd()类型的操作(而我可以轻松地传递0.0使其滚动到初始位置)。

有没有简单的方法来实现这个目标?

使用reverse: true对我来说不是一个完美的解决方案,因为当只有少量的项目适合ListView视口时,我希望项目在顶部对齐。


当前回答

为了得到完美的结果,我将Colin Jackson和CopsOnRoad的答案结合如下:

_scrollController.animateTo(
    _scrollController.position.maxScrollExtent,
    curve: Curves.easeOut,
    duration: const Duration(milliseconds: 500),
 );

其他回答

不要将widgetBinding放在初始化状态,相反,您需要将它放在从数据库获取数据的方法中。比如,像这样。如果将scrollcontroller置于initstate,它将不会附加到任何listview。

    Future<List<Message>> fetchMessage() async {

    var res = await Api().getData("message");
    var body = json.decode(res.body);
    if (res.statusCode == 200) {
      List<Message> messages = [];
      var count=0;
      for (var u in body) {
        count++;
        Message message = Message.fromJson(u);
        messages.add(message);
      }
      WidgetsBinding.instance
          .addPostFrameCallback((_){
        if (_scrollController.hasClients) {
          _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
        }
      });
      return messages;
    } else {
      throw Exception('Failed to load album');
    }
   }

我使用的是一个动态列表视图,但scrollController.animateTo()不会工作在这里提到的动态列表https://stackoverflow.com/a/67561421/13814518,我甚至没有在以前的答复中找到任何好的解决方案。下面是我解决这个问题的方法。

void scrollToMaxExtent() {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    scrollController.animateTo(
      scrollController.position.maxScrollExtent,
      duration: const Duration(milliseconds: 100),
      curve: Curves.easeIn,
    );
  });
}
_controller.jumpTo(_controller.position.maxScrollExtent);
_controller.animateTo(_controller.position.maxScrollExtent);

这些调用不能很好地用于动态大小的项列表。在调用jumpTo()时,我们不知道列表有多长,因为所有的项都是变量,并且是在向下滚动列表时惰性构建的。

这可能不是聪明的方法,但作为最后的手段,你可以这样做:

Future scrollToBottom(ScrollController scrollController) async {
  while (scrollController.position.pixels != scrollController.position.maxScrollExtent) {
    scrollController.jumpTo(scrollController.position.maxScrollExtent);
    await SchedulerBinding.instance!.endOfFrame;
  }
}

为了得到完美的结果,我将Colin Jackson和CopsOnRoad的答案结合如下:

_scrollController.animateTo(
    _scrollController.position.maxScrollExtent,
    curve: Curves.easeOut,
    duration: const Duration(milliseconds: 500),
 );

我在尝试使用滚动控制器到列表底部时遇到了很多问题,所以我使用了另一种方法。

我没有创建一个事件将列表发送到底部,而是将逻辑更改为使用反向列表。

所以,每次我有一个新项目,我简单地,在列表的顶部插入。

// add new message at the begin of the list 
list.insert(0, message);
// ...

// pull items from the database
list = await bean.getAllReversed(); // basically a method that applies a descendent order

// I remove the scroll controller
new Flexible(
  child: new ListView.builder(
    reverse: true, 
    key: new Key(model.count().toString()),
    itemCount: model.count(),
    itemBuilder: (context, i) => ChatItem.displayMessage(model.getItem(i))
  ),
),