Flutter-ListViewで末尾に追加したアイテムにジャンプする

ListView(サンプルはReorderableListView)なんだけど、末尾にアイテムを追加しそこにスクロール位置を移動する方法について。

実装方法

概要で、一部抜粋なんだけど以下の様な実装になる。

  bool toLastIndex = false;

  void goToLast() async {
    if (toLastIndex) {
      toLastIndex = false;
      await Future.delayed(const Duration(milliseconds: 100));
      scroll.jumpTo(scroll.position.maxScrollExtent);
    }
  }

  // リストビューを作成する
  Widget _listView() {
    goToLast();
    final result = ReorderableListView.builder(
      itemBuilder: (context, i) => Card(
          key: Key("$i"),
          child: ListTile(
              trailing: doReorder

中略
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            value.add(ViewItem(counter, "Item $counter"));
            counter++;
            toLastIndex = true;
          });
        },
        child: const Icon(Icons.add),
      ),

ScrollController.jumpTo(scroll.position.maxScrollExtent)を使うのだけど、これをこのまま使っても追加された行を表示しない。

maxScrollExtentの値は、アイテムを追加する前のListViewの状態の時での最大なので、この値を使っても追加前の最後の行を表示することになる。

そこで、delayを入れてmaxScrollExtentの値をListView構築後の値になったと思われる時間でジャンプするという動作にしてある。
ただこの実装でも確実ではない。ListViewの構築に指定された秒以上の時間がかかれば動かないのだから。Flutterでは特定fps以内に画面をリフレッシュするようにという指針があるので、それを基準に決めていけばいいのかもしれない。

ここに至るまでの経緯

初めに

初期実装した時、追加したのにそのひとつ前のアイテムに移動してしまうという問題が出てしまい、いろいろ探した結果、以下のパッケージを使うことにした。

scrollable_positioned_list | Flutter package
A list with helper methods to programmatically scroll to an item.

構築時の初期位置を指定できるので、この初期位置に最終行を指定することでとりあえず解決した。

順序変更の実装

表示されているリストをドラッグ操作で位置交換ができるようにしようと画策。


赤丸部分を押すと、下の様に順序変更のドラッグハンドルを表示して移動可能にしている。

この実装では、順序変更時と通常時で、表示するアイテムを変えることで実装してみた。

やり方としては、reOrderフラグを用意してそのTrue/Falseの状態で構築するWidgetを変更するという方法。
Trueの場合はReorderableListView、Flaseの場合ScrollablePositionedList。

ここで問題となったのが、reOrderフラグを変更した時にスクロール位置が異なってしまうという点。
ScrollablePositionedListは初期値で与えられた位置。ReorderableListViewは先頭となる。

この部分はreOrderのON/OFFにかかわらず同じ位置であってほしかった。

reOrderフラグで構築するウィジェットを切り替えるのではなく、ReorderableListViewを統一して使用するしかないと決断をした。

位置決定その1

ここで元に戻り、ScrollController.jumpTo(scroll.position.maxScrollExtent)で何とか最終行に移動できないかということで、また検索した結果、Stackoverflowに1件記載があった。

Flutter: Listview get full size of scrollcontroller after adding item to list & scroll to end
This post describes a very similar problem, but the answer there doesn't solve all problems: I have a potentially long L...

こちらの方は、ウィジェット構築後に実施されるポスト処理に関数を登録し、その中でジャンプするという内容。
ただ単純にジャンプするだけではやはり最終行には飛ばす、Scrollable.ensureVisible(lastKey.currentContext);という処理を入れなければいけないという。

実装が面倒だったのだけど、サンプルソースでは上の実装でうまくいったので、ヨシ!!、と思った。

FutureBuilderとの組み合わせで難あり

上の実装を本ちゃんアプリの方に実装しなおしたのだけど、うまくいかなかった。

FutureBuilder->ReorderableListViewという階層構造になっているのだけど、FutureBuilderに渡したFutureの途中でReorderableListViewが消えるという状態になり、その時にポスト処理が動作したため、jumpToを実行中に例外が発生してしまった。

FutureBuilderで渡したFutureがすぐに値が確定するというものでも、すぐにReorderableListViewの構築が走るというわけではないので、一度登録されているReorderableListViewが削除され、Futureの実施途中であるウィジェットが表示されたのだろう。
このタイミングでポスト処理が動作したのだと思う。

で、最初の実装になる

どうするかということで、FutureBuilder ListViewで検索した結果、以下のものがヒットした。

Scroll a future ListView
I'm creating a page which has a list of items. I want to scroll to the bottom of the page after the list has rendered. T...

これをもとに実装しなおしたところ、うまくいったというわけである。

コメント

タイトルとURLをコピーしました