【Flutter】画像を含んだWidgetを"もっと見る"で折り畳み開閉可能とする (See More/Read More)

2024/05/06に公開

1. はじめに

長いテキストやWidgetを折り畳みたい場合のサンプルです。

以下のパッケージがありましたが、このパッケージはテキストにしか適用できないようですね。

https://pub.dev/packages/readmore

テキストだけでなく画像が入っているようなケースでも折り畳んで、「もっと見る」をタップしたら全体を表示するようにしたいです。

2. 挙動

以下のような挙動になりました。See Moreをタップすると全体を表示し、Closeタップで閉じます。画像はネットワークから入手しています(Lorem Picsumを使用させていただきました)。

3. コード

以下のExpandableSeeMoreクラスが中心となるコードです。

https://github.com/motucraft/expandable_see_more/blob/main/lib/main.dart#L114-L214

useEffect(() {
  () async {
    if (completer != null) {
      await completer;
    }

    WidgetsBinding.instance.addPostFrameCallback((_) {
      final box = contentKey.currentContext?.findRenderObject() as RenderBox?;
      if (box != null && box.size.height < collapsedHeight) {
        isExpandable.value = false;
      }
    });
  }();

  return null;
}, [child]);

このuseEffectフック内で対象Widgetのサイズを入手するようにしています。画像はネットワークから入手しますので、画像のロード完了後にサイズを取得します。そのために、Completerを使って完了を待つようにしています。

あとは、See Moreタップ、Closeタップをflutter_hooksuseStateで状態管理しつつ、AnimatedSizeConstrainedBoxで表示領域を変更しているだけです。

展開の必要がない場合は、以下のようにSee Moreが表示されないようにしています。

ExpandableSeeMoreの呼び出し元は、以下のようにCompleterとWidgetを渡しています。

final imageLoadCompleter = Completer();
final image = CachedNetworkImage(
  imageUrl: 'https://picsum.photos/id/197/800/600',
  imageBuilder: (_, imageProvider) {
    if (!imageLoadCompleter.isCompleted) {
      imageLoadCompleter.complete();
    }

    return Image(fit: BoxFit.fitWidth, image: imageProvider);
  },
);
return ExpandableSeeMore(
  completer: imageLoadCompleter.future,
  child: Wrap(
    alignment: WrapAlignment.center,
    children: [
      Text('Some Text. ' * 10),
      image,
      const Text('No Baseball, No Life.', style: TextStyle(fontSize: 24)),
    ],
  ),
);

4. おわりに

画像を含んだWidgetを折り畳んだり展開したりして表示することができました。

See Moreで展開したときにスクロール位置を調整して、当該コンテンツがトップに表示されるようにできたりすると良いかもとも思いましたが、最低限こんなところでしょうか。

あとは、例えばColumnウィジェットをExpandableSeeMoreのchildへ渡すと、おそらくオーバーフローすることになると思いますので、この辺り改善点かなと思います。

GitHubで編集を提案

Discussion