👋

Flutter で Skelton ローディング ( Shimmer Effect ) を実装する

2023/11/26に公開

こんな UI があって、まだデータの取得ができていない時に単純なローディングでは物寂しいので、 Skeleton ローディングを実装したい時があります。

今回、実装してみたので紹介します。基本的には下記を導入するだけです。

https://pub.dev/packages/shimmer

他にも下記のようにいくつかあるようですが、Likeの数が他と桁違いに多いことから導入実績も多そうですし、自分の中ではこれが一番使いやすく調整しやすかったです。

インストール

pubspec.yml に下記を追加します。

dependencies:
  shimmer: ^3.0.0

実装方法

まずは元の構造通りに実装しつつ、テキストや画像などを color 属性をつけた Container に置き換えます。

先ほどのUIでこれを実装するとこのようになります。(少し長い)

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

  
  Widget build(BuildContext context) => Container(
        padding: const EdgeInsets.all(16),
        decoration: const BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.all(Radius.circular(16))),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                _icon(context),
                Container(width: 16),
                Expanded(
                    child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [_hostName(context), _dateTime(context)],
                ))
              ],
            ),
            _body(context),
            _content(context),
            Padding(
              padding: const EdgeInsets.only(top: 8),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Expanded(child: Container()),
                  ..._comments(context),
                  Container(width: 30),
                  ..._likes(context)
                ],
              ),
            )
          ],
        ),
      );

  Widget _icon(BuildContext context) => Container(
        width: 40,
        height: 40,
        decoration: const BoxDecoration(
          color: Colors.grey,
          shape: BoxShape.circle,
        ),
      );

  Widget _hostName(BuildContext context) =>
      Container(
        width: 100,
        height: 20,
        color: Colors.grey,
      );

  Widget _dateTime(BuildContext context) => Container(
        width: 50,
        height: 20,
        color: Colors.grey,
      );

  Widget _body(BuildContext context) => Padding(
        padding: const EdgeInsets.symmetric(vertical: 16),
        child: Container(
          width: double.infinity,
          height: 20,
          color: Colors.grey,
        ),
      );

  Widget _content(BuildContext context) => AspectRatio(
        aspectRatio: 16 / 9,
        child: Container(
          color: Colors.grey,
        ),
      );

  List<Widget> _comments(BuildContext context) => [
        SvgPicture.asset(
          "assets/ic_comment.svg",
          width: 24,
          height: 24,
          color: Theme.of(context).textTheme.bodyMedium?.color,
        ),
        Container(width: 8),
        Container(
          width: 24,
          height: 24,
          color: Colors.grey,
        )
      ];

  List<Widget> _likes(BuildContext context) => [
        SvgPicture.asset(
          "assets/ic_like.svg",
          width: 24,
          height: 24,
          color: Theme.of(context).textTheme.bodyMedium?.color,
        ),
        Container(width: 8),
        Container(
          width: 24,
          height: 24,
          color: Colors.grey,
        )
      ];
}

この状態ではこんな感じです。

ここまで来ればあとはもう一瞬で、あとは Shimmer.fromColors で包むだけ!

  @override
  Widget build(BuildContext context) => Container(
        padding: const EdgeInsets.all(16),
        decoration: const BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.all(Radius.circular(16))),
+        child: Shimmer.fromColors(
+          baseColor: Colors.grey.shade300,
+          highlightColor: Colors.grey.shade100,
+          enabled: true,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  _icon(context),
                  ...
            ),
+          ),
        ),
      );

( 他の部分もインデントが変わってるので、厳密な diff 出ないことにご注意を )

完成した UI がこちら!

以上、参考になれば幸いです。

Discussion