【Flutter】PageViewでスライド時にアニメーションさせる実装

もくじ

こんにちは。

スマホアプリをメインに開発しているロッキーカナイです。

前回、Flutterでカードめくりアニメーションの紹介をしましたが、続編ということで、今回はPageViewでスライド時に、いい感じにアニメーションさせる実装を紹介したいと思います。

やること

  1. PageViewでトランプ画像をスライドさせる時に拡大アニメーションをさせる。
  2. メインのトランプ以外は元の大きさに戻す
  3. ページインジケータを実装する。
  4. 移動時に該当トランプの詳細を出す。

今回はこれらを行なってみたいと思います。

コード

まず、1と2の実装を紹介します。

import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SlidePage(),
      ),
    );
  }
}



class SlidePage extends StatefulWidget {
  @override
  _SlideshowState createState() => _SlideshowState();
}


class _SlideshowState extends State<SlidePage> {

  // ページコントローラ
  final PageController controller = PageController(viewportFraction: 0.8);

  // ページインデックス
  int currentPage = 0;

  // データ
  List<String> _imageList = [
    "images/card_back.png",
    "images/card_j.png",
    "images/card_q.png",
    "images/card_k.png",
  ];


  @override
  void initState() {

    super.initState();

    // ページコントローラのページ遷移を監視しページ数を丸める
    controller.addListener(() {
      int next = controller.page.round();
      if (currentPage != next) {
        setState(() {
          currentPage = next;
        });
      }
    });
  }


  /*
   * アニメーションカード生成
   */
  AnimatedContainer _createCardAnimate(String imagePath, bool active) {

    // アクティブと非アクティブのアニメーション設定値
    final double top = active ? 100 : 200;
    final double side = active ? 0 : 40;

    return AnimatedContainer(
      duration: Duration(milliseconds: 500),
      curve: Curves.easeOutQuint,
      margin: EdgeInsets.only(top: top, bottom: 50.0, right: side, left: side),
      decoration: BoxDecoration(
        image: DecorationImage(
          fit: BoxFit.fitWidth,
          image: Image.asset(imagePath).image,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {

    return PageView.builder(
      controller: controller,
      itemCount: _imageList.length,
      itemBuilder: (context, int currentIndex) {

        // アクティブ値
        bool active = currentIndex == currentPage;

        // カードの生成して返す
        return _createCardAnimate(
          _imageList[currentIndex],
          active,
        );
      },
    );
  }
}

素材はこちらから拝借致しました。

いい感じにできました。

PageViewの個々のViewを生成時にmarginを変更しアニメーションをさせてます。

return AnimatedContainer(
      duration: Duration(milliseconds: 500),
      curve: Curves.easeOutQuint,
      margin: EdgeInsets.only(top: top, bottom: 50.0, right: side, left: side),
      decoration: BoxDecoration(
        image: DecorationImage(
          fit: BoxFit.fitWidth,
          image: Image.asset(imagePath).image,
        ),
      ),
    );

上記が該当コードになります。

次に3と4のコードを紹介します。

import 'package:flutter/material.dart';
import 'package:page_view_indicators/circle_page_indicator.dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Introduction(),
      ),
    );
  }
}



class Introduction extends StatefulWidget {
  @override
  _IntroductionState createState() => _IntroductionState();
}


class _IntroductionState extends State<Introduction> {

  // ページコントローラ
  final PageController controller = PageController(viewportFraction: 0.8);

  // ページインデックス
  final _currentPageNotifier = ValueNotifier<int>(0);

  // データ
  List<String> _imageList = [
    "images/card_back.png",
    "images/card_j.png",
    "images/card_q.png",
    "images/card_k.png",
  ];

  List<String> _textList = [
    "トランプ背面",
    "ジョーカー",
    "クイーン",
    "キング",
  ];

  @override
  void initState() {

    super.initState();

    // ページコントローラのページ遷移を監視しページ数を丸める
    controller.addListener(() {
      int next = controller.page.round();
      if (_currentPageNotifier.value != next) {

        setState(() {
          _currentPageNotifier.value = next;
        });
      }
    });
  }

  /*
   * アニメーションカード生成
   */
  AnimatedContainer _createCardAnimate(String imagePath, bool active) {

    // アクティブと非アクティブのアニメーション設定値
    final double top = active ? 100 : 200;
    final double side = active ? 0 : 40;

    return AnimatedContainer(
      duration: Duration(milliseconds: 500),
      curve: Curves.easeOutQuint,
      margin: EdgeInsets.only(top: top, bottom: 50.0, right: side, left: side),
      decoration: BoxDecoration(
        image: DecorationImage(
          fit: BoxFit.fitWidth,
          image: Image.asset(imagePath).image,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {

    return SafeArea(
      child: Column(
        children: <Widget>[

          /*
           * ページ
           */
          Expanded(
            child: PageView.builder(
              controller: controller,
              itemCount: _imageList.length,
              itemBuilder: (context, int currentIndex) {

                // アクティブ値
                bool active = currentIndex == _currentPageNotifier.value;

                // カードの生成して返す
                return _createCardAnimate(
                  _imageList[currentIndex],
                  active,
                );
              },
            ),
          ),

          /*
           * ページインジケータ
           */
          Container(
            height: 30.0,
            child: CirclePageIndicator(
              itemCount: _imageList.length,
              currentPageNotifier: _currentPageNotifier,
            ),
          ),

          /*
           * 説明エリア
           */
          Container(
            height: 80.0,
            padding: EdgeInsets.all(10.0),
            child: Container(
              width: double.infinity,
              padding: EdgeInsets.all(10.0),
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.blue,
                  width: 4.0,
                ),
                borderRadius: BorderRadius.circular(10),
              ),
              child: Center(
                child: Text(
                  _textList[_currentPageNotifier.value],
                  style: TextStyle(
                    fontSize: 20.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

インジケータはcircle_page_indicatorのライブラリを使用してます。

特に特殊なことはしておらず、ページ変更がありsetStateされると該当のアイテムリストを参照しテキストを表示させている流れです。

さいごに

flutterでアニメーションは結構簡単に実装できます。

今回紹介したものか簡易的なものですが、より凝ったものも実装可能です。

なによりもアニメーションがあるUIだと、ちょっとかっこよくなりますよね。今後そういうものも記事にできたらと思います。

関連する記事

もっと見る