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

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

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

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


当前回答

如果你想看到最后一项从底部填充可见,然后添加额外的距离像这样

 _controller.jumpTo(_controller.position.maxScrollExtent + 200);

这里200是额外的距离

其他回答

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

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

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

// 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))
  ),
),

优点:

这是一个100%有效的解决方案。它总是滚动到最后,在线上一些其他的解决方案。而且,它不需要倒车。

缺点:

锁定曲线。线性动画曲线。

    Future.doWhile(() {
      if (scrollController.position.extentAfter == 0)
        return Future.value(false);
      return scrollController
          .animateTo(scrollController.position.maxScrollExtent,
              duration: Duration(milliseconds: 100), curve: Curves.linear)
          .then((value) => true);
    });

截图:

Scrolling with animation: final ScrollController _controller = ScrollController(); // This is what you're looking for! void _scrollDown() { _controller.animateTo( _controller.position.maxScrollExtent, duration: Duration(seconds: 2), curve: Curves.fastOutSlowIn, ); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton.small( onPressed: _scrollDown, child: Icon(Icons.arrow_downward), ), body: ListView.builder( controller: _controller, itemCount: 21, itemBuilder: (_, i) => ListTile(title: Text('Item $i')), ), ); } Scrolling without animation: Replace above _scrollDown method with this: void _scrollDown() { _controller.jumpTo(_controller.position.maxScrollExtent); }

我的解决方案:

1 . 像这样定义全局键:

final lastKey = GlobalKey();

2 . 附加到最后一条消息

SingleChildScrollView(
    controller: scrollController,
    padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
    physics: const AlwaysScrollableScrollPhysics(),
    child: Column(
        children: List.generate(
            data.length,
            (index) {
            return BuildMessage(
                key:
                    data.length == index + 1 ? lastKey : null,
                message: data[index],
                focusNode: focusNode,
            );
            },
        ),
    ),
)

3 . 创建滚动的函数调用

void scrollToBottom() {
    Scrollable.ensureVisible(lastKey.currentContext!,alignment: 1, duration: const Duration(milliseconds: 500));
}

当你想滚动到底部时调用,延迟100毫秒

Timer(const Duration(milliseconds: 100),() => scrollToBottom());

你可以使用0.09*height作为列表中一行的高度,_controller的定义如下:_controller = ScrollController();

(BuildContext context, int pos) {
    if(pos != 0) {
        _controller.animateTo(0.09 * height * (pos - 1), 
                              curve: Curves.easeInOut,
                              duration: Duration(milliseconds: 1400));
    }
}