J'ai une variable ListView
où le nombre d'éléments peut changer dynamiquement. Chaque fois qu'un nouvel élément est ajouté à la fin de la liste, j'aimerais faire défiler par programme la ListView
jusqu'à la fin. (par exemple, quelque chose comme une liste de messages de discussion où de nouveaux messages peuvent être ajoutés à la fin)
Je suppose que je devrais créer une variable ScrollController
dans mon objet State
et la transmettre manuellement au constructeur ListView
afin de pouvoir ultérieurement appeler la méthode animateTo()
/jumpTo()
sur le contrôleur. Cependant, étant donné que je ne parviens pas à déterminer facilement le décalage de défilement maximum, il semble impossible de réaliser simplement une opération de type scrollToEnd()
(alors que je peux facilement passer 0.0
pour le faire défiler jusqu'à la position initiale).
Y a-t-il un moyen facile d'y parvenir?
Utiliser reverse: true
n’est pas une solution parfaite pour moi, car j’aimerais que les éléments soient alignés en haut lorsque le nombre d’éléments pouvant être insérés dans la fenêtre ListView
est petit.
Si vous utilisez une ListView
rétrécie avec reverse: true
, le faire défiler jusqu'à 0,0 fera ce que vous voulez.
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),
);
}
),
);
}
}
Utilisez la méthode ScrollController.jumpTo()
ou ScrollController.animateTo()
pour y parvenir.
Voici l'extrait de code (après 1 seconde, la ListView
défilera vers le bas)
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"),));
}
}
Timer(Duration(milliseconds: 300),(){
_scrollController.animateTo(_scrollController.position.maxScrollExtent,curve: Curves.ease,
duration: const Duration(milliseconds: 300), );
});
Ce code fonctionne sans reverse:true
, donc la liste ressemble à un nouvel élément du dernier index.
J'ai tellement de difficulté à utiliser le contrôleur de défilement pour aller au bas de la liste que j'utilise une autre approche.
Au lieu de créer un événement pour envoyer la liste au bas de la liste, je change de logique pour utiliser une liste inversée.
Donc, chaque fois que j'ai un nouvel article, je le fais simplement à insérer en haut de la liste.
// 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))
),
),