🕺

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

13 min read 2

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

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

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

ログインするとコメントできます