ScrollablePositionedListの使い方メモ

ntaoo
6 min readMay 9, 2020

--

詳細な解説ではありませんが、メモ書きだけでも共有します。

ListViewとの違い

ListViewには、Listのindexを指定してその位置に移動するAPIがありません。 そのような機能が必要な場合は、替わりにScrollablePositionedListを使います。

ItemScrollController

ListViewでは ScrollController を使用しますが、このWidgetでは代わりにItemScrollControllerを使用します。

ItemScrollControllerのscrollToで、Listのindexを指定して、指定アニメーションでスクロールします。jumpToでは、アニメーションせずに瞬時にそのIndexの位置に移動します。

ItemPositionsListener

ViewPort内に表示されているList Itemの情報を得るコールバック関数を指定できます。その情報が変化するたびにコールバック関数が呼ばれます。必要に応じて、Streamで中継してThrottleなどすると良いでしょう。ViewPort内の情報のみを返すので、たとえListに数千のItemがあろうが、この関数が返すPosition Listのlengthは数個程度になります。

そのList Itemに関する情報を格納した ItemPositionには、そのitemのindex, leadingEdge, trailingEdgeの情報があります。

  • indexは、Listにおけるそのitemのindexのことです。
  • leadingEdgeは、ViewPortのleadingEdgeから、そのItemのleadingEdgeまでの距離を指します。
  • trailingEdgeは、ViewPortのleadingEdge から、そのItemのtrailingEdgeまでの距離を指します。

leadingEdgeとは、たとえば、ScrollablePositionedListの方向を縦方向(Vertical)に指定しているならば、上端を指します。

スクロール位置の 保存と復元 ( save / restore )

ListViewならばPageStorageを組み合わせてスクロール位置をsave / restoreできます。

ScrollablePositionedListでは、同じようにPageStorageを組み合わせると、私の環境ではこのWidgetがクラッシュしてしまいました。

Failed assertion: line 491 pos 14: ‘minScrollExtent <= maxScrollExtent’: is not true

PageStorageの替わりに、ItemPositionsListenerのコールバック情報のindexとleadingEdgeを組み合わせることでスクロール位置となるので、それらを保存するコードを書きましょう。

PageStorage の構造と同じく、Map<String, ScrollPosition> で、keyに応じてScrollPositionをsave, loadする単純なもので良いでしょう。必要ならば適当な手段で永続化します。たとえば、Firestore など。

class ScrollPosition {
final int index;
final double leadingEdge;
ScrollPosition(this.index, this.leadingEdge)
: assert(index != null),
assert(leadingEdge != null);

@override
String toString() =>
'ScrollPosition index: $index leadingEdge: $leadingEdge.';
}

ScrollablePositionedListのライフサイクルを超えて情報を保持する必要があるので、たとえばWidget階層のRoot (MaterialAppの祖先)でProviderを使用してそのMap<String, ScrollPosition> 情報を保持するstorageを宣言しておき、ScrollablePositionedListを含むWidgetで Provider.of で取得します。

ScrollablePositionedListには、 initialScrollIndex と initialAlignment を指定できますので、それぞれ index と leadingEdgeを指定すると、スクロール位置を復元できます。

ScrollablePositionedList.builder(
initialScrollIndex: scrollPosition.index,
initialAlignment: scrollPosition.leadingEdge,
...

注意点

いくつかの不具合が報告されています。私が遭遇したものは以下です。

不正な値、たとえば initialScrollIndexやinitialAlignmentに誤ってnullを指定すると、そのコードはassertでnullを弾いていないので、コードの奥深い箇所でエラーになってしまいます。そのほかにも不具合が発生する場合があるそうです。このpackageのGitHub Issue Listを確認すると良いでしょう。

そういったリスクはありますが、仮にItemのindexを指定して移動する機能を自作するとなると、かなり苦労しそうです。その難易度の高さ故にListViewにも、この記事の冒頭でのGitHub Issueで議論されているように、そのAPIはありません。

Itemのindexを指定して移動する機能がどうしても必要ならば、リスクを認識した上で採用するといいでしょう。典型的なユースケースは、チャットアプリのメッセージリストが挙げられるでしょう。

--

--