サンタさんのパラパラ漫画を実装した話

# Flutter女子、共同開発する
　我らが[Flutter大学](https://flutteruniv.com) にはFlutter女子部という秘密組織（ウソ）がある。エンジニアを目指す主婦から、子育てと両立するバリバリ現役エンジニア、そして隠居初心者の私まで、実に多様なメンバーが揃っている。closeのSlackやギャザーで女子トークに花を咲かせる一方、openにいろんな企画も考案中だ。その第一弾として共同開発しようということになった。本記事は、そこで私が担当した部分の話になる。


## アプリとパラパラ漫画概要
　アプリは季節柄、クリスマス向けということになった。そこにローディングマーク代わりにサンタさんのanimationを入れたい。すると瞬く間に動画をつくる人が現れるから、素晴らしい。これを埋め込んで終わり、だったらこの記事は生まれなかったのだが、せっかくだからコードを書くことでサンタさんを動かそう、という話になった。ならパラパラ漫画はどうだろう。というのが、私の担当箇所のスタートである。
　記事がやたら長くなりそうなので、codeの掲載は少しにしておく。最後にGitHub上のSampleCodeを付けるので、詳細はそちらをどうぞ。　

## PHASE１、ほんとにpageをパラパラする。　
　パラパラ漫画といえば、授業中の退屈しのぎに、教科書の隅に落書きするというのが、遊びの乏しい時代を生きた私の常識だ。だからもちろん「ページ遷移」からのスタート。

### fit: BoxFit.cover
　まずは最初に作られた動画から、10カットをキャプチャーして、assets/imagesに取り込む。その画像を背景設定したページを作る。こんな感じ。　
```dart
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: const BoxDecoration(
          image: DecorationImage(
            image: AssetImage('assets/images/1.png'),
            fit: BoxFit.cover,
          )),
      ));
  }
```
### Timerによる自動遷移　
　パラパラ漫画を進めるのに一々ボタン押したのではお話にならない。自動で進んでほしい。そうか、Timerなんていう便利なものがあるのね。これでなにもしなくても1秒たったら次のページに移る。もちろんスピードは調整できる。
```dart
    timer = Timer(
      const Duration(milliseconds: 1000),
          () {
        Navigator.push(
          context,
          CustomPageRoute(
            const SecondScreen(),
          ),
        );
      },
    );
```
### CustomPageRoute
　defaultだと、画面は横に遷移する。これがキモチワルイ。durarionを短くすることでキモチワルサは軽減できるが、今回はCustomPageRouteでフェードをかけてみることにした。
```dart
    return FadeTransition(
      opacity: animation,
      child: child,
    );
```
　実にうれしいことに、たった１０コマのパラパラ漫画が、ほわん、ほわんと白いフェードの雲を挟みながら繋がっている。それなりにかわいい。見た目はかわいいんだが、codeが美しくない。いや、各ペーシのcodeはたいして問題ないが、それが10 Pageあるというのが問題だ。たかだか「ローディング待ちグルグルの代わり」なのだ。ふつう１行で済むところじゃないか。　
　
## 大学Slackの真ん中で「HELP!」を叫ぶ
　ページ遷移はだいぶ身についたけれど、そのほかの方法でパラパラ漫画を実現するには力不足な私は、大学のSlack、自分のtimesでHELP!と叫ぶ。と、ちゃんと助けが来るんですねえ。実にありがたいことです。以下はその助言を得ながらの進展となる。

## PHASE2、重ねた画像を除いていく。
　ともかくリアルのパラパラ漫画に引っぱられたページ遷移という根本を再考する。つまりフェーズ１は「捨てられた過程」なので、ここには書かなくてもよいようなものだが、そこで学んだ技術は、実は今後も使う。これが学習としての共同開発のいいところだ。

### StackとVisibility
　まずは10枚の画像を１Pageに積み重ねる。一番最後のカットをcodeのトップに置き、画像を逆順で積んでいく。最後に最初のカットを置く。codeとしては一番上、積み重ねとしては一番下にあるカットは5000ミリ秒、そこにあり続ける。でも、codeの最後、カードの山のてっぺんにあるカットは1000ミリ秒で消える。次のカードは1500ミリ秒、という感じで順に消えていく。　
```dart
  @override
  Widget build(BuildContext context) {
    return Visibility(
      visible: _visible,
      child: Container(
        decoration: BoxDecoration(
            image: DecorationImage(
              image: AssetImage(widget.imagePath),
              fit: BoxFit.cover,
            )),
      ),
    );
  }
```
　各画像のVisibilityについて画像名と表示時間を引数に設定したfileと、実際に画像を積み上げたfileの計２枚で成立した。すてき、すてき。ただ遷移がすごくシャープになった。たった１０コマのパラパラ漫画でこのシャープさは、カクカクして粗さが目立つし、サンタさんのファンタジー感がない。

### AnimatedOpacity
 ここで、最初CustomPageRouteで使ったOpacityという技術が復活する。Visibilityだと、画像はパッと消滅する。Opacityだと、じわっと透明化する。なので、前の画像の残像から、次の画像が透けて見える。すてき、すてき。
```dart
  Widget build(BuildContext context) {
    return AnimatedOpacity(
      duration: const Duration(milliseconds: 1000), // 不透明 -> 透明にどれくらいの時間をかけるか
      opacity: _opacity,
      child: Container(
        decoration: BoxDecoration(
            image: DecorationImage(
              image: AssetImage(widget.imagePath),
              fit: BoxFit.cover,
            )),
      ),
    );
  }
```
## PHASE３、前後の接続。
　パラパラ漫画単独なら、これで終了。runしたら勝手に始まって、勝手に終わる。だけれども、アプリの中で使うのだから、それでは足らない。
　
### 画像を積み重ねたfileに遷移すれば自動でスタート。　
　これは簡単。パラパラ漫画をスタートさせたいfileから遷移すれば動く。　

### 次のPageに遷移。　
　パラパラ漫画は、画像がなくなったら自動停止する。これでは次に繋がらない。パラパラ漫画のclassはStatelessWidgetだが、これを一番最初につくった、Pageが自動遷移するTimer付きStatefulWidgetに埋め込んだ。そうすることで、自動で次のPageに遷移できる。ここからは別のメンバーの分担だ。最後にページ遷移が復活して、大満足。　

## codeはこちら
https://github.com/dongri727/flip-book-sample


　Flutter大学女子部のアプリイメージを侵害しないように、別の画像14枚を用いています。
　Phase1,2,3それぞれのバージョンが含まれています。mainからどのPhaseのトップに遷移するかで分岐します。どのPhaseを選んだかによって、unused importが発生します。　
　MIT Licenseをつけていますので、例えばお絵かき機能と合わせてパラパラ漫画メーカーなどを自由に作ることができます。
 
https://youtube.com/shorts/DuNl6a5Cn08?feature=share

## じっさいのアプリはこちら
　~~PCから見るとまだちょっとバグがありますが~~ スマホは大丈夫。PCも大丈夫。

https://recommender-11c56.web.app/#/


# メリークリスマス。