【Flutter】2つの画像を交互にクロスフェードでループ表示させる方法
スポンサーリンク

背景画像をフェードイン/フェードアウトで繰り返し自動的に切り替える方法

2つの画像をアニメーション付きでリッチに切り替えられるようにする方法

ヒーラー
Flutterを使ってiOS/Android共通的にエフェクトをかけて画像表示を切り替えるリッチな作りにしたい

はじめに

やりたいこと

FlutterのリッチなUIパーツとして、2つの画像を自動的に交互に表示し続けたい場合があります。
それもできれば、フェードイン・フェードアウトのように切替えをリッチにするためにエフェクトを加えたいものです。

最近のアプリでは、初回のアプリ起動時からチュートリアルにかけてアニメーションがかかっているものも見かけます。

また、わざわざバナーをスライドしてもらうようなユーザーの操作を阻害させることなく、自然と背景画像でアピールしたいものを訴求したり印象付けることができます。

そしてなによりアプリとしての質を1ランク向上させてくれます。


対処方法

「AnimatedCrossFade」を使って対応することができます。

特別なパッケージは不要で、標準のウィジェットとして使うことができます。

https://api.flutter.dev/flutter/widgets/AnimatedCrossFade-class.html

以下のサンプルコードのとおり、AnimatedCrossFadeを使用して、2つのウィジェット(今回は画像)を交互に表示させることができます。

firstChild、secondChildにそれぞれ表示したいウィジェットを設定するだけで、クロスフェードによるリッチな切り替わり表示が可能です。

クロスフェードにかかるアニメーション時間はdurationで設定できます。
crossFadeStateの設定が切り替えるスイッチになっています。

AnimatedCrossFade(
  duration: const Duration(seconds: 3),
  firstChild: const FlutterLogo(style: FlutterLogoStyle.horizontal, size: 100.0),
  secondChild: const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0),
  crossFadeState: _first ? CrossFadeState.showFirst : CrossFadeState.showSecond,
)

crossFadeStateに設定した変数(ここでは_first)がtrue/false変わるたびに画像が切り替わります。

注意したいのが、このAnimatedCrossFadeを設定しただけでは、まだ自動的に画像を継続的に切り替わることにはなってないということです。

上記のコードでは、_firstのBool値が変わるまで画像は切り替わりません。
例えば、ユーザーがボタンか何かを押したときに_firstの値が変わるとかであれば、そのまま画像を切替えることができますが、自動ループさせるためにはもうひと工夫必要です。

そこで、非同期で一定間隔で_firstの値を変え続けるようなコードを書く必要があります。

実際のコードは以下のとおりです。

//top_page.dart

@override
Widget build(BuildContext context, WidgetRef ref) {
  return Scaffold(
    body: Stack(
      children: [
        //AnimatedCrossFadeを使用して2つの画像が1.5秒で自動的に切り替わる
        SizedBox(
          height: double.infinity,
          child: AnimatedCrossFade(
            duration: const Duration(milliseconds: 1500),
            firstChild: Container(
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: AssetImage('images/top_background01.jpg'),
                  fit: BoxFit.cover,
                ),
              ),
            ),
            secondChild: Container(
              decoration: BoxDecoration(
                image: DecorationImage(
                  image: AssetImage('images/top_background02.jpg'),
                  fit: BoxFit.cover,
                ),
              ),
            ),
            crossFadeState: ref.watch(topPageProvider).isFirstImage
              ? CrossFadeState.showFirst
              : CrossFadeState.showSecond,
            ),
          ),
          ...
        ],
      ),
    );
  }
//top_page_state_notifier.dart

class TopPageStateNotifier extends StateNotifier<TopPageState> {
  TopPageStateNotifier({required TopPageState state}) : super(state) {
    initialize();
  }

  Future<void> initialize() async {
    // 非同期で5秒おきにisFirstImageの値がtrueとfalseが切り替わる
    // TopPageが表示されている間は継続
    // TopPageが非表示になると停止
    Timer.periodic(const Duration(seconds: 5), (timer) {
      if (mounted) {
        state = state.copyWith(
          isFirstImage: !state.isFirstImage,
        );
      } else {
        timer.cancel();
      }
    });
  }
  ...
}

切替えのスイッチとなる変数は、ここでは_firstではなく、isFirstImageとしています。

このisFirstImageの値を5秒おきに変えるように、トップ画面のStateNotifierの初期化時に非同期で設定します。

mount状態を見て、toppageの画面が表示中かを確認し、表示状態でなければタイマーを切ります。
画面が表示中である限り、5秒おきにisFirstImageのステートをtrue/false切り替えるようになっています。

これで以下のようにFlutterでクロスフェードで2つの画像を自動ループで切り替えるリッチなUIができました。
もうひと超えできるとよいのが、クロスフェードではなくクロスディゾルブでできるとよいなというところです。

詳しい説明は以下の公式動画にもあります。

なお、この後に起きた問題と解決方法については以下の記事で詳しく載せています。


スポンサーリンク

Twitterでフォローしよう

おすすめの記事