以编程方式滚动到ListView的末尾

yyo*_*oon 51 flutter

我有一个可滚动项ListView,其中项目的数量可以动态更改.每当一个新项目添加到列表的末尾,我想以编程方式滚动ListView到最后.(例如,聊天消息列表,最后可以添加新消息)

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

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

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

Col*_*son 46

如果您使用收缩包装ListViewreverse: true,它滚动到0.0会做你想要什么.

import 'dart:collection';

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Example',
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> _messages = <Widget>[new Text('hello'), new Text('world')];
  ScrollController _scrollController = new ScrollController();

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Center(
        child: new Container(
          decoration: new BoxDecoration(backgroundColor: Colors.blueGrey.shade100),
          width: 100.0,
          height: 100.0,
          child: new Column(
            children: [
              new Flexible(
                child: new ListView(
                  controller: _scrollController,
                  reverse: true,
                  shrinkWrap: true,
                  children: new UnmodifiableListView(_messages),
                ),
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        child: new Icon(Icons.add),
        onPressed: () {
          setState(() {
            _messages.insert(0, new Text("message ${_messages.length}"));
          });
          _scrollController.animateTo(
            0.0,
            curve: Curves.easeOut,
            duration: const Duration(milliseconds: 300),
          );
        }
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我来寻找解决方案,只是想提一下,另一个答案可能是实现这个目标的更好方法 - /sf/ask/3089880391/ scrollcontroller (7认同)
  • 我同意,那个答案(我也写过)肯定更好. (3认同)

Cop*_*oad 38

使用ScrollController.jumpTo()ScrollController.animateTo()方法来实现这一目标.

这是代码片段(1秒后,ListView将滚动到底部)

class _HomePageState extends State<HomePage> {
  ScrollController _controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    Timer(Duration(milliseconds: 1000), () => _controller.jumpTo(_controller.position.maxScrollExtent));

    return ListView.builder(
      controller: _controller,
        itemCount: 50,
        itemBuilder: (context, index) => ListTile(title: Text("ListTile"),));
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 对于 Firebase 实时数据库,到目前为止,我已经能够逃脱 250 毫秒(这可能真的很长)。我想知道我们能走多低?问题是 maxScrollExtent 需要等待小部件构建完成并添加新项目。如何分析从回调到构建完成的平均持续时间? (5认同)
  • 您认为使用 Streambuilder 从 Firebase 获取数据可以正常工作吗?还在发送按钮上添加聊天消息吗?另外,如果互联网连接速度很慢,它还能工作吗?如果你能建议的话还有什么更好的吗?谢谢。 (2认同)
  • 希望不是一种策略:系统可能会根据需要延迟应用程序的执行。您应该在后帧回调中执行此操作,如其他答案中所述:/sf/answers/4486028991/ (2认同)

小智 17

listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent) 是最简单的方法。

  • 嗨,杰克,感谢您的回答,我相信我写了同样的内容,所以恕我直言,再次添加相同的解决方案没有任何优势。 (5认同)
  • 我正在使用这种方法,但您需要等待一些毫秒才能执行此操作,因为在运行 animateTo(...) 之前需要渲染屏幕。也许我们有一个很好的方法,使用良好的实践来做到这一点,而不需要黑客。 (2认同)
  • 受到@RenanCoelho 的启发,我将这行代码包装在一个延迟 100 毫秒的计时器中。 (2认同)

小智 11

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

_scrollController.animateTo(
    _scrollController.position.maxScrollExtent,
    curve: Curves.easeOut,
    duration: const Duration(milliseconds: 500),
 );
Run Code Online (Sandbox Code Playgroud)


小智 10

不要将 widgetBinding 放在 initstate 中,而是需要将其放在从数据库获取数据的方法中。例如,像这样。如果放入 initstate,则scrollcontroller不会附加到任何列表视图。

    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');
    }
   }
Run Code Online (Sandbox Code Playgroud)


Rob*_*eda 8

虽然所有答案都产生了预期的效果,但我们应该在这里做一些改进。

  • 首先,在大多数情况下(谈到自动滚动)使用 postFrameCallbacks 是无用的,因为在 ScrollController 附件(由attach方法生成)之后可以呈现一些东西,控制器将滚动到他知道的最后一个位置,并且该位置不能您认为的最新情况。

  • 使用reverse:true应该是一个很好的技巧来“拖尾”内容,但物理将被颠倒,因此当您尝试手动移动滚动条时,您必须将其移动到另一侧 - > BAD UX。

  • 在设计图形界面时使用计时器是一种非常糟糕的做法 -> 计时器在用于更新/生成图形工件时是一种病毒。

无论如何谈论这个问题,完成任务的正确方法是使用jumpTo带有hasClients方法的方法作为保护。

是否有任何 ScrollPosition 对象使用 attach 方法将自己附加到 ScrollController。如果为 false,则不得调用与 ScrollPosition 交互的成员,例如 position、offset、animateTo 和 jumpTo

用代码说话简单地做这样的事情:

if (_scrollController.hasClients) {
    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
Run Code Online (Sandbox Code Playgroud)

无论如何,这段代码仍然不够,即使可滚动不在屏幕末尾,该方法也会被触发,因此如果您手动移动栏,该方法将被触发并执行自动滚动。

我们可以做得更好,在听众的帮助下和一些 bool 就可以了。
我正在使用这种技术在 SelectableText 中可视化大小为 100000 的 CircularBuffer 的值,并且内容不断正确更新,自动滚动非常流畅,即使对于非常非常长的内容也没有性能问题。也许正如有人在其他答案中所说的那样,该animateTo方法可以更流畅、更可定制,所以请随意尝试。

  • 首先声明这些变量:
ScrollController _scrollController = new ScrollController();
bool _firstAutoscrollExecuted = false;
bool _shouldAutoscroll = false;
Run Code Online (Sandbox Code Playgroud)
  • 然后让我们创建一个自动滚动的方法:
void _scrollToBottom() {
    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}
Run Code Online (Sandbox Code Playgroud)
  • 然后我们需要监听器:
void _scrollListener() {
    _firstAutoscrollExecuted = true;

    if (_scrollController.hasClients && _scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
        _shouldAutoscroll = true;
    } else {
        _shouldAutoscroll = false;
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 注册它initState
@override
void initState() {
    super.initState();
    _scrollController.addListener(_scrollListener);
}
Run Code Online (Sandbox Code Playgroud)
  • 删除你的监听器dispose
@override
void dispose() {
    _scrollController.removeListener(_scrollListener);
    super.dispose();
}
Run Code Online (Sandbox Code Playgroud)
  • 然后_scrollToBottom根据您的逻辑和需求,在您的setState:
setState(() {
    if (_scrollController.hasClients && _shouldAutoscroll) {
        _scrollToBottom();
    }

    if (!_firstAutoscrollExecuted && _scrollController.hasClients) {
         _scrollToBottom();
    }
});
Run Code Online (Sandbox Code Playgroud)

解释

  • 我们做了一个简单的方法:_scrollToBottom()为了避免代码重复;
  • 我们做了一个_scrollListener()和我们连接它的_scrollControllerinitState- >后的第一时间将被触发,该滚动条会移动。在此侦听器中,我们更新 bool 值的值_shouldAutoscroll以了解滚动条是否位于屏幕底部。
  • 我们删除了监听器dispose只是为了确保在小部件处理后不会做无用的事情。
  • 在我们setState确定_scrollController已附加并且位于底部(检查 的值shouldAutoscroll)时,我们可以调用_scrollToBottom().
    同时,仅在第一次执行时,我们强制_scrollToBottom()短路 的值_firstAutoscrollExecuted


nag*_*ya0 7

_controller.jumpTo(_controller.position.maxScrollExtent);
_controller.animateTo(_controller.position.maxScrollExtent);
Run Code Online (Sandbox Code Playgroud)

这些调用对于动态大小的项目列表效果不佳。我们当时不知道您调用jumpTo()列表的长度,因为所有项目都是可变的,并且是在我们向下滚动列表时延迟构建的。

这可能不是明智的方法,但作为最后的手段,您可以执行以下操作:

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


And*_*rej 6

当我使用StreamBuilder小部件从数据库中获取数据时,我遇到了这个问题。我把它WidgetsBinding.instance.addPostFrameCallback放在小部件的方法之上build,它不会一直滚动到最后。我通过这样做修复了它:

...
StreamBuilder(
  stream: ...,
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    // Like this:
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_controller.hasClients) {
        _controller.jumpTo(_controller.position.maxScrollExtent);
      } else {
        setState(() => null);
      }
     });

     return PutYourListViewHere
}),
...
Run Code Online (Sandbox Code Playgroud)

我也尝试过,_controller.animateTo但似乎不起作用。


Ily*_*ath 5

如果您想看到最后一个可见的项目,并且从底部开始有填充,那么添加额外的距离,如下所示

 _controller.jumpTo(_controller.position.maxScrollExtent + 200);
Run Code Online (Sandbox Code Playgroud)

这里 200 是额外距离