Eu tenho um rolável ListView
onde o número de itens pode mudar dinamicamente. Sempre que um novo item é adicionado ao final da lista, gostaria de rolar programaticamente o ListView
até o final. (por exemplo, algo como uma lista de mensagens de bate-papo onde novas mensagens podem ser adicionadas no final)
Meu palpite é que eu precisaria criar um ScrollController
no meu State
objeto e passá-lo manualmente para o ListView
construtor, para que posteriormente eu pudesse chamar animateTo()
/ jumpTo()
método no controlador. No entanto, como não posso determinar facilmente o deslocamento máximo de rolagem, parece impossível simplesmente realizar um scrollToEnd()
tipo de operação (ao passo que posso facilmente passar 0.0
para fazê-lo rolar para a posição inicial).
Existe uma maneira fácil de conseguir isso?
Usar reverse: true
não é uma solução perfeita para mim, porque eu gostaria que os itens fossem alinhados na parte superior quando houver apenas um pequeno número de itens que cabem na ListView
janela de visualização.
Use
ScrollController.jumpTo()
ouScrollController.animateTo()
método para conseguir isso.Exemplo:
final _controller = ScrollController(); @override Widget build(BuildContext context) { // After 1 second, it takes you to the bottom of the ListView Timer( Duration(seconds: 1), () => _controller.jumpTo(_controller.position.maxScrollExtent), ); return ListView.builder( controller: _controller, itemCount: 50, itemBuilder: (_, __) => ListTile(title: Text('ListTile')), ); }
Se você quiser uma rolagem suave, em vez de usar o
jumpTo
acima, use_controller.animateTo( _controller.position.maxScrollExtent, duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, );
fonte
listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent)
é a maneira mais simples.fonte
para obter os resultados perfeitos, combinei as respostas de Colin Jackson e CopsOnRoad da seguinte forma:
_scrollController.animateTo( _scrollController.position.maxScrollExtent, curve: Curves.easeOut, duration: const Duration(milliseconds: 500), );
fonte
Tenho tantos problemas ao tentar usar o controlador de rolagem para ir para o final da lista que uso outra abordagem.
Em vez de criar um evento para enviar a lista para o final, mudo minha lógica para usar uma lista reversa.
Assim, cada vez que tenho um novo item, eu simplesmente, insiro no topo da lista.
// 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)) ), ),
fonte
Não coloque widgetBinding no initstate; em vez disso, você precisa colocá-lo no método que busca seus dados no banco de dados. por exemplo, assim. Se colocado em initstate, o
scrollcontroller
não será anexado a nenhum 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'); } }
fonte
você pode usar isto onde
0.09*height
é a altura de uma linha na lista e_controller
é definido assim_controller = ScrollController();
(BuildContext context, int pos) { if(pos != 0) { _controller.animateTo(0.09 * height * (pos - 1), curve: Curves.easeInOut, duration: Duration(milliseconds: 1400)); } }
fonte