
menu事業部 フロントエンドエンジニアの坂井田です。
今回は、Flutterで大量の画像付きリストをスムーズにスクロールできるようにするtipsをご紹介します。
- 背景
- 結論
- (補足)リスト表示について
- 1. cached_network_image パッケージで遅延読み込み
- 2. キャッシュを適切に設定する
- 3. サーバ側で画像を軽量化する
- 最後に
- 📝 2024/06/07 追記
背景
作成中のアプリには商品を一覧で表示する機能があるのですが、店舗によっては数千規模の商品が登録されている場合があります。
また、このアプリはモバイルネットワークでの運用も考えられるため、なるべくキャッシュを効かせた状態でストレス無く閲覧できるように実装する必要があり、この調査を行いました。
結論
cached_network_image
パッケージで遅延読み込み- キャッシュを適切に設定する
- サーバ側で画像を軽量化する
上記3つを取り入れることで、先程挙げた課題点を解決することが出来ました。
次の章で順番に解説します。
(補足)リスト表示について

Flutterでリスト表示をするには ListView
ウィジェットを使用しますが、これには以下の4通りの書き方があります。
- 1️⃣
ListView
children
に書いた部品をそのままリスト化する、一番シンプルな書き方です。
return ListView( children: const [ Text("aaa"), Text("bbb"), Text("ccc"), ], );
return ListView.builder( itemCount: scrollItemCount, itemBuilder: (context, index) { return ListTile( title: Text("Item $index"), onTap: () {}, ); }, );
- 3️⃣
ListView.separated
要素間に表示するウィジェットを指定する場合の書き方です。
return ListView.separated( itemCount: scrollItemCount, separatorBuilder: (context, index) { return const Text("区切り要素"); }, itemBuilder: (context, index) { return ListTile( title: Text("Item $index"), onTap: () {}, ); }, );
- 4️⃣
ListView.custom
ListView.builder
のカスタマイズバージョンです。
1. cached_network_image
パッケージで遅延読み込み
大量の項目を動的にレンダリングしたいため、まずは ListView.builder
を用いてシンプルに画像を表示してみます。
※ダミー画像の表示には以下のサービスを使用しています fakeimg.pl
シンプルコード
return ListView.builder( itemCount: scrollItemCount, itemBuilder: (context, index) { return ListTile( leading: Padding( padding: const EdgeInsets.all(4), child: Image.network( "https://fakeimg.pl/300/?font_size=150&text=$index", height: 100, ), ), title: Text("Item $index"), onTap: () {}, ); }, );

このコードを実行すると、スクロールがカクつくことがわかります。
これは、読み込みが完了するまでは画像の部分に何も表示されないために発生するもので、読み込み中に仮のウィジェットを表示してあげることで解消します。
今回は、読み込み中の表示と取得画像の表示の切り替えに cached_network_image
パッケージを使用しました!
書き換え後のコード
return ListView.builder( itemCount: scrollItemCount, itemBuilder: (context, index) { return ListTile( leading: Padding( padding: const EdgeInsets.all(4), child: CachedNetworkImage( imageUrl: "https://fakeimg.pl/300/?font_size=150&text=$index", placeholder: (context, url) => Image.asset("assets/thumbnail.png"), errorWidget: (context, url, error) => const Icon(Icons.error), ), ), title: Text("Item $index"), onTap: () {}, ); }, );
読み込み中に表示する仮のウィジェットとして、以下の画像を assets/thumbnail.png
に保存しておきます。
(pubspec.yaml
の assets
設定も必要です)
これらの設定を行うことで、以下のようにスムーズに表示することができるようになります!

2. キャッシュを適切に設定する
デフォルトの設定では、キャッシュは最大200枚、最終利用から30日間保持されるようになっています。
この設定をカスタマイズすることで、更に通信量を減らして高速化することが可能です。
試しに最大10,000枚・1ヶ月間保持するように変更してみます。
やり方としては、まずdartファイルを作成して以下の設定を記述します。
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; class CustomCacheManager extends CacheManager with ImageCacheManager { CustomCacheManager() : super( Config( 'customCacheKey', stalePeriod: const Duration(days: 30), // NOTE: キャッシュ期間 maxNrOfCacheObjects: 10000, // NOTE: キャッシュ上限枚数 ), ); }
あとは、この設定を反映したい CachedNetworkImage
ウィジェットの cacheManager
オプションで指定すればOKです。
CachedNetworkImage( imageUrl: "https://fakeimg.pl/300/?font_size=150", ...省略 cacheManager: CustomCacheManager(), ),
これだけで、キャッシュ領域を拡大することができます!
3. サーバ側で画像を軽量化する
元々表示に使用していた画像が、デバイスで表示するには十分に解像度が高く、1枚1枚の容量が大きいという問題がありました。
これに対応するため、さくらインターネット社の ImageFlux
を使用して軽量化したものを取得するように書き換えました。
上記サイトの例で検証してみます。
- 元画像
- URL:https://demo.imageflux.jp/bridge.jpg
- 解像度:5672 x 3781
- 容量:3.9 MB
- 幅を 700 px に指定(軽量化)
- URL:https://demo.imageflux.jp/w=700/bridge.jpg
- 解像度:700 x 467
- 容量:34 KB
かなり小さくなります!!
実際に表示に使用していた画像で試してみても、1枚あたり1/20以上の容量を削減できていました。
最後に
画像付きスクロールリストを高速化・通信量を減らす方法についてご紹介しましたが、いかがでしょうか。
Flutterを使うとシンプルなコードでとても簡単にリストを表示することができ、パッケージも豊富なためやりたいと思ったことが次々実現できるので、実装していて楽しいな〜と感じました😊
📝 2024/06/07 追記
5月21日に開催されたmobile勉強会に登壇し、この対応方法について発表しました!
その際に使用したスライドとサンプルリポジトリを載せておきますので、よろしければ是非参考にしてください。