コンサルティング事業 2021.01.13

Flutter × UI #05 ~ agexコーポレートサイトをトレースしてみた (スライダー編 part4) ~

マウスホバーで画像がズームするWidgetを作成します。「#01 ヘッダー編」でも紹介した「MouseRegion」Widgetを利用して、マウスの動きと連動した画像のズームイン・ズームアウトアニメーションを実装していきます。

前回までのおはなし
Flutter × UI #01 ~ agexコーポレートサイトをトレースしてみた (ヘッダー編) ~
Flutter × UI #02 ~ agexコーポレートサイトをトレースしてみた (スライダー編 part1) ~
Flutter × UI #03 ~ agexコーポレートサイトをトレースしてみた (スライダー編 part2) ~
Flutter × UI #04 ~ agexコーポレートサイトをトレースしてみた (スライダー編 part3) ~

今回のおはなし
今回は、弊社HPの「スライダー」をFlutterでトレースした際のおはなし part4です。

引きづづき、実装を進めていきます。

[ここまでの成果]
アイテムの形は平行四辺形
アイテムは水平方向に、一定間隔で配列されている
アイテムは一定時間間隔で自動的にスライドされる
アイテムのスライドは左方向
アイテムが最左端へスライドする際、アイテムのサイズが大きくなる
最左端のアイテムのみ、最右端へスライドする
最右端へのスライド時、アイテムはフェードアウトアニメーションがついている
最右端へのスライド時、アイテムは他要素の後ろ側を通過する
・ マウスがアイテム内へ移動すると、アイテム内画像がズームされる
・ マウスがアイテム外へ移動すると、アイテム内画像は元サイズへズームアウトされる
・ スライダー下部には、最左端アイテムについてのアイテム説明が配置される
・ アイテム説明は、アイテムのスライドに合わせてフェードアニメーションによって切り替わる
・ スライダー下部のアイテム説明右側に、スライドボタンが配置される
・ マウスがスライドボタン内に移動すると、スライドボタンは各方向に少し移動する
・ マウスがスライドボタン外に移動すると、スライドボタンは元の位置に移動する

再現してみる
今回は、「画像のズームイン・ズームアウト」がテーマとなっています。
さっそく実装を進めていきます!

マウスがアイテム内へ移動すると、アイテム内画像がズームされる
マウスがアイテム外へ移動すると、アイテム内画像は元サイズへズームアウトされる
#01 ヘッダー編」 でも扱ったように、Widget内へのマウス移動によるイベント処理は、「MouseRegion」Widgetを利用することで実現することができます。
イベント処理は「onEnter」「onExit」に登録されるコールバック関数で扱うことができ、それぞれに

○ 「onEnter」:マウスがWidget上に移動
  → ズームイン
○ 「onExit」:マウスがWidget外に移動
  → ズームアウト

となるように、コールバック関数を登録する必要があります。
まずは、アイテムを「MouseRegion」のchildとするよう実装を変更し、各コールバック関数を定義します。

MouseRegion(
  onEnter: (details) => _onEnterHandler(details, context),  // ズームインを行うコールバック関数
  onExit: (details) => _onExitHandler(details, context),    // ズームアウトを行うコールバック関数
  child: AnimatedContainer(
    duration: Duration(seconds: 1),
    curve: Curves.easeOut,
    width: widget.width,
    height: widget.height,
    child: AnimatedBuilder(
      animation: clipAnimation,
      builder: (context, child) {
        return ClipPath(
          clipper: ParallelogramClipper(widget.offset * clipAnimation.value),
          child: FadeTransition(
            key: ValueKey(widget.itemIndex),
            opacity: fadeController,
            child: Container(
              decoration: BoxDecoration(
                image: DecorationImage(
                  fit: BoxFit.none,
                  image: AssetImage(/* 画像のパス */),
                ),
              ),
            ),
          ),
        );
      }
    ),
  ),
);

  ・
  ・
  ・

void _onEnterHandler(PointerEvent details, BuildContext context) {
  // ズームイン
}

void _onExitHandler(PointerEvent details, BuildContext context) {
  // ズームアウト
}

アイテムの画像のズーム処理は、「DecorationImage」の「scale」プロパティを利用します。

scaleプロパティに登録するスケールを、

onEnter:ズームイン時の倍率
onExit:ズームアウト時の倍率

となるようにアニメーションをセットすることで、アイテム内画像のズームイン・ズームアウトを実装することができます。

#04 スライダー編 part3」 で作成した「SliderItem」Widgetを下記のように変更します。

class SliderItem extends StatefulWidget {

  ・
  ・
  ・

  // アイテム内画像のズームアニメーションを取り扱う
  AnimationController zoomController;
  Animation zoomAnimation;
  Animation zoomCurvedAnimation;

  @override
  void initState() {
    super.initState();

  ・
  ・
  ・

    zoomController = AnimationController(
      duration: const Duration(milliseconds: 200),
      vsync: this,
    );

    zoomController.addListener(() {
      setState(() {});
    });

    zoomCurvedAnimation = CurvedAnimation(
      parent: zoomController,
      curve: Curves.linear,
    );

    zoomAnimation = Tween(
      begin: 0.8,  // ズームイン時のスケール
      end: 0.7,    // ズームアウト時のスケール
    ).animate(zoomCurvedAnimation);
  }

  @override
  void dispose() {
    fadeController.dispose();
    clipController.dispose();
    zoomController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {

  ・
  ・
  ・

  return MouseRegion(
    onEnter: (details) => _onEnterHandler(details, context),  // ズームインを行うコールバック関数
    onExit: (details) => _onExitHandler(details, context),    // ズームアウトを行うコールバック関数
      child: AnimatedContainer(
        duration: Duration(seconds: 1),
        curve: Curves.easeOut,
        width: widget.width,
        height: widget.height,
        child: AnimatedBuilder(
          animation: clipAnimation,
          builder: (context, child) {
            return ClipPath(
              clipper: ParallelogramClipper(widget.offset * clipAnimation.value),
              child: FadeTransition(
                key: ValueKey(widget.itemIndex),
                opacity: fadeController,
                child: Container(
                  decoration: BoxDecoration(
                    image: DecorationImage(
                      fit: BoxFit.none,
                      scale: zoomAnimation.value, // ズーム値
                      image: AssetImage(/* 画像のパス */),
                    ),
                  ),
                ),
              ),
            );
          }
        ),
      ),
    );
  }

  void _onEnterHandler(PointerEvent details, BuildContext context) {
    zoomController.stop();
    zoomController.forward();
  }

  void _onExitHandler(PointerEvent details, BuildContext context) {
    zoomController.stop();
    zoomController.reverse();
  }
}

これにより、マウスのアイテム内・外への移動に応じて、画像のズームイン・ズームアウトアニメーションを
実装することができました。
(ズーム値の最適化は時間切れでまだ実装できていません。またの機会に。。)

(gifだと、ズームアニメーションが伝わりにくい、、)

[ここまでの成果]
アイテムの形は平行四辺形
アイテムは水平方向に、一定間隔で配列されている
アイテムは一定時間間隔で自動的にスライドされる
アイテムのスライドは左方向
アイテムが最左端へスライドする際、アイテムのサイズが大きくなる
最左端のアイテムのみ、最右端へスライドする
最右端へのスライド時、アイテムはフェードアウトアニメーションがついている
最右端へのスライド時、アイテムは他要素の後ろ側を通過する
マウスがアイテム内へ移動すると、アイテム内画像がズームされる
マウスがアイテム外へ移動すると、アイテム内画像は元サイズへズームアウトされる
・ スライダー下部には、最左端アイテムについてのアイテム説明が配置される
・ アイテム説明は、アイテムのスライドに合わせてフェードアニメーションによって切り替わる
・ スライダー下部のアイテム説明右側に、スライドボタンが配置される
・ マウスがスライドボタン内に移動すると、スライドボタンは各方向に少し移動する
・ マウスがスライドボタン外に移動すると、スライドボタンは元の位置に移動する

今回は切りが良いのでここまでとします。
MouseRegionを使った、マウスホバーイベントをトリガにしたアニメーションは今までの記事でも紹介していたので、スムーズに実装を進めることができました。

次回はとうとうスライダー編最終回(の予定)です!
長かったスライダー編、果たして無事終えることが出来るのでしょうか。。

今日の一言
「MouseRegion」Widget便利すぎる

この記事をシェアする
事業について詳しく知る

コンサルティング事業について詳しく知りたい方はこちら

RECOMMEND

おすすめ記事