リストビューの最後までプログラムでスクロールする


108

ListViewアイテムの数を動的に変更できるスクロール可能なものがあります。リストの最後に新しいアイテムが追加されるたびに、プログラムでListView最後までスクロールしたいと思います。(たとえば、最後に新しいメッセージを追加できるチャットメッセージリストのようなもの)

私の推測ではScrollControllerStateオブジェクトにを作成して手動でListViewコンストラクターに渡す必要があるので、後でコントローラーでanimateTo()/jumpTo()メソッドを呼び出すことができます。ただし、最大スクロールオフセットを簡単に決定できないため、ある種scrollToEnd()の操作を単純に実行することは不可能のようです(ただし、簡単にパス0.0して初期位置にスクロールさせることができます)。

これを達成する簡単な方法はありますか?

ビューポートreverse: true内に収まるアイテムの数が少ないときにアイテムを上部に配置したいので、使用することは私にとって完璧な解決策ではありませんListView

回答:


104

でシュリンクラップListViewを使用する場合は、reverse: true0.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),
          );
        }
      ),
    );
  }
}

1
これでうまくいきました。私の特定のケースでは、ListViewをExpandedウィジェットに配置するためにネストされた列を使用する必要がありましたが、基本的な考え方は同じです。
yyoon 2017

21
-私は解決策を探して来て、ちょうど他の答えは、この達成するためのより良い方法かもしれないことを言及したかったstackoverflow.com/questions/44141148/...
Animeshジャイナ

6
私は同意します、その答え(私も書いた)は間違いなくより良いです。
コリンジャクソン

1
@Dennisこれにより、ListViewは下から逆の順序で開始されます。
CopsOnRoad

1
@CopsOnRoadからの回答は見栄えが良く、この回答は解決策というよりはハックのように見えます。
Roopak ANelliat19年

128

これを達成するためのScrollController.jumpTo()またはScrollController.animateTo()方法を使用します。

例:

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')),
  );
}

スムーズなスクロールが必要な場合は、jumpTo上記を使用する代わりに

_controller.animateTo(
  _controller.position.maxScrollExtent,
  duration: Duration(seconds: 1),
  curve: Curves.fastOutSlowIn,
);

4
Firebase Realtime Databaseの場合、これまでのところ250ミリ秒(おそらく本当に長い)で逃げることができました。どれだけ低くできるのだろうか?問題は、maxScrollExtentがウィジェットのビルドが完了して新しいアイテムが追加されるのを待つ必要があることです。コールバックからビルド完了までの平均期間をプロファイリングするにはどうすればよいですか?
SacWebDeveloper

2
これは、Firebaseからデータをフェッチするためにstreambuilderを使用することで適切に機能すると思いますか?送信ボタンにもチャットメッセージを追加しますか?また、インターネット接続が遅い場合でも機能しますか?あなたが提案することができればもっと良いもの。ありがとう。
ジェイムンガラ

@SacWebDeveloperこの問題とFirebaseに対するあなたのアプローチは何でしたか?
イドリススタック

@IdrisStack実際には、250msは明らかに十分な長さではなく、更新が行われる前にjumpToが発生することがあります。更新後のリストの長さを比較し、長さが1つ大きい場合は、一番下にジャンプするのがより良いアプローチだと思います。
SacWebDeveloper

@SacWebDeveloperに感謝します、あなたのアプローチはうまくいきます、多分私はあなたにFirestoreの文脈で別の質問をすることができます、それはトピックを少しそらすかもしれません。Qn。誰かにメッセージを送信しても、リストが自動的に一番下までスクロールしないのはなぜですか?メッセージを聞いて一番下までスクロールする方法はありますか?
イドリススタック

14

listViewScrollController.animateTo(listViewScrollController.position.maxScrollExtent) 最も簡単な方法です。


2
こんにちはジャック、あなたの答えに感謝します、私は同じことを書いたと思います、それで同じ解決策を再び私見に加えることの利点はありません。
CopsOnRoad

1
私はこのアプローチを使用していましたが、animateTo(...)を実行する前に画面をレンダリングする必要があるため、実行するには数ミリ秒待つ必要があります。たぶん、ハックなしでそれを行うための良い習慣を使用する良い方法があります。
RenanCoelho19年

1
それが正解です。現在の行で+100を入力すると、リストが一番下までスクロールします。listViewScrollController.position.maxScrollExtent +100のように;
リズワンアンサー

1
@RenanCoelhoからインスピレーションを得て、このコード行を100ミリ秒の遅延でタイマーでラップしました。
ymerdrengene

5

完璧な結果を得るために、私はColinJacksonとCopsOnRoadの回答を次のように組み合わせました。

_scrollController.animateTo(
                              _scrollController.position.maxScrollExtent,
                              curve: Curves.easeOut,
                              duration: const Duration(milliseconds: 500),
                            );

2

スクロールコントローラーを使用してリストの一番下に移動しようとすると、非常に多くの問題が発生するため、別のアプローチを使用します。

リストを一番下に送信するイベントを作成する代わりに、逆のリストを使用するようにロジックを変更します。

ですから、新しいアイテムを手に入れるたびに、リストの一番上に挿入して作成しました。

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

2
私がこれに関して抱えている唯一の問題は、スクロールバーが逆方向に機能することです。下にスクロールすると、上に移動します。
ThinkDigital

2

widgetBindingをinitstateに配置しないでください。代わりに、データベースからデータをフェッチするメソッドにwidgetBindingを配置する必要があります。たとえば、このように。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');
    }
   }

-2

これ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));
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.