🕺

Flutterでこなれた感じのUIを作るコツ

2020/12/15に公開
3

Flutter してますか?

こんにちは、すぎっとです。
以前、Flutter 愛に溢れた記事を書きました。
https://zenn.dev/sugitlab/articles/d0b3858b300b0af64ed9

継続してどんどん Flutter の記事を描こうと思っていたのですが、なかなか時間がとれずにいました。

Flutter への愛をあんなに暑く語っておきながら、実はそんなに愛してなかったんじゃないの?

他のプラットフォームに浮気してたんじゃないの?

そう思われても仕方がないほど、時間が経ってしまいました。

実は Flutter への愛が溢れすぎた結果アプリを作らずにはいられなくなり 夢中でアプリ制作に勤しんでいました。 それがこちらです。

https://apps.apple.com/jp/app/目標管理アプリ-やるひゃく/id1544283769

やるひゃくは、私が毎年ノートに書いていた『やりたいことリスト 100』をアプリにしたものです。
これまで、やりたいことリストを作るときはどうしてもタスク管理のようになってしまい、やったかどうかの振り返りは結局年末までほったらかしになっていました。
しかし、やりたいことリストとして自ら掲げた事はもっと大事にした方がいいんじゃないか?とふと考えたことをきっかけに、やりたいことリストをただのタスクとして管理するのではなく大切な思い出として記録できるアプリ を作ろうと動き出しました。

開発の過程は Twitter に毎日投稿していたのでスレッドになっています。よければご覧ください。作り始めて 40 日くらいでできました。2 時間/日くらいなので、80 時間ですね(๑>ᴗ<)Flutter さまさまです。

ではまず、ここから・・・。

なんて言うんだろう、めっちゃ Flutter 感が強い

Flutter でアプリ開発を始めてすぐにこの感情が芽生えました。決して悪いことではないのですが、なんだか見る人が見ればすぐに これ Flutter だよね? と思えてしまうような、そんな見た目になってしまいました。

このやりたいことリスト管理アプリ「やるひゃく」は、Flutter で作りましたがまだ iOS 専用です。これは単に私が Android 端末を持っていないからで、満足にテストができないからです。また、育児も本業もやりながらの個人開発ですので割ける時間も限られていたことも理由のひとつです。

さて、Flutter の UI ですが、大きく 2 つの選択肢があると思います。

  • Material
  • Cupertino

私は「やるひゃく」を将来的には Android でもリリースしたいという思いがあったので迷うことなく Material を選択しました。プラットフォームに合わせて UI を切り替えるテクニックもあるらしいのですが、ちょっとまだ私には高度すぎたので、選択肢からは外しています。
これについては以前、mono さんが Twitter でまとめてくださっていたので、このあたりからたどっていただくと良いと思います。

さて、Material を選択した私はいそいそと iPad に落書きしておいたアプリのイメージを実装していきました。モックがざっくりでき上がったので iPhone にインストールしてみたのですが、ふとこんなことに気づきました。

「他のアプリと雰囲気が違うなぁ」

実際に iPhone にインストールすると、インストールされている他のアプリとついつい比べてしまいます。この形容が正しいかは疑問ですが、他のアプリはネイティブで作られているものが多かったからか「なんだか iPhone っぽいなぁ」そう感じました。

Material のデザインが iPhone に合ってないのか?

そんな疑問を持ってしまいました。

私は Material デザインは大好きです。React でアプリを作る時も Material-ui を使います。ただ、iOS で Material を使うと、どうしても iOS のネイティブと雰囲気が違ってきてしまうことが気になってしまいました。

しかし私は Flutter を愛しています。

Flutter は悪くない、Material は悪くない、私の使い方が悪いんだ

そこで、私のモックアップアプリと実際に iPhone にインストールされたアプリを細かく比較してどうすれば良い感じの UI になるかを試行錯誤しました。

なお、当方は非デザイナーです。
ただの、りかけいのおとこ です。

試行錯誤の結果、私が辿り着いた(ってほどのことでもないかもしれない)結論について説明していこうと思います。(前置きが大変長く、申し訳ありません。)

こなれていこう

私が気づいたポイントをいくつか紹介します。

Part1 ダークモード対応を一番最初にやっておく

いきなりダークモード対応を強要する感じになっていますが、その訳をこれから説明します。私が確認した限り、iPhone にインストールされているほとんどのメジャーなアプリはダークモードに対応していました。私のアプリはモックの段階では対応していませんでした。

そこで、 端末がダークモード設定の時、白基調のアプリはまぁまぁ視界がびっくりする と言うことに気づきました。

これはすなわち、ダークモードに対応しておかないと ユーザーをびっくりさせてしまう可能性がある と言うことです。ダークモードがここまで普及した現代では避けては通れないものなのかもしれません。つまり、私が強要しているのではありません、社会がそうさせるのです。(大袈裟)

さて、ダークモードにしようとすると必然的にダークモードとそうではない通常のモードそれぞれでどこの色が切り替わるのかを考えておかなければなりません。さらにはそのモードに応じて切り替わる色というのはきちっと ライト感ダーク感 のある色にしなければなりません。

私の心の中のデビルくん 😈 がささやきます。

「デザインの素人にライト感とダーク感なんて難しい話は無理だよ。少なくとも君には無理だ。」

そして心の中のエンジェルさん 🧚‍ がささやきます

「大丈夫、ライト感なんて白と薄いグレーでいいし、ダーク感なんて濃いグレーでいいのよ。Flutter にはそれっぽいテーマが用意されているわ。」

LOVE Flutter

素人が色で困らないようにいい感じのテーマを用意してくれているんですね!最高じゃないですか。
いい感じのテーマを使うのはとても簡単です。こんな感じです。

  • Flutter Create 直後
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  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 MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

ファビュラス...

最高ですね。もうダークモード対応ができました。

さらに、微調整したい人のためにcopywith()メソッドが用意されています。これは、Flutter さんが用意してくださっている light なテーマや dark なテーマをつかいつつも ここだけはカスタマイズしたい っていうことを実現するためのメソッドです。
お試しに PrimaryColor を変えてみましょう。

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData.light().copyWith(
        primaryColor: Colors.green,
      ),
      darkTheme: ThemeData.dark().copyWith(
        primaryColor: Colors.green,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

そうすると、こうなる!

ジーザス

なんてこった、Flutter さんが美しくしてくださっていたのに、いとも容易くダッセェ色合いの完成だ。黒メインに明るめの緑に蛍光色のアクセントなんて、密林で発見された新種のカナブンみたいなもんじゃないか。

ここで次の学びを得ることができましたね。(平常心

Part2 白系統や黒系統以外の色はここぞというところだけ使う

iOS のアプリをいろいろ並べて見てみましょう。

左から、

  • GoogleNews
  • Twitter
  • Instagram
  • Medium

すべて完全に白の世界にいらっしゃいますね。Medium は優しい緑、その他 3 つは少し薄めの青がアクセントとしてあしらわれていますが、あとは写真やアバターがメインですね。もちろんこれらのアプリはすべてダークモードにも対応しています。

Twitter がちょっとダークブルーなのはこれは設定がそうなっているからで、普通のダークモードも OK でした。

ここからわかることは、私のような非デザイナーはこういったビックウェーブに習うのが こなれた感じを出す ための近道だと言うことです。

これはすなわち、『あしらい』以外は白系統と黒系統で仕上げる ということです。

そしてまた次の気づきに出逢います。

Part3 色付きの AppBar は非デザイナーには早すぎたのかもしれない

AppBar は Flutter のデフォルトでついているものでなんとなくそこにありますが、あなたは AppBar をつかいこなせているでしょうか?
AppBar は左右にボタンを配置することができ、中央には少し大きめのコンテンツを配置できます。初期値はタイトル(TextWidget)ですね。

さきほどのビッグウェーブなアプリたちの AppBar を見てみてください。
通常モードなら白系統、ダークモードなら黒系統 になっていませんか?

iOS ってそうなんですよね。
ここで Flutter に慣れていたり、iOS 開発に慣れていれば、「それはまぁ、常識というか・・・iOS ってそうだよね。CupertinoWidget もそうなってるし・・・」

はい、そうなんです。CupertinoWidget 使っていればなんの疑問も持たずに済んだところなんです。でも、Flutter 初心者は Material から始めるんです。iOS のアプリを作りたくても、Material から始めちゃうんです。(私だけ・・・?)

CupertinoWidget を使った Flutter アプリを作ってみたい、という方にはこちらの Google Codelabs がオススメです。https://codelabs.developers.google.com/codelabs/flutter-cupertino#0

さてさて、CupertinoWidget を使ったら簡単にことがすみそうなのですが、今回は Material でいきます。AppBar を白系統にしてみましょう。

え〜いこんな感じだろうってやると・・・

theme: ThemeData.light().copyWith(
    primaryColor: Colors.white,
),

こうなります。

ジーザスアゲイン

なかなか一筋縄ではいきませんねぇ。
ここで、AppBar のテキストが見えないのなら、そこの色を黒系統にしたらええんとちゃうん?とまた思
いつきで行動すると、、、

appBar: AppBar(
  title: Text(widget.title, style: TextStyle(color: Colors.black)),
),

ダークモードで見えなくなりました。通常モードの方も、タイトルは見えるようになりましたが、iOS のシステム固有の部分(バッテリーなど)が白くて見えません。

こうやってハマっていくのですね。

実は AppBar で PrimaryColor だけを変更すると、iOS のシステム固有のカラーモードがついてこないんですね。なので、ThemeData.light()のデフォルトである青っぽい AppBar を想定して、白色になってしまいます。

じゃあ、AppBar の色ってどうやって変更するのが正攻法なんだろう?となりますね。私も正直どれが一番いいのかは分かっていません。ただ、ThemeData.light().copyWith()は AppBar に色がついている時はまぁまぁ便利なのですが、白い時はちょっといろいろ調整が必要になるので、別の方法をとったほうがいいんじゃないかと思っています。
それが、PrimarySwatch です。

たとえばこうすると、

return MaterialApp(
 title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.lime,
  ),
  darkTheme: ThemeData.dark(),
  home: MyHomePage(title: 'Flutter Demo Home Page'),
);

こうなります。
色付きはこれでも OK です。ここに White を設定してみます。
そうすると、エラーに見舞われます。

flutter: type 'Color' is not a subtype of type 'MaterialColor'

Colors.White は MaterialColor じゃないので Swatch には指定できません

もうどうしたもんかというところですね。
GitHub に Issue がありました。力技 🏋️ で対応できるようですね。プログラミングには筋肉が大事。

https://github.com/flutter/flutter/issues/23239

class MyApp extends StatelessWidget {
  final MaterialColor materialWhite = const MaterialColor(
    0xFFFFFFFF,
    const <int, Color>{
      50: const Color(0xFFFFFFFF),
      100: const Color(0xFFFFFFFF),
      200: const Color(0xFFFFFFFF),
      300: const Color(0xFFFFFFFF),
      400: const Color(0xFFFFFFFF),
      500: const Color(0xFFFFFFFF),
      600: const Color(0xFFFFFFFF),
      700: const Color(0xFFFFFFFF),
      800: const Color(0xFFFFFFFF),
      900: const Color(0xFFFFFFFF),
    },
  );
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: materialWhite,
      ),
      darkTheme: ThemeData.dark(),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

一苦労でしたが、それっぽくなりました。(もっといい方法ないかなぁ・・・)

ここまで頑張って白系統の AppBar を用意しておいて・・・

その次の気づきです。

Part4 AppBar を出しっぱにするのはまぁまぁ勇気がいることだ

さきほど使用した iOS の著名なアプリをもう一度見てみましょう。

AppBar って、スクロールしたら隠れますよね。それなりのコンテンツ量がある場合は AppBar を出しっぱなしにするっていうのはあんまり良くないんじゃないかなぁと思いました。

最近話題のオブジェクト指向ユーザーインターフェイスの書籍(通称 OOUI 本)にも、ユーザーの関心対象を捉えてシンプルに設計したほうがいいといったことが書いてありました(正しく理解できているかはまた別の話として・・・)
https://www.sociomedia.co.jp/10105

これを思うと、それなりのコンテンツ量がある場合はその コンテンツ が主役なわけで、AppBar みたいな画面のスペースを占有するものはなるべく隠してあげるのがいいんじゃないかなぁと思いました。

ということで、私はSliverAppBarを推薦します。Sliver 系統の Widget はなんだかかっこよくて大好きです。

https://api.flutter.dev/flutter/material/SliverAppBar-class.html

  • AppBar は必要な時にだけ出す
  • AppBar がそのアプリに本当に必要かどうかちゃんと考える

私なりの学びです。

さて、次です。ここからはここまでの内容と比べるとかなり Tips 的な感じがしますが、個人的に好きなテクニックなので紹介程度に説明します。

Part5 カドはなるべくとる

スティーブ・ジョブズが言ったとか言わなかったとか、なセリフに以下のようなものがあります。(ニュアンスだけ)

円とか楕円みたいに長方形も角を丸くしろ。世の中のものは大体そうなっている。

私、コレ好きなんですよね。
木で作った家具とか食器とか大好きなんですが、木の温もりって職人さんが手間暇かけて角を磨いてくれたすべすべの手触りがあってこそだと思うんですよね。ソフトウェアにもそういう温もりみたいなものを感じたいのかなぁと勝手に想像を膨らませている信者でございます。

さて、Flutter でカドをとるなら Decoration です。

body: Center(
    child: Container(
        width: 200,
	height: 200,
    ),
),

こんな感じの四角いコンテナーも、Decoration を設定すれば・・・

body: Center(
  child: Container(
    width: 200,
    height: 200,
    decoration: BoxDecoration(
      color: Colors.lime,
      borderRadius: BorderRadius.circular(10),
      boxShadow: [
        BoxShadow(
          offset: Offset(10, 10),
          color: Theme.of(context).scaffoldBackgroundColor,
          blurRadius: 20,
        ),
        BoxShadow(
          offset: Offset(-10, -10),
          color: Theme.of(context).scaffoldBackgroundColor,
          blurRadius: 20,
        ),
      ],
    ),
  ),
),

カドに丸みがでていい感じになりますね。
画面の横幅いっぱいのコンテンツはさすがにカドを丸める必要はないかなぁと思うのですが、画面のどこかにポツリと現れるコンテンツについてはカドを丸めてあげると、優しくてこなれた雰囲気になってくれると思います。

たとえばさきほど例にあげた Google News も、良く見ていただくと写真のカドが取れていることがわかると思います。

ではお次で最後です。

Part6 たまには Stack でも

こんな感じに Decoration と Stack を組み合わせてゴリゴリ組めばこんなことができます。配置の調整はもうちょっとちゃんとやったほうがいいんでしょうね・・・(手抜きごめんなさい)

body: Center(
  child: Stack(
    children: <Widget>[
      Container(
        width: 200,
        height: 200,
        decoration: BoxDecoration(
          color: Colors.lime,
          borderRadius: BorderRadius.circular(10),
          boxShadow: [
            BoxShadow(
              offset: Offset(10, 10),
              color: Theme.of(context).scaffoldBackgroundColor,
              blurRadius: 20,
            ),
            BoxShadow(
              offset: Offset(-10, -10),
              color: Theme.of(context).scaffoldBackgroundColor,
              blurRadius: 20,
            ),
          ],
        ),
      ),
      Container(
        alignment: Alignment.center,
        height: 100,
        width: 90,
        child: Container(
          alignment: Alignment.center,
          height: 50,
          width: 90,
          decoration: BoxDecoration(
            color: Colors.white,
            border: Border(),
            borderRadius: BorderRadius.only(
              topRight: Radius.circular(50),
              bottomRight: Radius.circular(50),
            ),
          ),
          child: Text(
            'test',
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
              color: Colors.black,
            ),
          ),
        ),
      ),
    ],
  ),
),

ちょっとした あしらい をつけたい時に Stack をうまく使うといい感じにこなれた感がでていいですよ。

まとめ

Flutter でこなれた感じのアプリを作る方法について紹介しました。
私自身がまだまだ Flutter 初級ですので、これから Flutter にチャレンジしたいという方にとって参考になればいいなぁと思っています。

また、最初に紹介させていただいた個人開発したアプリ「やるひゃく」を使って、2021 年のやりたいことリストの 1 つに「Flutter アプリを作る」を登録して、チャレンジしてみてはいかがでしょうか?
(最後の最後で宣伝、失礼しました)

ではでは。

すぎっと ٩( ᐛ )و

GitHubで編集を提案

Discussion

kazukazu

興味深くよみました。おっしゃるとおりFlutterで作るといかにもFlutterな感じがなんか気持ち悪いんですよね
特にiOSの設定画面のようなリストを作るときに感じます。ああCupertinoがいいなあ、、みたいな。

sugitsugit

Material Design自体はとても良いものなので、iOSでの見た目も意識しながら上手く使ってFlutterの良さを活かせるようにしたいですね。私はいまだに苦戦することがしばしばあります( ᐛ )

sugitsugit

筆者によるメモ

      theme: ThemeData.light().copyWith(
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.transparent,
          foregroundColor: Colors.black,
          elevation: 0,
          systemOverlayStyle: SystemUiOverlayStyle.dark,
        ),
     ),

とかやっておけば白いAppBar手軽にできて楽では(白いというか透明)

Darkはこっち。

      darkTheme: ThemeData.dark().copyWith(
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.transparent,
          elevation: 0,
        ),
     ),