AndroidStudioで「Flutter Device Selection」が表示されない
Flutter Webでホームページを作ってみた所感
Flutter Webでホームページを公開しました
こんにちは、Fastriver(@fastriver_org)です。先日私の所属しているKCS(Computer Society)の新歓特設サイトを制作、公開させていただきました。
Flutterはモバイル向けのフレームワークなので、「普通の」Webサイトを作ってみた事例は少ないと思われるためここに所感を書いておきます。
環境
>flutter doctor Doctor summary (to see all details, run flutter doctor -v): [√] Flutter (Channel unknown, v1.14.6, on Microsoft Windows [Version 10.0.18363.720], locale ja-JP) [√] Android toolchain - develop for Android devices (Android SDK version 29.0.1) [√] Chrome - develop for the web [√] Android Studio (version 3.6) [√] IntelliJ IDEA Community Edition (version 2019.2) [√] VS Code (version 1.43.1) [√] Connected device (2 available) • No issues found!
公開時点(2020/03/21)では Beta channelはv1.15.17なのですが、flutter runがうまく作動しないためv1.14.6に戻して作業しています。
レスポンシブ対応
Webサイトのレイアウトを考える上でレスポンシブ対応は非常に重要です。 特にflutterだとモバイル向けのデザインになりがちですが、それをPCで見るとつらいものがあります。
ですがFlutter、よく出来ているのでレスポンシブに対応するためのLayoutが多く揃っています。 私はページのベースとしてLayoutBuilderを使いました。
LayoutBuilder
LayoutBuilderはbuilderの引数にBoxConstraintsを持ち、現在の"そのWidgetの"大きさを教えてくれます。 またサイズが変わると都度リビルドをかけてくれるので、その値によってLayoutを組み直します。
情報としては画面のアスペクト比・PC or Phoneが欲しかったので以下のクラスを定義してツリーに流しました。
その他
その他レスポンシブに便利だったWidgetたちです。
AspectRatio
画面の比率に関わらずWidgetを一定の縦横比にリサイズしてくれます
Align
基本はStack内で使うものですが、Alignで囲むと大きさが(部分的に)親に依存しなくなるので便利です
Wrap
Column, Rowに似たもので、Mainの幅が足りなくなると自動的に折り返してくれます。chipとかによく使われるやつですね
Expanded
ColumnやRowに使います。余り部分を埋めてくれます
AutoSizeText
幅に合わせて文字の大きさを自動的に決定してくれます。最高
ExpandedGrid
私が作ったもので、こんな感じのViewが作れます。
パフォーマンス
Flutterさん、クロスプラットフォームにしてはAndroid, iOSでの実行速度が速く問題ないだろうと思っていたのですが、 Flutter Webは現状かなり重いです。
それもそのはずそれぞれの実装は
であり、またFlutter Webは殆どをCanvasで描画しているということがあります。 Stable版に期待したいところです。
特に気になる点
起動
main.dart.jsの読み込みに時間がかかります。
Google PageSpeed Insightsでみると、以下のように8秒以上かかっていることがわかります。
プロジェクト作ったときのサンプルでも数秒かかります、削減方法はよくわかってないです。
スクロール
今回一番躓いたのがスクロールでした。文字だけのスクロールでも60fpsを達成できません。 特に画像を中に数枚置くと、動きがガクガクになりました(画像の大きさより数の方が影響が大きい)。
こちらのトップページを見ていただけるとわかると思います。
またしっかり調査したわけではないのですが、周りの反応からみて泥ChromeよりもアイポンSafariの方がスクロールに関してはスムーズだったようです。 (端末のスペックにもよるので一概には言えない)
一応対策として以下のようなStatefulWidgetを作り、スクロールの現在位置を監視しながら表示されていないViewのOpacityを0にすることで 常時読み込みのWidgetを減らしています。
少し長いのでPageでの実装は
を見てください。
現状Flutter Webでスクロールさせるのはやめたほうがいいです。許されても単純なListViewだけです(ListView.Builderを使おう!)。
パス(URL)
Webサイトにとって完全な単一ページでない限り、それぞれのページにパスを設定し直接飛べるようにしなければなりません。 Flutter Webでは基本全てが"#/"に飛ばされるのでFlutter側でパラメータの読み取り・URLの変更・画面遷移をする必要があります。
公式ではまだまだ対応が少なく(そもそもモバイルのroutingを使っているので面倒)、ライブラリも決定的なものはないようです。
私が実装したかった機能はこれらです
- 画面遷移時にURLを変更
- そのURLに直接アクセスするとそのページに飛ぶ(rootからツリーを辿る)
- パラメータを渡す
- Modalな画面遷移
画面遷移時のURL変更
これは標準で対応してくれているので、すぐにできます。やり方はMaterialAppsのRoutesに登録する方法と、MaterialPageRoutesを使う方法があります。
MaterialAppsにはroutesという引数があるので、パスと遷移先のページを指定します。
その後遷移したいときに、Navigator.pushNamde()にパスを渡して呼び出します
実はこの方法を取れば2個目の直接遷移ができます。ただしURLには注意が必要で、#/が入っていなければいけません。
routesに登録していない場合でもMaterialPageRoutesでパスを指定することでURLが変更されます。
パラメータを渡す
Flutter WebがURLから呼ばれたときの挙動は、
- routes内に "#"より後(hoge.hoge/#[この部分])の一致するものはあるか確認、ある場合は遷移
- onGenerateRoute()を呼び出す
- 返り値がnullの場合はhomeに指定されたページに遷移
onGenerateRoute、homeは共にMaterialAppsの引数に取ります。onGenerateRouteは引数にRouteSettingsを取り、 その中(settings.name)に"#"より後のパスが入っているのでこれをパースすることでパラメータを得られます。
後はUriクラスなどを使って適当に処理しましょう。
Modalな画面遷移
かっこいいのでHeroでつながったポップアップ式の画面遷移にしたい+都合上それぞれにURLを振りたい
ということを考えていました。しかし
- 自由なレイアウトのためDialogではなくNavigatorで遷移する
- URLを振るためにPushNamed()で遷移したい
- ページを重ねるために背景を透明にしなければならない
- しかしPushNamed()だと背景を透明にすることが出来ない
となっており、一筋縄ではいきません。
3つ目についてですが、Navigatorは内部でStackを利用し、Pageを上に重ねて遷移しているらしい(未確認)ので PageRouteBuilderのopaqueをfalseにすると背景を透明にでき、下のページが見える感じになります。
どうしてもパスの文字列で画面遷移したかったので、routesを保持するクラスを作ってそこからPageRouteBuilderを呼ぶようにしました。
ハイパーリンク
FlutterのTextViewはHTMLの文字とは違い<a>タグでリンクを貼ることができません。 そのためなにかWidgetにタッチイベントを付けてそこから飛ばす実装をすることになります。
Flutter Webでも外部リンクはurl_launcherを使えます。
タッチイベントを貼るのには
- Card+Icon
- Button類
辺りが分かりやすいと思います。
hover時にカーソルを変えるのはここを参考にしました。
埋め込み
他サイト、HTML、MarkDownなどの埋め込みにはeasy_web_viewが使えました。
内部でdart:htmlを使っているようなので動作も軽快です。ただMarkDownについてはCSSの設定がよくわかりませんでした。
PWA
PWAは現在Flutter Webは標準で対応しています。特段の設定は必要ないようです。情報はweb/manifest.jsonに書けば反映されます。
スプラッシュ画面
パフォーマンスの項でも話しましたが、Flutter Webは起動がなかなか遅いです。白い画面が続くとブラウザバックの率が高まります。 少し考えると、main.dart.jsが読み込まれるまではindex.htmlの内容が表示されているわけで、そこに写真を貼り付ければ スプラッシュ画面の代わりとして使えます。
アナリティクス
google analyticsは、ページごとに統計をとってくれるようなので普通のWebページ用のだとHPに訪問したかどうかしかわかりません(実質単一ページのため)。
Firebaseパッケージではなんとanalyticsが使えます。
なのでPageを読み込む時(initState()とか?)に
analytics().logEvent("PAGE_MAIN", null);
のように埋め込んでイベントを記録することで、ページごとの訪問数を記録できます。
まとめ
- 現状パフォーマンスにおいて実用には向かない
- パス周りも苦労を強いられる
- スクロールさせないSPAであればそこそこ行ける気がする
- 細かい機能はPackageを漁れば結構見つかる
- HTML,CSS,JSを書かなくていいのは素晴らしい
Flutter Webの将来は明るいと思います。発展させていきましょう!
Flutter on the WebでFirebase導入して詰まった
状況
Flutter WebはFirebaseを導入する際、モバイルとは違いJavaScriptを読み込んで使う。そのためindex.htmlに\<script>を仕込むのだが、異変が起きた。
Launching lib\main.dart on Chrome in debug mode... Building application for the web... Attempting to connect to browser instance..
flutter runをしてもここから一切動かないのだ。ググったらimportを消せとか色々策があったが効果はなかった。
解決
A . main.dart.jsを読み込んだ後にFirebaseの設定をしようとしていませんか?
はい。
<body> <!-- The core Firebase JS SDK is always required and must be listed first --> <script src="https://www.gstatic.com/firebasejs/7.8.1/firebase-app.js"></script> <!-- TODO: Add SDKs for Firebase products that you want to use https://firebase.google.com/docs/web/setup#available-libraries --> <script src="https://www.gstatic.com/firebasejs/7.8.1/firebase-analytics.js"></script> <script> // Your web app's Firebase configuration var firebaseConfig = { //略 }; // Initialize Firebase firebase.initializeApp(firebaseConfig); firebase.analytics(); </script> <script src="main.dart.js" type="application/javascript"></script> </body>
とするのが正解です。
真理
終わりに
公式の文章はちゃんと読もうね!
下からひょこっと出てくるボタンを作る(Android/Kotlin)
やりたいこと
画面を開いたら下からひょこっとボタンが出てくる
ボタンを下にフリックすると引っ込む
できたもの
ひょっこりと pic.twitter.com/HkFTsBah1F
— Fastriver (@Fastriver_org) February 2, 2020
よい
前提として
これは
の応用例です。実装の際はこちらも一読すると良いと思います
作ってみる
環境はAndroidStudio+Kotlinです。
ボタンを用意する
画面外下から出したいのでConstraintLayoutを使って下に配置します。
Top_ToBottomOfで画面外配置できるのかなり便利
ボタンを出す
これが本題かな?(そうでもない)
アニメーションには前回も使ったSpringAnimationを使います。
説明は前の分を見てください。
遅延を付けて出す
画面を出してから少し遅延を付けて出したいので、Kotlinの非同期処理としてCoroutineを利用します。
dependencies { //略 //Physical Based Animation implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' //Coroutine implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3" }
GlobalScopeでCoroutineを立て、1秒delayしてからアニメーションを実行するよう設定します。animation.start()はバックグラウンドスレッドで呼び出すと怒られるのでwithContextでスレッドを変えましょう。
出す長さの計算としてviewHeightとmarginを使っています。
val buttonMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16f+16f, resources.displayMetrics)
この一文ですが、SpringAnimationはPixel単位で動かすのでこう書くとDPから変換してくれるらしいです(やらないと端末ごとに移動距離が歪になる)。
引っ込める
利便性のため引っ込める関数も作っておきます。こちらは呼び出されたらすぐ実行するだけなのでCoroutineなしで書きます。
ぐにぐに動かす
前の記事のとおりです。今回は上方向に動いてほしくないので上に指が動いたときのみ制限をかけるようにします。
originAnimationは外部からアクセスしたいのでActivity内に宣言しました。
下へのフリックで引っ込むようにする
フリックの検知にはGestureDetector.SimpleOnGestureListenerを使います。
class MainActivity : AppCompatActivity() { private lateinit var buttonGestureDetector: GestureDetector //略 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val buttonGestureListener = object: GestureDetector.SimpleOnGestureListener() { override fun onFling( e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float ): Boolean { println("速度: Y=$velocityY") return super.onFling(e1, e2, velocityX, velocityY) } } buttonGestureDetector = GestureDetector(this, buttonGestureListener) limitedButton.setOnTouchListener { _, e -> //略 buttonGestureDetector.onTouchEvent(e) } //略 } }
ボタンのフリックした速度が出力されると思います。
適当な速度を超えたら引っ込むようにします。Viewが移動していてもよしなにアニメーションを作ってくれるのがSpringAnimationの素晴らしいところ。
パラメータがいくつかあるのでそれらを調整すれば、それなりの動きになると思います(最初の動画は動きすぎ...)。
最後に
Animationたのしい
出来上がったコードはGitHubにあげてあります(Branch切ってるだけなので見にくい)。
指で"少し引っ張れる"Animationを付ける(Android/Kotlin)
正式名がよく分からない
Material Designなどではあまり見かけませんが、必要性が出てきたので作ってみました。
何に使うのか
業務にて動きのある/指で動かせるボタンを作る機会があったのですが、動かせる範囲を制限したくこのような動きを考えました。これについては他の記事で話しましょう。
作ってみる
環境はAndroidStudio+Kotlinです。
SpringAnimation
今回、指を離した後に戻る部分のアニメーションにはSpringAnimationを使っています。これはAndroidで使えるAnimationのうちの一つ、「Physical based Animation」の一種で、簡単にバネの動きをViewに対して実装できます。
app/build.gradleに以下を追加しましょう。
dependencies { //略 implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' }
指に追随させる
指の動きに合わせてViewを動かすには、ViewのonTouchListenerを利用します。
limitedButton.yに座標(pixel)を書き込むことでボタンを移動できるのですが、ここではtouchGapYとstartRawYを使って制御しています。
ボタンのY座標には"親の"Viewの左上からの座標を指定しなければならないのに対しe.rawYで取得できるのは"画面の"左上からの座標です。この分のズレを吸収するためにViewが押された瞬間に2つの差分を取ってtouchGapYに保持します。そして動いたときにはtouchGapYを引いてあげればちょうど良くなるということです。
MotionEvent.ACTION_DOWN -> { touchGapY = e.rawY - limitedButton.y startRawY = e.rawY } MotionEvent.ACTION_MOVE -> { limitedButton.y = e.rawY - touchGapY }
動きの限界を設定する
本題です。限界を設定するにしても動きがスムーズでなければ不自然になってしまいます。そこで私はarctanを使って滑らかさを作りました。なにかの値に収束する関数であればなんでもいいと思います。
2/πに収束するのだと不便なので正規化してこのような関数を用意しましょう。
適当に動かした分(startY-rawY)に応じた量(α)でボタンが動くようにします。
これでlimitを超えない範囲でいい感じに動いてくれるようになります。実際に動かしながらvariationの値などを変えるとよいです。
離したら戻るようにする
ここまでだとただ単に動きの悪いDraggableButtonです。先のSpringAnimationを付けてみましょう。
次の関数を用意します。
まずSpringAnimationの引数で対象と終点を設定します。終点の種類には以下のようなものがあります。
DynamicAnimation. | 説明 |
---|---|
ALPHA | 透明度を設定 |
TRANSLATION_(X|Y|Z) | 現在座標からの移動距離(px)を指定 |
(X|Y|Z) | 画面の左上からの絶対位置を指定 |
SCALE_(X|Y) | 拡大縮小を設定 |
次にAnimationの物理的な特性である剛性(spring.sfiffness)と減衰率(spring.dampingRatio)を指定します。
- 剛性を上げる -> 勢いが強くなる
- 減衰率を下げる -> 終点に達するまでの揺れが多くなる
公式が詳しいので目を通すのがよいです。
指を離したときに関数を呼ぶようにすれば完成です!
MotionEvent.ACTION_UP -> { goBack2MyRoots(startRawY - touchGapY) }
ついでにX座標にも動かせるようにしたのが以下のコードです。冒頭のGIFもこれですね。
参考
出来上がったコードはGitHubにあげています。
Flutter Web で最速Modern Web App公開
Flutter Webが本体に帰ってきた
Flutterは今までAndroid, iOSに両対応したMobile Appが作れるフレームワークとしていましたが、Flutter1.9から別に開発されてきていた「Flutter for Web(Hummingbird)」が統合され、晴れてFlutter Webとなりました!
これでAndroid, iOSと同列でWebアプリにも同一コードで対応できるようになったわけです。(まだテクニカルプレビューなので注意)
早速やっていく
まずはflutter 1.9以上が必要なのでアップデートしましょう。
(私の環境が変なのはあしからず)
>flutter doctor Doctor summary (to see all details, run flutter doctor -v): [√] Flutter (Channel master, v1.12.5-pre.21, on Microsoft Windows [Version 10.0.18362.476], locale ja-JP) [√] Android toolchain - develop for Android devices (Android SDK version 29.0.1) [√] Chrome - develop for the web [√] Android Studio (version 3.5) [√] VS Code (version 1.40.2) [√] Connected device (2 available) • No issues found!
そうしたら File > New > New Flutter Project... から新しいプロジェクトを作ります。
BMIを計算するアプリを作ります
Projectが作成されると、ディレクトリにWebが追加されているのが分かると思います。
適当にコードを変更して、Chromeからデバッグしてみましょう。
コマンドラインから、
>flutter run -d chrome
でできます。
できました。ここまでWeb向けのコードは一切書く/書き換えていません。HotReloadも使えます。
公開してみる
Flutter WebはコードをJSにトランスパイルしてWebページの作成を実現しています。要は静的ページです。と、いうことは、
GitHub Pagesで無料で公開できる!
というわけです。やっていきましょう。
>flutter build web
でトランスパイルします。すると build > web にファイルが生成されます。
そうしたらGitHubで適当にRepositoryを作り、Webの階層からpushしましょう(rebuildしたら消えるので別に移動した方が良い)。
Pushが完了したら、GitHubのページに行き、Settings > GitHub Pages でmasterブランチを選びます。
1分も待てば公開されます。当然独自ドメインも使えますよ。
https://organic-nailer.github.io/calc_bmi_web_app/
今回作ったのがこれです。記事書きながらでも1時間かかりませんでした。
...わかりますかこの速度感!!これはやばいですよ(アプリ開発者の感想)
まとめ
いかがでしたか?
まだテクニカルプレビューですが触る限り大きなバグは見られず、試用程度なら十分に使えると思いました。Stableになるのが非常に楽しみです。
ちなみにFlutter Webでいくつかアプリを作っているので、よければ遊んでみてください
この2つは1日で作成
動物将棋 https://doubutsu.fastriver.dev/#/
ルーレット https://roulette.fastriver.dev/#/
Android版から移植
クラタン https://clatan.fastriver.dev/#/
今回作ったアプリのコードはGitHubに転がしておきます。参考にどうぞ
Huaweiのスマホのテーマを自作する
Huaweiこそ至高
EMUIの魅力の一つとして、テーマ(見た目)を自由に変えられるということがあります。Huaweiのテーマストアなり、Playストアなりでテーマを手に入れて適用することで、通知バーやロック画面、ランチャーなどを変更することができます。
悩み
割と有料のテーマが多い。
自分にピッタリ合う物が見つからない。
適用すると文字が読めなくなりがち。
ほならね?
ストアで提供されてるものであればSDKが配布されてるはずなので、自分で作れるだろ、と。やっていきましょう。
Toolのインストール
ここから Huawei Theme Tool Suite のzipをダウンロード、インストール。私はWindows Defenderに文句を言われました。
hwtToolがそれです。
作成
左の Themes->New で新規作成できます。もしくは.hwtファイルをimportして編集することもできます。
色々と編集ができる。
ロック画面は結構自由度高めに編集できるよう(触ってない)で、他は色を指定したり画像を指定することでテーマを作れます。手元にHuawei端末があればPCにつないでその場で動かすこともできます。GUIで簡単に作れて素晴らしいですね。
...と思いきや
実際には一部分しか作れない
このテーマ、全部一括で変更できそうに思えますが、実はアプリ単位でテーマを設定してます。なので最大でも左に表示されているアプリ(launcher, contacts, messaging)+システムUI(通知バーなど)しか変更できません。私としては設定アプリのテーマを弄りたかったのですができませんでした。
まだ手はある
実はhwtToolで新規作成・importしたテーマは /hwtTool/res 下に配置されています(ex: C:\Program Files (x86)\hwtTool\res)。そこに色を指定したxmlや画像が入っているので、直接弄ることでもテーマを作ることができるのです。というよりもそもそも.hwt自体がただのzip圧縮なのでTool Suiteなんぞ無くともテーマは作れるんですね。
問題
ただ製作するにあたって一つだけ問題があります。ドキュメントが見つからない。
日本語はおろか英語にも多分存在しません(中国語ならある?)。Huawei DeveloperもhwtToolの使い方しか載せていませんでした。
頑張っている人たちはいるようですが私は解読する気力がないです...
私は諦めました
通知バーのデザイン変えたい!くらいなら好きな色にできるので有用だと思います。
エセダークモード
誰かドキュメントの作成...ないしは発掘お願いします!(他力本願)