2019年12月7日土曜日

1人 Flutter Advent Calendar 2019:#7 layout by flex


ZeplinからUIを取り込むというのをやりましたが、Zeplinなどから取り込むのとは違う方法でやってみたいと思います。

基本的にはwidthなどに直値を指定せずに横幅が変わったらいい感じにレイアウトも変化するようにしたいです。

これを実現するにはaspect比を指定したり、親の幅のpercent指定でwidthを決めたりします。画像の重ね合わせはStackを利用して配置します。
https://github.com/matsuhiro/flex_box_percent

コードを見てもらえばわかるところではありますが、親の幅に合わせた変化のところで、FractionalSizedBoxを利用します。
それだけだと画像が縦長になってしまうので、AspectRatioで縦横比が常に一定になるようにしています。

このようなレイアウトを工夫した場合の一つのメリットはテキストサイズかなと思います。
基本的には横幅に合わせてレイアウトが変化するようにしているので、画面幅が小さくなったら、アスペクトを固定している画像などは小さくなります。
ただ、それと同じようにフォントまで小さくなってしまうと文字が読めないくらい小さくなることが懸念されます。
FittedBoxを利用して、Zeplinなどで指定されたデザインが歪まないように画面幅に合わせて大きさを変えた場合にこのようになります。フォントサイズは小さくなるので、視認性が悪くなる可能性があります。

しかし、フォントサイズを変えないようにすれば、以下のように文字の視認性が損なわれることはないです。(この例のデザインが良いかは別にして。。。)

サンプルのアプリだとGroupFlexというWidgetを作って、引数でwidhtを変えられるようにして、わかりやすくなるようにしています。

2019年12月6日金曜日

1人 Flutter Advent Calendar 2019:#6 list

Dartのtourのlistのページをみると
https://dart.dev/guides/language/language-tour#lists
listの中にifやforをまぜたり、...でlistを連結させられたりとか書いてあります。

面白そうなので、試してみるました。
      Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (_counter % 2 == 0)
              Text(
                'Count is even!',
              ),
            if (_counter % 2 != 0)
              for (var i = 0; i < _counter; i++) Text('This is $i'),
            ..._widgets(),
          ],
        ),
      ),

適当にこんな感じで入れるとエラーが出ます。2.2.2までサポートしていないよ。と。

Flutterの開発環境にはstable,master,dev.betaとあって、簡単に切り替えられるわけですが、stableで

$ flutter create [application name]

を実行するとpubspec.yamlの中のsdkversionが

sdk: ">=2.1.0 <3.0.0"

といった感じになります。
なので、2.2.2にすれば上記のコードも動作します。

サンプルはこんな感じです。
https://github.com/matsuhiro/flutter_dart_list_test
いつものfloating buttonをクリックするとcount upするやつです。

counterが偶数のときだけ「'Count is even!'」が表示されます。
奇数のときにはforで繰り返された文だけTextが表示されます。
最後に、適当にTextを関数からの戻り値で追加しています。

軽く使うなら便利だなと思いますが、たくさんWidgetを配置するならStatelessWidgetを作ったほうが良いのだろうとは思います。

2019年12月5日木曜日

1人 Flutter Advent Calendar 2019:#5 Flexbox


CSSにはFlexible boxというのがありますが、FlutterColumnとかもこれがベースになっています。
ColumnのParentを見るとFlexだったりすのでわかりやすいです。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/basic.dart#L4293

なので、FlexBoxの仕様に従ってデザインを実現して行けば画面サイズがかなり変わったとしてもうまいこと表示できたりもします。

たとえば http://tinyurl.com/yy55rm3m こんなレイアウトを作成しようとしたらどうなるでしょうか? 
アイテムごとにalignを変えています。ここで使っているのは、YogaというライブラリのPlaygroundです。YogaはReactNativeとかでも使われているライブラリです。
ここで、FlexBoxのレイアウトを試したりもできます。

Flutterには、FlexというWidgetがあるので、これを利用してみます。
directionを設定して、内部にFlexを入れ子にしてAlignmentを指定することで、上に上げたようなレイアウトが実現できます。
https://github.com/matsuhiro/flex_box_test/blob/master/lib/main.dart#L93-L146

YogaのPlaygroundで表現するとしたらhttp://tinyurl.com/y46o8ksr こんな感じの組み合わせで表現しています。

いまはFlexで実現してみましたが、ColumnはFlexでdirectionを指定した実装になっているので、ColumnとRowの組み合わせでも実現できます。
https://github.com/matsuhiro/flex_box_test/blob/master/lib/main.dart#L148-L198

flexを設定すると以下のように1:2:3のような感じにも出来ます。
https://github.com/matsuhiro/flex_box_test/blob/master/lib/main.dart#L200-L247

flexはFlexクラスには存在しないですがExpandedクラスなどには存在しています。個人的にはFlexクラスの中ですべてを実現できているとわかりやすいのですが、そうするとFlexクラスが巨大になるでしょうし、しかたが無いのかも知れません。

この辺のFlexBoxのレイアウトは、AndroidやiOSのエンジニアよりもWebのfrontendのエンジニアやReactNativeなどを利用していたエンジニアにとっての方が馴染みやすいかもなと感じます。

2019年12月4日水曜日

DartでThrowできるもの

DartのAdvent Calendarがあまりにも過疎っていたので、何でもいいから書こうかという気持ちになりました。
そこでLanguage tourを見直しました。

Exceptionのところをみると
NullでないObjectは何でも投げられる!
ということで、いろいろThrowしてみました。

まずはint
  try {
    throw 1;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: 1
投げられました!

いくつか続けて試します。boolとsymbolsとclassです。
  printDivider();
  try {
    throw false;
  } catch (e) {
    print('What is thrown: $e');
  }

  printDivider();
  try {
    int i = 0;
    throw #i;
  } catch (e) {
    print('What is thrown: $e');
  }

  printDivider();
  try {
    var h = Hoge(0);
    throw h;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
-----------------
What is thrown: false
-----------------
What is thrown: Symbol("i")
-----------------
What is thrown: Instance of 'Hoge'
投げてもいいんですね。 

ふとclassにinstanceを渡さなかったらどうなるのかなと思い試してみました。
 try {
    Hoge h;
    throw h;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: Throw of null.
行けましたね。コンパイルエラーとかにならないですね。
non-nullオブジェクトって言ってた気がしますがいけました。

じゃあ、null自体は?
  try {
    throw null;
  } catch (e) {
    print('What is thrown: $e');
  }

結果は
What is thrown: Throw of null.
これも、いけます。

ほんとになんでも投げられますね。
関数は。。。
試してみました。
 try {
    var f = (int i) {
      return i + 1;
    };
    print(f(1));
    throw f;
  } catch (e) {
    print('What is thrown: $e');
    print(e(2));
  }

結果は
2
What is thrown: Closure 'main_closure'
3

catchした後にも関数として利用できるのは面白いです。何に使うんだって話ですが。

ちなみに、
 try {
    var f = (int i) {
      return i + 1;
    };
    throw f;
  } catch (e) {
    print('What is thrown: $e');
    print(e(2));
  }

このように、tryのなかのprintを消してみたら、DartPadでは、結果は
What is thrown: Closure 'main_closure0'
Uncaught TypeError: i.$add is not a functionError: TypeError: i.$add is not a function
のようになってしまいました。

Flutter上で実行したら

I/flutter ( 7570): What is thrown: Closure: (int) => int
I/flutter ( 7570): 3
といった感じで成功したので、Platformによって挙動がちょっと違うみたいです。
詳しい人いたら教えて下さい。

サンプルは下にあります。
https://dartpad.dartlang.org/29bb6a884331c48abb9091a7f3c9941b

Flutter(Android)で実行した場合のログは以下のような感じです。
https://gist.github.com/matsuhiro/8700addb5939dc33be3433d055a96bc3

でも、ここでの内容は、使うことは無いと思います。
ドキュメントにも、こう書かれていますし。

1人 Flutter Advent Calendar 2019:#4 Flutter for Web


現状はまだ正式サポートじゃないです。
ただ、動かすことは可能なので、試してみます。

dev channelに変更してWebアプリを作成します。
作成の仕方はhttps://flutter.dev/docs/get-started/web とか見るのが早いのでやってみてください。コマンド何個か打つだけですし。
簡単に作成出来ます。

https://github.com/matsuhiro/flutter_for_web_test
サンプルおいておきます。

これを実行すると画面幅が十分ならば以下のようになります。

ただ、ブラウザってすごく小さくすることもできます。そうすると

こんなかんじになって、上の段は横幅が小さくなると右端の四角が隠れていると思います。

別にWebに限ったことではないですが、こういうことが起こるのでPositionedDirectionalとかは基本的に使わないほうが良いかなと思います。
Webの場合はユーザーが画面幅を自由に変えられるので、こういった問題が起こりやすいかなと思います。
Flutterのコードで横幅がXXXpx以下だったらこのレイアウト。とか、画面幅ごとにレイアウトの場所の定義テーブルみたいなのを持つのはあまりやりたくは無いですからね。。。

SnackBarもちゃんとブラウザで表示されて、UIに関して言えば、それなりに動くなーという印象です。

しかし、公式にもありますが、
https://flutter.dev/docs/development/platform-integration/web
dart:ioがつかえなかったりとまだまだな感じです。

なんとなくですけど、1年位で商用で利用できるようになることはないかなーという印象です。


Webとは直接関係ないですが、dev channelでは、以下のことがあったのでついでに記載しておきます。

stable channelはAndroid support libraryを利用していますが、dev channelはAndroidXです。
そのため、既存のProjectでflutter create .とか実行するとAndroidXに対応していないとかでメッセージが出ると思います。

他にはPluginの呼び出しをrunAppの前に行うと

https://gist.github.com/matsuhiro/1ccbc9a6a5527b2c490ed31b79ffd1eb

このようなエラーが表示されます。
dev channelだけです。

Pluginの機能を呼び出す前にWidgetsFlutterBinding.ensureInitialized()を実行せよと言っているわけです。
呼び出せばいいだけといえばいいだけですが、onRunApp的なinterfaceがFlutter側にあっても良いのかなとかは思いました。
アプリの起動シーケンスの中で、Native側の初期化をしたいとはあると思うので。

サンプルを試すときは
https://github.com/matsuhiro/flutter_for_web_test/blob/master/lib/main.dart#L6
コメントアウト消してください。

2019年12月3日火曜日

1人 Flutter Advent Calendar 2019:#3 display resolution


前回のソースをQVGAのデバイス実行してみると以下のようになります。

色々とはみ出していてデザインが破綻しています。
ボタンが要素の右の端にあったらもう押すことも出来ないです。

Androidだけではなく、iOSでも画面の解像度にに種類があります。
FlutterではMediaQueryから取得できるsizeはLogical Pixelとされており、Deviceに関係なくだいたい同になるようにいい感じにやってくれます。Androidのdpみたいなもんですね。

しかし、画面自体が小さすぎれば当然、上の画像のように切れてしまいます。

Androidは画像やsizeの定義ファイルを解像度ごとにdirectoryで分けることで、こういった状況に対処します。
https://developer.android.com/training/multiscreen/screendensities?hl=ja

Flutterでも画像に関しては似たような回避策がありそうです。https://flutter.dev/docs/get-started/flutter-for/android-devs#where-do-i-store-my-resolution-dependent-image-files

しかし、widthやheightなどの値については回避策が無さそうでした。
また、デザインツールで出力されるものはwidhtなどが直値で指定されるので、これですべての解像度に対応するのは難しいです。

とはいえ、デザインツールで作成したものからは直値しかわかりません。
アプローチの方法は、Percentで指定する、全体にScaleをする、FlexBoxを利用してサイズに対して柔軟に対応するかだと思います。

Percent指定ですとflutter_screenutil というのがあります。
最初に画面のサイズを決めて、以降はutilityで提供されている関数でwidth/heightを指定します。
基本的には最初に設定したサイズでscaleしているだけになります。
ただ、これだと全体でScreenUtil.getInstance()を利用してサイズを指定しないとダメなので、ちょっと手間ですし、画面の縦横比に従って設定値を調整しないといけません。
https://github.com/matsuhiro/display_resolution_tests/blob/master/lib/main.dart#L189-L275

全体にScaleする方法につい
https://flutter.dev/docs/development/ui/widgets/layout
の中から利用できるものを選んで解決しました。

私の見た中ではFittedBoxが解決策に最適でした。
使った場所はこんなかんじです。
https://github.com/matsuhiro/display_resolution_tests/blob/master/lib/main.dart#L85-L87

全体のレイアウトは崩さずに全体的にScaleされます。
現実には、見栄えのために画面幅に応じたPaddingを入れるのが良いと思います。

これは、最大でも画面幅の90%になるようにPaddingを入れてあります。

これを解像度が大きい端末で実行すると
この様になって、全体にきれいに表示できると思いますし、解決策としても簡単に出来るのではないかと思います。

ここで扱ったサンプルは
https://github.com/matsuhiro/display_resolution_tests
にあります。


FlexBoxに関しては、また違う機会に扱おうと思います。

2019年12月2日月曜日

1人 Flutter Advent Calendar 2019:#2 FigmaとZeplinでUI構築


Flutterでアプリを作成するにあたって、デザインツールでデザインしたものをきれいに取り込みたいという要望とか同期はアプリを作っていれば当然あると思います。
それを実現する方法としてFigma→Zeplin→Flutterの方法を一つの案として照会しようと思います。

連携のためにFigmaとZeplinはそれぞれDesktop版を入れてくれさい。
また、FigmaとZeplinでそれぞれProjectを一つ作っておいてください。

Figmaの細かい説明はしませんがFrameを作成してGoogle Pixel 2 XLを選択してみてください。これでだいたいいい感じに表示されると思います


Group化して、Component化などします。

(画像は適当に拾ったものです。。。)

Component化したGroupを選択した状態で、メニューからExportを選択すると

このようなメニューが出てくるので、Zeplinを選びます。
すると、1回、Browserを経由したあとで、以下のような画面になります。


Figma側では、以下を選んでおきます。


そうすると、Componentに以下のように取り込めます。


ココまでで、Designを取り込む準備が出来ました。

次にZeplinにFlutterのExtensionがあるので入れてみてください。
https://extensions.zeplin.io/johnatagoranet/flutter_extension
これです。

Zeplinを再起動すると以下のようにWidgetのコードが見られます。

これをコピペーすると


画像をassetsディレクトリーに入れて実行すると

となります。うまくいかないときは、Figmaの部品の並び順を見てみてください。z-indexで考えた時に下にあるものほど、下の順番にしないときちんと描画されないです。
Figmaの並び順でFlutterのコードも生成されるので、その順番で重なるためです。

なかなかの再現度ですが、微妙にずれています。
ずれている原因はborderのwidthの計算がextensionとFlutterの実装が合っていないようです。
borderのwidthを0にするとぴったり一致します。Blurなどのeffectは再現できていないようです。

同じようなことは、Sketch → Zeplinでも可能です。

このようにデザイナーが作成したものをきれいに取り込むことが出来るわけですが、小さい解像度のデバイスなどでは以下のようになってしまいます。

これについては、別途記述したいと思います。

ここで用いたcodeは
https://github.com/matsuhiro/figma_zeplin_test
こちらにあります。
Figmaはfigファイルというのを作成できだので、後でgitにでもpushしておきます。
Zeplinのプロジェクトは僕が無料版使っているから共有できないっぽいですね。