2019年12月9日月曜日

1人 Flutter Advent Calendar 2019:#9 flexで画像をcenterに


アプリを作成しているときには、大抵の場合は、画像自体も表示してほしいアスペクト比になっているたりすると思います。
とはいえ、画像を加工したりは普通にしたくなるとは思います。
中心に合わせて拡大したり、画像全体が表示されるようにしたりなど。

その場合にFlutterでどうするのかというとImage.assetのfitにcoverを指定したりすれば簡単に出来ます。
画像の表示をするだけならこれだけで十分ですし、SVGを表示したいと思った場合も、flutter_svgを利用すれば、変数にfitがあるので、それを指定すれば十分です。

その他にやりたくなりそうなのは、Containerの背景に画像を指定しようとした場合とかでしょうか。
その場合は、BoxDecorationとDecorationImageを利用すれば可能です。

この例ではjpegを背景としてchildにsvgを表示させています。
https://github.com/matsuhiro/flutter_image_test/blob/master/lib/main.dart#L121-L136

BoxDecorationはshapeで円に切り抜きなども出来るので、やってみます。
そうするとsvgの画像がはみ出しています。

こういった時にはClipOvalなどのClipXXXを使うのが良いかと思います。
https://github.com/matsuhiro/flutter_image_test/blob/master/lib/main.dart#L154-L170
SVGも含めて全体をclipする感じです。

コードの全体はここにあります。

https://github.com/matsuhiro/flutter_image_test

2019年12月8日日曜日

1人 Flutter Advent Calendar 2019:#8 flexboxで縦横くみあわせたり改行とか

FlexBoxを利用した場合の良さを実感できるのは、値としてflexやwrapを指定した場合かなと個人的には思っていたりします。


FlutterにもFlexがあるので、利用してみたいと思います。Flexの配下にFlexibleを指定しているのはパラメーターとしてflexを指定することで、要素の比率を決めるためです。
比率だけが固定されて幅が変われば全体的に変化します。

縦と横の指定を組み合わせれば、このようなことも可能です。

このレイアウト自体は、FlexとFlexibleとAspectで表現しています。

更にwrapについても見てみます。
FlexBoxだとwrapを指定することで、配下に存在する要素が折り返したりするのですが、Flex自体にはwrapに関連するパラメーターがありません。
かわりにWrapというWidgetが存在します。

Flexの配下に要素を並べたようにWrapの配下に要素を並べれば折返しが実現できます。
今回は、heightを固定して、widthをバラバラにした要素を横に並べてみました。




各要素のwidhtは同じなのですが、それが入る全体の幅が変わることで、UIの表示が変わっています。

これだけ見てもメリットが特に無いに感じるとは思うのですが、縦横比がバラバラの画像の高さだけを合わせて、全て並べるような場合がよく例に出されます。

上記2つを一つにまとめてみると以下のようになります。



幅の違うものを縦に並べていますが、レイアウトが崩れずに表示されているのがわかると思います。

Zeplinで表現されたものをFlutter上で表現することができることは以前に示しましたし、同じようなレイアウトをFlexでも実現可能なことは以前示しました。

どっちも良し悪しあるとは思いますが、個人的にはFlexibleやWrapなどを利用して表現するほうが、UI的な完成度は高くなるのではないかと感じます。
横幅に制限されないかつ、ただの縮小でなくレイアウトされるのは視認性の面でもメリットが高いと感じます。

ただ、FlexBoxなどに多少なりとも触れたことがあれば、FlexBoxのあれはどこにあるのだろうか?という発想でいろいろ探せますし、探してみるとFlutterにすでに欲しい機能の実装が存在することが多いと思います。
しかし、FlexBoxのことを何も知らないで、いきなりFlutterのflexなどを利用するのは少々厳しいのではないかとも感じます。

サンプルは
https://github.com/matsuhiro/flex_box_wrap
においてあります。

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
コメントアウト消してください。