Программная прокрутка до конца ListView

У меня есть прокручиваемый ListView, где количество элементов может изменяться динамически. Всякий раз, когда новый элемент добавляется в конец списка, я хотел бы программно прокрутить ListView до конца. (например, что-то вроде списка сообщений чата, где новые сообщения могут быть добавлены в конце)

Я предполагаю, что мне нужно будет создать ScrollController в моем State объекте и передать его вручную конструктору ListView, поэтому я могу позже вызвать метод animateTo()/jumpTo() на контроллере. Однако, поскольку я не могу легко определить максимальное смещение прокрутки, представляется невозможным просто выполнить операцию типа scrollToEnd() (тогда как я могу легко пройти 0.0, чтобы прокрутить ее до исходной позиции).

Есть ли простой способ достичь этого?

Использование reverse: true для меня не идеальное решение, потому что я хотел бы, чтобы элементы выравнивались вверху, когда есть только небольшое количество элементов, которые вписываются в окно просмотра ListView.

Ответ 1

Если вы используете обертку ListView с reverse: 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),
          );
        }
      ),
    );
  }
}

Ответ 2

Для этого используйте 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"),));
  }
}

Ответ 3

У меня так много проблем при попытке использовать контроллер прокрутки, чтобы перейти к нижней части списка, и я использую другой подход.

Вместо того, чтобы создавать событие для отправки списка в конец, я изменяю свою логику, чтобы использовать обратный список.

Таким образом, каждый раз, когда у меня появляется новый элемент, я просто делаю его на вкладке вверху списка.

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

Ответ 4

listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent) - самый простой способ.