プロトタイピングツールとしてのFlutter


はじめに

本稿は,クロスプラットフレームワークであるFlutterを使ってプロトタイピングをするときにどうすればいいかを雑に並べた記事です.
あくまでも個人の感想であり,「自分ならこうする」的なやつですが何かの参考になればと思います.

ターゲット

Flutterは現状モバイルに関しては1つのコードでAndroid,iOS両対応できるフレームワークで,更にデスクトップやwebの対応も進んでいます.立ち位置的には完全にエンジニアの使う開発環境という感じですが,すべてが構造的に書ける点や,組んだUIがすぐに動く点などにより,デザイナーがFlutterを使って直に組むのにも良いとされています.

参考:https://flutter.dev/docs/resources/faq#what-does-flutter-do

What does Flutter do?

For users, Flutter makes beautiful app UIs come to life.

For developers, Flutter lowers the bar to entry for building mobile apps. It speeds up development of mobile apps and reduces the cost and complexity of app production across iOS and Android.

For designers, Flutter helps deliver the original design vision, without loss of fidelity or compromises. It also acts as a productive prototyping tool.

自分は現在高専生で,ポジション的にはエンジニア寄りですが,デザインもやりたいと思っているので,デザインも多少できるエンジニア的なのを目指してます.この記事はどちらかというとエンジニア寄りでデザインも考えたい,みたいな欲張る人のために書こうと思いました.

静的なページを何も考えずにゴリゴリ書くとき

Material Designを使っていく

これは本当に簡単です(機械翻訳感).
Flutterの始め方とかそういうのは割愛しますが,

  • flutter create ***を端末に打ち込む
  • main.dartを開く
  • Scaffoldあたりを書き換えてどんどんWidgetを付け足していく

くらいです.静的なMaterial Designページを作るくらいだったら,Widget一覧を見て良さげなWidgetを使っていくくらいの温度感でできます.

デザインを自分でやりたい

見た目Material Designっぽいのはイヤだなってときは,Widgetをカスタマイズして対応していきます.
基本的な流れは上記と同じですが,FlutterにはContainerSizedBoxといった要素を入れる枠組みが存在し,その見た目をカスタマイズできるBoxDecorationなどが用意されています.
サンプルとして自分が何も考えず1時間くらいで組んだ(真似した)UIがこちらです.
https://github.com/Kurogoma4D/paepae

main.dartを見ていただければ分かると思うんですが,Containerがいっぱいあります

動的なページを作りたい / もうちょっと綺麗なコードを書きたい

プロトタイプとはいえ,やっぱり動いてほしいときがあります.UI要素を動かすためには考えることが一気に増えるわけですが,一番「どうすればいいの?」ってなるのが状態管理だと思います.

状態管理

Flutterには早くもいろいろな状態管理方法が提案されています.一番直感的で,コード量も少なく済む(?)のはStatefulWidgetを使った上でのsetState()だと思いますし,何も考えずに書くときもちょっと値を変えたいくらいだったらこれが便利です.上記paepaeでは,ボトムナビゲーションの選択時の状態管理に使っています.

main.dart
bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
              icon: Icon(
                Icons.home,
                color: Theme.of(context).unselectedWidgetColor,
              ),
              title: Text("ホーム")),
          BottomNavigationBarItem(
              icon: Icon(
                Icons.attach_money,
                color: Theme.of(context).unselectedWidgetColor,
              ),
              title: Text("残高")),
         // 中略
        ],
        currentIndex: _selectedItem,
        fixedColor: Colors.blueAccent,
        onTap: _onItemTapped,
        type: BottomNavigationBarType.fixed,
      ),
    );
  }

  void _onItemTapped(int index) {
    setState(() {
      _selectedItem = index;
    });
  }

しかし,setState()だけでは以下の問題点があります.

  • そこそこの規模になった時ロジックが分散してつらくなってしまう
  • 部分だけ抜き出してファイル分割したいときに,状態管理変数の置き場所に困る

そこで他の管理方法に手を付けるわけですが,どんな物があるかについてはmono氏の記事などで触れられています.
その中でも,自分がしっくり来たものを紹介します.

ChangeNotifierProvider

ざっくり説明すると,状態管理変数とそれを動かすロジックをクラスとしてまとめてしまって,それをWidgetツリーの上位の方で持っておき,変更されたらそれを使ってるWidgetを更新しようってことです.
それとなく図示したものがこちら.

(細かい部分を省略してます)

そしてこれを使った自作例がこちら
https://github.com/Kurogoma4D/ui_training_1

必要なものは,

  • 状態変数を宣言したモデルクラス(ChangeNotifierをmixinする)
  • ChangeNotifierProvider<モデル>

で,例えばルートページのWidgetをProviderの子にして,その下でテキストなど必要なタイミングでProvider.of<モデル>(context)を呼び出すなり,

main.dart
// スクロールを検知したときの処理
// スクロール位置から現在のページ番号を算出し,セットしている
  void _scrollListener() {
    var model = Provider.of<AppStateModel>(context);
    double positionRate =
        _scrollController.offset / _scrollController.position.maxScrollExtent;  
    model.setIndex((positionRate * model.pageNum).toInt());
  }

Consumer<モデル>を使うなり

main.dart
// それぞれのページを生成する部分
// メニューを開いてるかどうかがAppStateModelに入っているため,
// 開いていたらスクロール可能,そうでなければスクロール不可能にしている
Consumer<AppStateModel>(
  builder: (_, model, child) {
    return SingleChildScrollView(
      controller: _scrollController,
      physics: model.isMenuOpened
          ? const BouncingScrollPhysics()
          : const NeverScrollableScrollPhysics(),
      child: Column(
        children: <Widget>[
          Page1(),
          Page2(),
          Page3(),
        ],
      ),
    );
  },
)

本来であれば,管理を楽にするだけでなくテキストやボタンなど状態変数の絡むそれぞれの要素を宣言するタイミングで適宜モデルを呼び出すなどして,更新を効率よく行うという目的もあるproviderですが,今回の例では割とツリー上位の方でConsumerを使うなど結構雑に扱っています.
しかし,プロトタイプの段階と割り切ってしまえば,そんな感じでもいいかなと思ってます.パフォーマンス的には心配になりますが,コードがそこそこ綺麗な状態を保てるので後からいじっても(多分)setState()濫用よりは見やすい気がします.

AnimatedContainerを活用する

上記インタラクションの例でも使いまくってますが,これがとにかく便利です.
Containerのプロパティに加えてCurve(アニメーションカーブ)とDuration(アニメーション時間)を指定するだけで,値が変わったときに自動でアニメーションしてくれます.
Widget of the Weekでも紹介されています.
https://youtu.be/yI-8QHpGIP4
Animated系はいろいろありますが,Containerは元々が高機能なWidgetのため,プロトタイプレベルであればとりあえずこれ使っとけばいいのでは感があります

おわりに

書いてるうちに何を伝えたいのか微妙な感じになってきましたが,何が言いたかったかというと

  • Flutterはプロトタイプツールとしても使えるよ!
  • 何なら組んだUIそのまま使えるよ!

ってことです.つい最近SwiftUIが発表されて,UI組むだけなら良さそうとは思いましたが,Flutterにもクロスプラットフォームという良さがあるので個人的には何ともかんともって感じです(語彙力).