リバース・エンジニアリング

Flultterとテックブログと時々iOS

今更ながら2020年前半を振り返る

【目次】

振り返り

こんばんは、Tamappeです。
もう7月も中盤に差し掛かっていますが、まだ2020年の前半を振り返っていませんでしたので今日は2020年前半の振り返りを行います。

1月に行ったこと

ふと、何を思ったのかいきなりFlutterを触ってみたくなった。
僕の生活スタイルは1年間で1つのテーマを学習するスタイルです。その1つのテーマをFlutterに決めたのが1月でした。
ただFlutterを学習するだけなのはもったいないのでなんかテーマを決めたのが

iOSエンジニア視点でFlutterでアプリ開発を学習してみたら?」

ということで割と面倒くさいFlutterでiOSアプリを開発する学習記録を残していくことにした。
これなら近い将来FlutterをするiOSアプリエンジニアが増えてきたら僕のブログが読まれるじゃん!と勝手に思ってた。

ちなみにブログを続ける自信がなかったので1ヶ月ぐらい隠れて記事を投稿してた。

tamappe.hatenadiary.com

それからブログを投稿する習慣を付けたかったのでとりあえず2日に1回更新するように心掛けていた。

このときに参考にしてた本はこちらの本です。

Android/iOSクロス開発フレームワーク Flutter入門

Android/iOSクロス開発フレームワーク Flutter入門

この本は昨年、会社の福利厚生で購入した本だった。
去年(2019年)はそろそろFlutterがiOSアプリエンジニアでも流行り出すでしょということで予め購入していました。
その後積ん読になった・・・。

この本で学習した知識を通してさらにUIKitだったらこう作るよね?
という視点で記事を書いていた。

1月は7記事ぐらい投稿していた記憶があります。

2月に行ったこと

2月はまだコロナウイルスが騒がれていなかったような記憶がある。
ダイヤモンド・プリンセス号でコロナウイルスの集団培養が起きてコロナウイルスの感染力を目の当たりにしたような印象だった。

まだまだマスクなしで外出していた時期だった。
2月のブログの更新は2日に1回ペースでこっそり続けていた。

中旬ぐらいにコンパスイベントpotatotipsのイベントがあったのでそれに参加したのを覚えている。

potatotips #68

potatotips.connpass.com

初のpotatotipsでの登壇でした。
iOS Tips発表枠で参加しました。確かUIAlertControllerの制御をテーマにして登壇しました。
Androidの登壇者はFlutterネタが多かったです。

あまりに多かったので、このタイミングでノリでツイッターで実はFlutterを学習していることをツイートしてしまいました。
これで逃げられなくなったのを覚えている。
2月の終わり頃にはFlutterの本が終盤でRSSアプリを作ってた。

tamappe.hatenadiary.com

3月に行ったこと

この頃になると日本でもコロナウイルスの流行が見逃せなくなったからか世間が騒がしくなってましたね。
どっちかというとパニックになっていた印象があります。
マスクは2月上旬で既に売り切れで仕入れることができなくなってたので引き続きコンビニや薬局で品切れになってました。

アプリ開発はFlutterでの操作に慣れてきた頃でSketchで4択クイズアプリを作る企画をしていた。

Flutter本が完了してUdemyで手頃な動画を探してその動画に沿ってコードを写経していた。

参考にしていた動画はこちらです。

www.udemy.com

動画を見ながらデザインをアレンジして4択クイズアプリを開発しながら記事を作っていた。
書き慣れてきたので土日に記事を作り溜めして2日に1回ペースで記事を投稿するようになった。

tamappe.hatenadiary.com

そういえば、2月まで1日平均3時間以上Switchのスプラトゥーン2をプレイしていたゲーム中毒問題は 会社の人にSwitch本体ごと譲渡したことで解消されたな。

プレイ時間はたしか3年間でトータル2000時間には達すると思うので、 この時間を別のことに集中してたらまた違った人生を歩んでいたに違いないと思っている。
今振り返ればブログに投資していたら何記事出来たことやらと思っている。

4月に行ったこと

4月に東京都で緊急事態宣言が発令されたので会社も突如リモートワークが導入されて在宅ワークになった。
世間的にはリモートワークとテレワークという言葉が流行りだした時期です。
気分の切り替えとして朝イチにレッドブルを飲む習慣を付けた。

この時は会社も世間も状況の入れ替わりが激しなったので業務後にしていた事を覚えていなかった。
人生でそうそうない在宅ワークが始まったのでYouTubeで動画を見るようになった。(これが一番今でも影響を受けている)

印象に残っているのはYouTubeにFlutterのライブコーディングの動画が沢山あったので、 これからFlutterを触る方はYouTubeの動画だけで十分な気がしていた。

Flutterでのアプリ開発はネタ切れ感があったので上司に相談したところ、 AppleMusicのクローンアプリを作ってみればということでSketchでデザインを作ってクローンアプリ開発を始めました。

tamappe.hatenadiary.com

5月に行ったこと

5月のゴールデンウィークまではひたすら色々なブログを書きまくっていた。
スプラトゥーン2のプレイ時間をブログ記事執筆に当てていれば・・・

とこのときほど後悔したことはなかった。。。

5月中旬はiOSアプリエンジニアの堤さんがキャンプファイヤーでなんかするということだったので、 日頃お世話になったような気持ちになったので寄付しました。
寄付してからオンラインサロンだったことに気づいてとりあえず登録作業だけ済まして放置してた。

Flutterでのアプリ開発はAppleMusicクローンアプリ開発が飽きてきた頃で オリジナルアプリとしてナンバーズアプリを作ることに決めた。

今振り返れば、AppleMusicアプリの延長でYouTubeAPIを使って動画のまとめアプリに改変すればよかったと反省している。

tamappe.hatenadiary.com

この時が一番Flutterでのアプリ開発で楽しかった時期だったかもしれない・・・。

6月に行ったこと

(編集中)

全体の振り返り

思い返せば、2020年の前半は

  • Flutterでのアプリ開発のスタート
  • ブログ活動の本格開始
  • オンラインサロンでの活動
  • ゲーム中毒の解消

となかなか人生の分岐点になりそうなイベントばかりが起こっている。
半年前はまだスプラトゥーン2でゲーム中毒になっていたんだな。。。
あのゲームは極めれば極めるほどストレスが溜まるというなかなかの鬼畜ゲームだったのでそれを辞められたのは凄い大きい。

ブログ活動にFlutterなんで2020年は生活習慣がガラッと変わった年になったと思う。
ということで締まりが悪いですが色々振り返れたんでこういうのも悪くないと思いました。

それでは、バイバイ!

65. FlutterでListの要素をランダムにシャッフルするには

開発しているアプリでListの要素をランダムシャッフルして格納し直す必要が出てきたので ランダムにシャッフル出来ないのかを調べてみました。

List _shuffle(List items) {
    var random = new Random();
    for (var i = items.length - 1; i > 0; i--) {
      var n = random.nextInt(i + 1);
      var temp = items[i];
      items[i] = items[n];
      items[n] = temp;
    }
    return items;
  }

これでListに入っている要素をランダムに並び替えることができる。

final list = List<int>.generate(25, (i) => i + 1);
print("list: $list" );
final randomList = _shuffle(list);
print("randomList: $randomList" );

# 結果
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
# [12, 3, 18, 8, 23, 20, 7, 10, 9, 1, 19, 5, 6, 4, 17, 24, 21, 11, 16, 13, 22, 15, 25, 14, 2]

参考にしたページを貼ります。

stackoverflow.com

Flutter for Web でWebプロジェクトを新規作成する

前提

Flutter コマンドを叩ける状態にしておく

コマンド

Flutterパッケージのあるディレクトリで次のコマンドを叩く

$ flutter channel master

flutter をアップグレードします。

$ flutter upgrade

Web サポートを有効にします。

$ flutter config --enable-web

chrome などのブラウザが使えるかを念の為確認します。

$ flutter devices

モバイルプロジェクトと同様にプロジェクトファイルを作成します。

$ flutter create the_basics

Chrome で開く場合

Chrome でローカルで開発する場合に使う感じです。

$ flutter run -d chrome

## 成功時

Downloading Web SDK...                                              8.9s
Launching lib/main.dart on Chrome in debug mode...
Syncing files to device Chrome...                                       
17,359ms (!)                                       
Debug service listening on ws://127.0.0.1:62972/QwJpY-485sk=

Warning: Flutter's support for web development is not stable yet and hasn't
been thoroughly tested in production environments.
For more information see https://flutter.dev/web

🔥  To hot restart changes while running, press "r" or "R".
For a more detailed help message, press "h". To quit, press "q".

Chrome が開いて開発できます。

任意のサーバーで立ち上げる場合

laravel artisan serve みたいな感じ localhost で立ち上げたい場合に使う。

$ flutter run -d web-server

## 成功時
Launching lib/main.dart on Web Server in debug mode...
Syncing files to device Web Server...                                   
11,031ms (!)                                       
lib/main.dart is being served at http://localhost:63225

最後に書いている http://localhost:63225 を開くと表示されます。

Flutter コマンドを叩けるようにする

Flutter パッケージのダウンロードページ

Flutter パッケージのダウンロードページ

flutter.dev

FlutterパッケージをDLしてパスを通す。

Flutter パッケージのあるディレクトリーで次のコマンドを叩く

export PATH="$PATH:`pwd`/flutter/bin"

これで flutter のパスを叩けるようになる。

64. FlutterのProviderパターンを3分で理解する

FlutterのProviderパターンを3分で理解する

Flutter 初心者にとって Provider の扱いはとても難しく思うはず。
僕も例外なく Flutter 学習初めの頃は Provider を見てもあまり魅力を感じなかった。

今回はその Provider について理解できるようにすることが主目的である。

【目次】

事前知識

Provider の書き方を理解する上で、前提知識といいますか経験値が必要な気がします。

  • Container, Column, Row, ListView を使ってレイアウトを組み立てられる
  • StatefulWidget の setState の使い方を理解している
  • Widget の分割のメリットを理解している

多分、これらまで理解していたら Provider で今よりもいい感じの設計ができる(はず)。

Providerとは

Provider の概念・理念の理解は itome さんのブログが一番分かりやすいと思う。

itome.team

この記事を読めば Provider については理解できるはずだった。はずだったというのは僕が例外だったから。
多分、本当に初心者の方にとっては Provider の理解は難しいと思う。

Provider の特徴

完全に Providerについて理解していないので、勿論間違った解釈をしているかもしれない。
そのレベルですが、 Provider を使うことで得られるメリットは

  • StatefulWidget を使う必要がなくなる ( setState の更新は不要)
  • iOS でいうところの RxSwift の使い方に似ている
  • 末端の widget まで値を送る必要がなくなる

これらだと思っている。
それではこれを前提に Provider のついて学習していきたい。

サンプルコード

Provider のサンプルコードについては上記のページと同じコードを載せることになります。
最初は Flutter の初期コードをそのままカスタマイズするためです。

それでは Flutter の初期コードについてコメント文を削除した状態を載せます。

main.dart

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

こちらが初期コードになります。

特徴は

  • setState で値を更新している (StatefulWidget のため)
  • Widget の生成 と Model が同じクラス内で行われている

です。

これをProviderパターンに書き換えます。

Provider の手順について

それでは Provider を使う手順について紹介します。

  1. Provider をインストールする
  2. ChangeNotifier を継承した Model クラスを定義する
  3. 発火したいイベントを持っている widget にConsumer で包む
  4. Provider で値を更新させたい widget に ChangeNotifierProvider で包む
  5. 値を更新したい箇所 に Provider で包む

1. Provider をインストールする

Provider パッケージのページはこちらになります。

pub.dev

yaml ファイルに宣言してインストールします。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  provider: ^4.1.2

使うファイルで import します。

main.dart

import 'package:provider/provider.dart';

これで準備が整いました。

2. ChangeNotifier を継承した Model クラスを定義する

次に ChangeNotifier を継承した Model クラスを定義します。

main.dart

import 'package:provider/provider.dart';

class CountModel extends ChangeNotifier {
  /// 初期値
  int count = 0;
  
  /// count の更新メソッド
  void increment() {
    count ++;
    notifyListeners();
  }
}

こんな感じでOKです。

3. 発火したいイベントを持っている widget に Consumer で包む

次に値を更新するイベントを持っているwidget を ChangeNotifierProvider で包んであげます。
ですが、最初のコードは StatefulWidget で書かれているので StatelessWidget に書き直して Widget build(BuildContext context) の中身を移動させます。

main.dart

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

この時点では counter や incrementCounter が存在しないのでエラーになります。
まずは Scaffold の部分を次のように変更します。

      child: Consumer<CountModel>(
          builder: (context, model, child) => Scaffold(
              appBar: AppBar(
                title: Text('Flutter Demo Home Page'),
              ),
             /*
             * 以下省略
              */
          )
      )

Consumer() で Scaffold を包みました。

4. Provider で値を通知したい widget に ChangeNotifierProvider で包む

次に値を更新させたい widget を ChangeNotifierProvider で包みます。
今回は MyHomePage クラスの Scaffold に対して値を通知したいです。

main.dart

    return ChangeNotifierProvider<CountModel>(
      create: (context) => CountModel(),
      child: Consumer<CountModel>(
          builder: (context, model, child) => Scaffold(
              appBar: AppBar(
                title: Text('Flutter Demo Home Page'),
              ),
             /*
             * 以下省略
              */
          )
      ),
    );

5. 値を更新したい箇所 に Provider で包む

最後に更新された値を受け取ってそれを widget に反映させます。
値を提供するということで Provider と名付けられているかもしれません。

main.dart

Text(
    /// Provider を使う
    '${Provider.of<CountModel>(context).count}',
    style: Theme.of(context).textTheme.headline4,
    )

これで Consumer で包んだ FloatingActionButton をタップした時にProviderで受け取った Text の数値が変更されます。
またこれはwidget で切り離して使うこともできます。

class CountText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      /// context からModelの値が使える
      '${Provider.of<CountModel>(context).count}',
      style: Theme.of(context).textTheme.headline4,
    );
  }
}

なんと、model の count が Provider.of からシングルトンのように取得できるようになりました。
わざわざ count のプロパティを受け取る必要がないことがわかります。

これでビルドすると Flutter プロジェクトが作成された初期画面と同じ挙動になります。

全体のソースコード

最後に全体のソースコードを載せておきます。

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CountModel extends ChangeNotifier {
  /// 初期値
  int count = 0;

  /// count の更新メソッド
  void increment() {
    count++;
    notifyListeners();
  }
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}


class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<CountModel>(
      create: (context) => CountModel(),
      child: Consumer<CountModel>(
          builder: (context, model, child) => Scaffold(
              appBar: AppBar(
                title: Text('Flutter Demo Home Page'),
              ),
              body: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text(
                      'You have pushed the button this many times:',
                    ),
                    CountText(),
                  ],
                ),
              ),
              floatingActionButton: FloatingActionButton(
                onPressed: model.increment,
                tooltip: 'Increment',
                child: Icon(Icons.add),
              )
          )
      ),
    );
  }
}

class CountText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      /// context からModelの値が使える
      '${Provider.of<CountModel>(context).count}',
      style: Theme.of(context).textTheme.headline4,
    );
  }
}

ということで長かったですが、最後にこのパターンを暗記します。
一回テンプレート的に書き方を覚えたら後は応用になるからです。

ちなみに、この Provider パターンは、

RxSwift に例えるとするなら、
Consumer は BehavorRelay の accept で値を渡すところ
Provider は Observable で値を通知するところ

iOS でいうならば、
Consumer は NotificationCenter をpostするところ
Provider は NotificationCenter でadd して値を通知するところ

みたいなイメージです。

そんな印象でした。今日はここまで。

それでは、バイバイ!

63. Flutterでボタンにアニメーションを追加する

ボタンにアニメーションを追加する

本当はボタンに1回転のアニメーションを入れたかったのですが、Animation の回転が思わぬ位置に止まったので諦めました。
なので前回紹介したフリップのアニメーションを使ってボタンにアニメーションをかけます。

前回の続きはこちらになります。

tamappe.hatenadiary.com

使うアニメーションのパッケージはこちらです。

pub.dev

NumberButtonのウィジェットクラスを改造します。

改造前

class NumberButton extends StatelessWidget {
  final int number;
  final Function onPressed;

  NumberButton(this.number, this.onPressed);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.white, width: 2.0),
        borderRadius: BorderRadius.circular(10),
        color: Constants.orangeColor,
      ),
      child: FlatButton(
          child: Text(
            '$number',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
            ),
          ),
          onPressed: onPressed),
    );
  }
}

改造後

class NumberButton extends StatelessWidget {
  /// ボタンの番号
  final int _number;
  /// タップした時の処理
  final Function _onPressed;
  final bool _isOnTouch;

  NumberButton(this._number, this._onPressed, this._isOnTouch);

  @override
  Widget build(BuildContext context) {
    return FlipCard(
      direction: FlipDirection.HORIZONTAL,
      speed: 500,
      // タップイベント
      onFlip: _onPressed,
      flipOnTouch: _isOnTouch,
      front: _frontNumberButton(),
      back: _backNumberButton(),
    );
  }

  Widget _frontNumberButton() {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.white, width: 2.0),
        borderRadius: BorderRadius.circular(10),
        color: Constants.orangeColor,
      ),
      child: Center(
        child: Text(
          '$_number',
          style: TextStyle(
            fontSize: 30.0,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }

  Widget _backNumberButton() {
    return Stack(
      children: <Widget>[
        Container(
          width: 60,
          height: 60,
          decoration: BoxDecoration(
            border: Border.all(color: Colors.white, width: 2.0),
            borderRadius: BorderRadius.circular(10),
            color: Constants.orangeColor,
          ),
          child: Center(
            child: Text(
              '$_number',
              style: TextStyle(
                fontSize: 30.0,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
        Opacity(
            opacity: .8,
            child: Container(
                width: 60,
                height: 60,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10),
                  color: Colors.black,
                )))
      ],
    );
  }
}

注目するところはこちらになります。

FlipCard(
      direction: FlipDirection.HORIZONTAL,
      speed: 500,
      // タップイベント
      onFlip: _onPressed,
      flipOnTouch: _isOnTouch,
      front: _frontNumberButton(),
      back: _backNumberButton(),
    );

FlipCard はフリップ速度をコントロールできるので500 に指定します。
フリップの報告は縦は違和感しかないので横(HORIZONTAL) に指定します。

そして、前面(front) と背面(back) にそれぞれ乗せたいウィジェットを乗せるといった感じになります。

_frontNumberButton() のウィジェットはこちらになります。

  Widget _frontNumberButton() {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.white, width: 2.0),
        borderRadius: BorderRadius.circular(10),
        color: Constants.orangeColor,
      ),
      child: Center(
        child: Text(
          '$_number',
          style: TextStyle(
            fontSize: 30.0,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }

_backNumberButton() のデザインはタップ後にボタンを押せなくして、 さらに少し非アクティブに見せたいので黒色のカバーを乗せます。

  Widget _backNumberButton() {
    return Stack(
      children: <Widget>[
        Container(
          width: 60,
          height: 60,
          decoration: BoxDecoration(
            border: Border.all(color: Colors.white, width: 2.0),
            borderRadius: BorderRadius.circular(10),
            color: Constants.orangeColor,
          ),
          child: Center(
            child: Text(
              '$_number',
              style: TextStyle(
                fontSize: 30.0,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
        Opacity(
            opacity: .8,
            child: Container(
                width: 60,
                height: 60,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10),
                  color: Colors.black,
                )))
      ],
    );
  }

こちらをビルドして確認すると下のようなアニメーションが出来ているのが分かります。

f:id:qed805:20200530182924g:plain:w300
フリップアニメーションを追加

だんだんゲームぽく見えてきましたね。
これでゲーム画面での最後の機能は右上のタイマーのみになります。

ただ Dartの Time 系の API 操作が結構難しそうでこれを作れるかちょっと不安です。

62. Flutterライブラリflip_cardでフリップアニメーションを作ってみる

フリップアニメーションを簡単に作れる flip_card を使ってみる

今作っているナンバーズアプリでカードにアニメーションを入れたいのでアニメーションの入れ方を調査しました。 調査しているうちにシンプルなパッケージがありましたので簡単に紹介してみます。

フリップカードアニメーションを作れるflip_cardパッケージ

flip_card はフリップカードアニメーションを簡単に再現できる component です。

pub.dev

Horizontal Vertical
f:id:qed805:20200530112345g:plain:w300 f:id:qed805:20200530112417g:plain:w300

簡単に使い方を説明すると

FlipCard(
  direction: FlipDirection.HORIZONTAL, // default
  front: Container(
        child: Text('Front'),
    ),
    back: Container(
        child: Text('Back'),
    ),
);

このように デザインは Container で作った widget を front(全面) と back (背面) に乗せるだけです。 Card と FlatButton が合体した widget みたいな印象を受けました。 ボタンタップ時の処理は onFlip のプロパティがありますのでタップイベントもハンドリングできます。

サンプルソースコード

flip_card を入れる

yaml ファイルを編集して flip_card が使える状態にします。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  flip_card: ^0.4.4

yaml ファイルでインストールに失敗するときはインデントを見直します。結構あるあるです。

パッケージをインポートする

ファイルにインポートします。

import 'package:flip_card/flip_card.dart';

今回は簡単に StatelessWidget に FlipCard を乗せました。

class FlipCardButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FlipCard(
          direction: FlipDirection.HORIZONTAL,
          speed: 500,
          // タップイベント
          front: Container(
            width: 100,
            height: 100,
            color: Colors.redAccent,
            child: Center(child: Text('前面')),
          ),
          back: Container(
            width: 100,
            height: 100,
            color: Colors.blueAccent,
            child: Center(child: Text('背面')),
          ),
        ),
      ),
    );
  }
}

main.dart にはただ FlipCardButton ウィジェットを乗せました。

main.dart

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Numbers',
      theme: new ThemeData(
        fontFamily: 'Arial',
      ),
      home: FlipCardButton(),
    );
  }
}

これをビルドすると次のような画面が表示されます。

f:id:qed805:20200530115145g:plain:w300
FlipButtonCard

(はてなブログではめちゃくちゃ容量小さい gif でないとアップロードできないので動作が鈍いですが、実際は滑らかな動きです。)

これを使ってナンバーズアプリのボタンにアニメーションを入れていきます。

今日はここまでです。

それではバイバイ!