☠️

【Flutter】X(旧Twitter)のようにローディング表示したい

に公開
ローディング中 描画後

要約

X(旧Twitter)のタイムラインでよく見る「グレーの骨格(スケルトン)+ シマー」なローディングを、「skeletonizer」を使って最小コードで実装します。既存レイアウトをそのまま「骨格化」できるので、別途ローディング用UIを作る手間が激減します。加えて、ダークテーマ、スイッチアニメーション、Sliver対応、パフォーマンスの注意点まで一気に解説します。

対象

  • X(旧Twitter)風のローディングを導入したいFlutterエンジニア
  • 初歩的な使い方を知りたい人(※Riverpod/BLoC等でも応用可)
  • シマーをウィジェットに乗せたい人

問題設定

  • APIレスポンス待ちの間、CircularProgressIndicatorだけだとUXが悪い
  • ローディング専用UIを別途作るのは手間
  • ダークモードやリスト/Sliverでも一貫した表現にしたい

これらの課題を、既存UIを自動変換して骨格化するskeletonizerで解決します。骨格化(skeletonize)は、既存レイアウトを「同じレイアウトのまま」灰色のボーン(骨)に置き換え、既定ではシマーを適用します。

解決アプローチ

骨格化

以下、FloatingActionButtonでListViewを表示したり、骨格化したものを表示したりする実装例です。
ポイントは3点!
Skeletonizer()を使うのと、enabledプロパティにbool値を渡すのと、Loading中でもダミーのリストデータを渡すこと。
最後が忘れがちなので要注意です。
また、skeletonizerはダミー表示用のUtilityクラスも用意されているのでそちらも要チェックです。

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Skeletonizer Demo')),
      floatingActionButton: Padding(
        padding: const EdgeInsets.only(bottom: 0, right: 4),
        child: Padding(
          padding: const EdgeInsets.only(bottom: 110),
          child: FloatingActionButton(
            child: Icon(
              _enabled
                  ? Icons.hourglass_bottom_rounded
                  : Icons.hourglass_disabled_outlined,
            ),
            onPressed: () {
              setState(() {
                _enabled = !_enabled;
              });
            },
          ),
        ),
      ),
      body: Skeletonizer(
        enabled: _enabled,
        enableSwitchAnimation: true,
        child: ListView.builder(
          itemCount: 6,
          padding: const EdgeInsets.all(16),
          itemBuilder: (context, index) {
            return Card(
              child: ListTile(
                title: Text('Item number $index as title'),
                subtitle: const Text('Subtitle here'),
                trailing: const Icon(Icons.ac_unit, size: 32),
              ),
            );
          },
        ),
      ),
    );
  }
}

ダークモードでの見た目

ライト・ダークモードでの見た目にも一貫性を持たせられるよう、デフォルトでこのパッケージが用意してくれてるSkeletonizerConfigData.dark()があります。
以下参考実装です。

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Skeletonizer Demo',
      theme: ThemeData(extensions: const [SkeletonizerConfigData()]),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        extensions: const [SkeletonizerConfigData.dark()],
      ),
      home: const SkeletonizerDemoPage(),
    );
  }
}

よくあるハマりどころ

空リストだと骨格が出ない

skeletonizerは「既存レイアウトを骨格化」する仕組み。つまり描画するレイアウトが必要です。ロード中は、ダミーデータで形だけ用意しておくと骨格化されます。

コンテナの扱いを調整したい

「コンテナは骨格化せず中身だけ骨格化したい」場合、ignoreContainersを使うと実現できます。

切替時にガクつく

enableSwitchAnimationにtrueを指定することである程度解消します。

まとめ

Skeletonizerで既存UIを自動骨格化し、より良いUXを目指していきましょう。空のレイアウトでは骨格が出ない部分は注意し、ロード中はダミーデータで形を作りましょう。これでは物足りない時はShimmerパッケージを検討するのも良いでしょう。以上Skeletonizerパッケージの紹介でした。

参考リンク

Discussion