- 投稿日:2020-02-16T21:05:27+09:00
[Flutter] Dribbbleデザイン、そのまま作ろう その14
挨拶
こんにちは 皆さん!Dreamwalkerです。
明けましておめでとうございます。
令和2年の初投稿になります。ほぼ2ヶ月ぶりに書き始めることになってしまいました。泣
今回のデザイン
今回は14番目の「DribbbleのデザインをFlutterでやってみた」になります。
Monochrome Shop App
https://dribbble.com/shots/9954517-Monochrome-Shop-App/attachments/1989500?mode=media
結果
一つ目のページ。
二つ目のページ。
三つ目のページ。
必要なライブラリー
1.
flutter_staggered_grid_view: ^0.3.0
全てのコード
main。
main.dartimport 'package:flutter/material.dart'; void main() => runApp(MonochromeShopApp());MaterialApp Importer。
home_page.dartclass MonochromeShopApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: MonochromeShopHome(), ); } }一つ目のページ。
個人的にお気に入りのデザインになります。
AppBarのタイトルはイメージがないので、Widgetで作ってみました。ShopMainPage
main_page.dartclass MonochromeShopHome extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: <Widget>[ Positioned( top: 34, left: 16, right: 16, child: Container( height: 100, child: Stack( children: <Widget>[ Center( child: Text( "LOSER", style: TextStyle( fontSize: 64, letterSpacing: -6, fontWeight: FontWeight.bold, ), ), ), Center( child: Text( "V", style: TextStyle( fontSize: 84, letterSpacing: -8, color: Colors.red, fontWeight: FontWeight.bold, ), ), ) ], ), ), ), Positioned( bottom: 32, right: 24, child: GestureDetector( onTap: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => WomensBlazersPage())); }, child: Container( height: 86, width: 86, decoration: BoxDecoration(shape: BoxShape.circle, border: Border.all()), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text( "explore", style: TextStyle(fontSize: 16), ), SizedBox( height: 4, ), Icon( Icons.arrow_forward, size: 20, ), ], ), ), ), ), Positioned( left: 4, bottom: 4, child: Container( width: MediaQuery.of(context).size.width / 2, height: MediaQuery.of(context).size.height / 3, decoration: BoxDecoration( color: Colors.black, image: DecorationImage( image: NetworkImage( "https://cdn.pixabay.com/photo/2014/01/04/14/35/fashion-238553__340.jpg"), fit: BoxFit.cover)), ), ), Positioned( right: 0, bottom: MediaQuery.of(context).size.height / 3.5, child: Container( width: MediaQuery.of(context).size.width / 1.4, height: MediaQuery.of(context).size.height / 2.8, decoration: BoxDecoration( color: Colors.black, image: DecorationImage( image: NetworkImage( "https://cdn.pixabay.com/photo/2016/06/17/09/54/beauty-1462986__340.jpg"), fit: BoxFit.cover, ), border: Border.all(color: Colors.white, width: 5)), ), ), Positioned( left: 0, top: 136, child: Container( width: MediaQuery.of(context).size.width / 2.4, height: MediaQuery.of(context).size.height / 3.4, decoration: BoxDecoration( color: Colors.black, border: Border.all(color: Colors.white, width: 5), image: DecorationImage( colorFilter: ColorFilter.mode(Colors.black, BlendMode.color), image: NetworkImage( "https://cdn.pixabay.com/photo/2016/03/09/10/22/two-women-1246024__340.jpg"), fit: BoxFit.cover)), ), ), Positioned( left: MediaQuery.of(context).size.width / 2.4 + 16, top: 136, right: 0, child: Container( height: 120, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ Text( "Men's", style: TextStyle(fontSize: 18), ), Text( "Blazers", style: TextStyle(fontSize: 18), ) ], ), ), ), Positioned( left: 16, top: MediaQuery.of(context).size.height / 2, child: Container( height: 100, width: 100, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text( "Women's", style: TextStyle(fontSize: 18), ), Text( "Blazers", style: TextStyle(fontSize: 18), ), ], ), ), ), Positioned( right: 16, left: MediaQuery.of(context).size.width / 2 + 24, bottom: 120, child: Container( height: 80, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ Text( "Kid's", style: TextStyle(fontSize: 18), ), Text( "Blazers", style: TextStyle(fontSize: 18), ) ], ), ), ) ], ), ); } }二つ目のページ。
GridViewが魅力的で今回のポイントになります。
entered_page.dartimport 'package:flutter/material.dart'; import 'package:flutter_notebook_4th/ep355_monochrome_shop_app/detail_page.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; class Wear { String img; String price; String name; Wear({this.img, this.price, this.name}); } List<Wear> items = [ Wear( img: "https://cdn.pixabay.com/photo/2017/08/06/09/51/blazer-2590798__340.jpg", price: "134", name: "Retro Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2017/08/10/08/00/suit-2619784__340.jpg", price: "365", name: "Fashion Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2016/07/19/10/48/girl-1527959__340.jpg", price: "428", name: "Classic Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2016/03/23/08/34/beautiful-1274361__340.jpg", price: "122", name: "Modern Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2017/08/06/09/51/blazer-2590798__340.jpg", price: "134", name: "Retro Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2017/08/06/09/51/blazer-2590798__340.jpg", price: "134", name: "Retro Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2016/07/19/10/48/girl-1527959__340.jpg", price: "428", name: "Classic Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2016/03/23/08/34/beautiful-1274361__340.jpg", price: "122", name: "Modern Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2016/07/19/10/48/girl-1527959__340.jpg", price: "428", name: "Classic Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2016/03/23/08/34/beautiful-1274361__340.jpg", price: "122", name: "Modern Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2016/07/19/10/48/girl-1527959__340.jpg", price: "428", name: "Classic Blazer"), Wear( img: "https://cdn.pixabay.com/photo/2016/03/23/08/34/beautiful-1274361__340.jpg", price: "122", name: "Modern Blazer"), ]; class WomensBlazersPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( children: <Widget>[ Expanded( flex: 2, child: Padding( padding: const EdgeInsets.only(right: 16, left: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Icon( Icons.arrow_back, color: Colors.grey.shade500, ), Text( "Women's Blazers", style: TextStyle(fontSize: 18, color: Colors.black87), ), Icon( Icons.search, color: Colors.grey.shade500, ), ], ), ), ), Expanded( flex: 13, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: StaggeredGridView.countBuilder( itemBuilder: (context, index) { return GestureDetector( onTap: (){ Navigator.push(context, MaterialPageRoute( builder: (context)=>ItemDetailPage( wear: items[index], ) )); }, child: Container( child: Column( children: <Widget>[ Expanded( flex: 8, child: Container( decoration: BoxDecoration( image: DecorationImage( image: NetworkImage(items[index].img), fit: BoxFit.cover, colorFilter: ColorFilter.mode( Colors.black, BlendMode.color))), ), ), Expanded( flex: 2, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text("\$ ${items[index].price}"), Text("${items[index].name}") ], ), ) ], ), ), ); }, itemCount: items.length, crossAxisCount: 4, mainAxisSpacing: 16, crossAxisSpacing: 16, staggeredTileBuilder: (int index) { return new StaggeredTile.count(2, index.isEven ? 4 : 3); }, ), // child: GridView.builder( // itemCount:items.length, // itemBuilder: (BuildContext context, int index) { // return Container( // child: Column( // children: <Widget>[ // Expanded( // flex: 8, // child: Container( // decoration: BoxDecoration( // image: DecorationImage( // image: NetworkImage( // items[index].img // ), // fit: BoxFit.cover, // colorFilter: ColorFilter.mode(Colors.black, BlendMode.color) // ) // ), // ), // ), // Expanded( // flex: 2, // child: Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // children: <Widget>[ // Text("\$ ${items[index].price}"), // Text("${items[index].name}") // ], // ), // ) // ], // ), // ) ; // }, // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // crossAxisCount: 2, // childAspectRatio: 0.55, // crossAxisSpacing: 16, // mainAxisSpacing: 16 // ), // // ), ), ) ], ), ), ); } } class searcher extends SearchDelegate { @override List<Widget> buildActions(BuildContext context) { // TODO: implement buildActions throw UnimplementedError(); } @override Widget buildLeading(BuildContext context) { // TODO: implement buildLeading throw UnimplementedError(); } @override Widget buildResults(BuildContext context) { // TODO: implement buildResults throw UnimplementedError(); } @override Widget buildSuggestions(BuildContext context) { // TODO: implement buildSuggestions throw UnimplementedError(); } }三つ目のページ。
Product Detail Page
detail_page.dartimport 'package:flutter/material.dart'; import 'package:flutter_notebook_4th/ep355_monochrome_shop_app/entered_page.dart'; class ItemDetailPage extends StatelessWidget { final Wear wear; ItemDetailPage({this.wear}); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Expanded( flex: 2, child: Padding( padding: EdgeInsets.symmetric(horizontal: 24), child: Row( children: <Widget>[ IconButton( icon: Icon(Icons.clear), onPressed: () { Navigator.pop(context); }, ), Spacer(), Text( "${wear.name}", style: TextStyle(fontSize: 20), ), Spacer(), ], ), ), ), Expanded( flex: 6, child: Container( decoration: BoxDecoration( image: DecorationImage( image: NetworkImage(wear.img), fit: BoxFit.cover, colorFilter: ColorFilter.mode(Colors.black, BlendMode.color)), ), ), ), Expanded( flex: 1, child: Padding( padding: const EdgeInsets.only(left: 24), child: Row( children: <Widget>[ Container( margin: EdgeInsets.only(right: 12), height: 4, width: 24, decoration: BoxDecoration( color: Colors.blueGrey, ), ), Container( margin: EdgeInsets.only(right: 12), height: 4, width: 24, decoration: BoxDecoration( color: Colors.grey, ), ), Container( margin: EdgeInsets.only(right: 12), height: 4, width: 24, decoration: BoxDecoration( color: Colors.grey, ), ), Container( margin: EdgeInsets.only(right: 12), height: 4, width: 24, decoration: BoxDecoration( color: Colors.grey, ), ) ], ), ), ), Expanded( flex: 1, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Text("No matter the occasion girl, get ready to edge\n" "out your suits collection and be best dressed"), ), ), Expanded( flex: 7, child: Column( children: <Widget>[ Expanded( flex: 1, child: Container( child: Row( children: <Widget>[ Expanded( flex: 10, child: Container( padding: EdgeInsets.only(left: 40), height: double.infinity, color: Colors.grey.shade200, child: Align( alignment: Alignment.centerLeft, child: Text( "Detail", style: TextStyle(fontSize: 20), )), ), ), Expanded( flex: 2, child: Container( child: Center( child: Icon( Icons.card_giftcard, color: Colors.grey.shade400, ), ), ), ) ], ), ), ), Expanded( flex: 1, child: Container( child: Row( children: <Widget>[ Expanded( flex: 10, child: Container( padding: EdgeInsets.only(left: 80), height: double.infinity, color: Colors.grey.shade400, child: Align( alignment: Alignment.centerLeft, child: Text( "Delivery", style: TextStyle(fontSize: 20), )), ), ), Expanded( flex: 2, child: Container( child: Center( child: Icon( Icons.favorite_border, color: Colors.grey.shade400, ), ), ), ) ], ), ), ), Expanded( flex: 1, child: Container( child: Row( children: <Widget>[ Expanded( flex: 10, child: Container( padding: EdgeInsets.only(left: 120), height: double.infinity, color: Colors.grey.shade600, child: Align( alignment: Alignment.centerLeft, child: Text( "Discount", style: TextStyle(fontSize: 20), )), ), ), Expanded( flex: 2, child: Container( child: Center( child: Icon( Icons.share, color: Colors.grey.shade400, ), ), ), ) ], ), ), ), Expanded( flex: 1, child: Container( child: Row( children: <Widget>[ Expanded( flex: 10, child: Container( child: Row( children: <Widget>[ Expanded( flex: 3, child: Container( color: Colors.white, child: Center( child: Text( "\$${wear.price}", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ), ), ), Expanded( flex: 8, child: Container( color: Colors.black, child: Center( child: Text( "Add to Cart", style: TextStyle( color: Colors.white, fontSize: 20), ), ), ), ) ], ), ), ), Expanded( flex: 2, child: Container( child: Center( child: Icon( Icons.more_horiz, color: Colors.grey.shade400, ), ), ), ) ], ), ), ), Expanded( flex: 1, child: Container(), ) ], ), ), ], ), ), ); } }終わりに
今回も読んでくださってありがとうございます。
- 投稿日:2020-02-16T20:36:19+09:00
Android Emulatorでnet::ERR_CLEARTEXT_NOT_PERMITTEDが出た時の対処法
WebViewでlocalhostを参照する実装をAndroid Emulatorで見る際、
net::ERR_CLEARTEXT_NOT_PERMITTED
というエラーが表示された。
これはAndroid 9(API レベル 28)からTLS(Transport Layer Security)がデフォルトとなり、HTTPプロトコル通信許が可されなくなったことが原因。
Google Developers Japan: Android P で TLS のデフォルト化によるユーザー保護対処法はいくつかあるが、今回はサンプルアプリということもあり、マニフェストファイルにHTTP通信を許可するよう
usesCleartextTraffic
の設定を追加することで回避した。AndroidManifest.xml<application ... android:usesCleartextTraffic="true" ... > </application>厳密に対応する場合は、特定ドメインのみを許可する設定などが推奨されている。
AndroidManifest.xml<?xml version="1.0" encoding="utf-8"?> <manifest ... > <application android:networkSecurityConfig="@xml/network_security_config" ... > ... </application> </manifest>res/xml/network_security_config.xml<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">secure.example.com</domain> <domain includeSubdomains="true">cdn.example.com</domain> <trust-anchors> <certificates src="@raw/trusted_roots"/> </trust-anchors> </domain-config> </network-security-config>
- 投稿日:2020-02-16T20:35:41+09:00
Android Emulatorでnet::ERR_CONNECTION_REFUSEDが出た時の対処法
WebViewでlocalhostを参照する実装をAndroid Emulatorで見る際、
net::ERR_CONNECTION_REFUSED
というエラーが表示された。
接続先は
http://localhost:3000/
を指定していたのだが、Android Emulatorは仮想環境上で実行されているため、ホストマシンで直接実行しているローカルサーバーには接続できない模様。エミュレータの各インスタンスは、仮想ルーターおよびファイアウォール サービスの背後で実行されます。この仮想ルーターおよびファイアウォール サービスによって、各インスタンスは開発マシンのネットワーク インターフェースと設定、そしてインターネットから分離されます。エミュレートしたデバイスでは、開発マシンやネットワーク上のその他のエミュレータ インスタンスは認識されず、イーサネット経由でルーター / ファイアウォールに接続されていることだけが認識されます。
各インスタンスの仮想ルーターは、10.0.2/24 ネットワーク アドレス空間を管理します。ルーターによって管理されるすべてのアドレスは、10.0.2.xx の形式を持ちます(xx は数字)。この空間内のアドレスは、エミュレータ / ルーターによって次のように事前に割り当てられます。
Android Emulator のネットワークを設定する | Android デベロッパー | Android Developers
Android Emulatorからホストマシンのローカルループバックアドレス(127.0.0.1)を参照するためには、ネットワークアドレス
10.0.2.2
を使用する必要がある。
ネットワーク アドレス 説明 10.0.2.1 ルーター / ゲートウェイ アドレス 10.0.2.2 ホスト ループバック インターフェースへの特殊エイリアス(開発マシンの 127.0.0.1 など) 10.0.2.3 1 番目の DNS サーバー 10.0.2.4 / 10.0.2.5 / 10.0.2.6 オプションの 2 番目、3 番目、4 番目の DNS サーバー(存在する場合) 10.0.2.15 エミュレートしたデバイスのネットワーク / イーサネット インターフェース 127.0.0.1 エミュレートしたデバイスのループバック インターフェース WebViewの接続先を
http://10.0.2.2:3000/
に指定することで、localhostへアクセスすることができた。
これまでAndroid Emulator上でlocalhostを参照する機会がなかったため、大変勉強になった。
- 投稿日:2020-02-16T17:52:42+09:00
ProcessLifeCycleOwnerを使ってみて
はじめに
アプリ復帰時の処理をonResumeで行うと通常のActivity遷移でも反応してしまうので困っていました。
(諸般の事情でAcitivityを多用するアプリだったので)isOnCreate のようなフラグ管理やIntent呼び出し時にフラグをつけるなど考えましたが、
いまいちスマートではなく...
そんな時、この記事を発見して、参考に取り組みました。アプリのバックグラウンド⇆フォアグラウンドを検知する。ProcessLifecycleOwnerの導入
ProcessLifecycleOwnerでアプリのバックグラウンド移行とフォアグラウンド復帰を検知する正直、前出の記事の焼き増しか劣化版になりますが、自分用の備忘録に残します。
ケーススタディ
ソースは こちら です
今回は、ProcessLifeCycleOwner + Event Bus で実装してみました。
(EventBusは便利で導入も容易ですが、若干古いかもしれないですね...)ソースの主要な部分を以下に掲載します。
App.ktclass App : Application(), LifecycleObserver { companion object { const val TAG = "App" } override fun onCreate() { super.onCreate() ProcessLifecycleOwner.get().lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun onCreateLifecycle() { // 最初の1回のみ Log.d(TAG, "ON_CREATE") } @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStartLifecycle() { // アプリ開始時 or バックグラウンドからの復帰時 Log.d(TAG, "ON_START") EventBus.getDefault().post(AppForegroundMessage()) } /// 以下、省略 }BaseActivity.ktabstract class BaseActivity : AppCompatActivity() { private val forceVersionUpLogic = ForceVersionUpLogic() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } override fun onStart() { super.onStart() EventBus.getDefault().register(this) } override fun onResume() { super.onResume() } override fun onStop() { super.onStop() EventBus.getDefault().unregister(this) } @Subscribe fun onReceiveMessage(eventMessage: AppForegroundMessage) { ApiManager.service.getVersionJson().enqueue(object : Callback<VersionModel> { override fun onFailure(call: Call<VersionModel>, t: Throwable) { Log.d( "AppForegroundMessage", "ApiManager.service.getVersionJson() onFailure", t ) } override fun onResponse(call: Call<VersionModel>, response: Response<VersionModel>) { response.body()?.let { if (forceVersionUpLogic.checkForceVersionUp(it.version, BuildConfig.VERSION_NAME )) { AlertDialog.Builder(this@BaseActivity) .setTitle("アップデート") .setMessage("アプリストアで最新のアプリをインストールしてください") .setPositiveButton("ストアへ") { _, _ -> // https://developer.android.com/distribute/marketing-tools/linking-to-google-play?hl=ja val intent = Intent( Intent.ACTION_VIEW ).apply { data = Uri.parse("https://play.google.com/store/apps/details?id=${BuildConfig.APPLICATION_ID}") } startActivity(intent) } .show() } } } }) } }設計としては単純ですが、アプリ起動・フォアグラウンド復帰時はダイアログが表示されました。
MainActivity <-> SubActivity の Activity遷移時はダイアログは表示されませんでした。MVVMなどの組み合わせが要検証ですが、Activityを複数使う場合の手段になると思います。
まとめ
ProcessLifeCycleOwnerを使うことで、
ActivityのOnResumeでのアプリ復帰処理ではないところでイベントのハンドリングができるようになります。
アプリ起動時に通信をする場合などはちょっとタイミングの調整が必要そうですが、
今後はこちらで実装を進めるのが良いと思います。追伸
最近、見つけたのですが、こちらも良記事です。
【Android】ライフサイクル(Lifecycle)アンチパターン 〜2020年版〜
- 投稿日:2020-02-16T09:40:17+09:00
Flutterアプリのライフサイクル
はじめに
Flutterアプリのライフサイクルについてまとめています。Flutterのライフサイクルというと、
- アプリ (
AppLifecycleState
) ← 今回の内容- 画面 (
StatefulWidget
)の2種類がありますが、今回は上のアプリ自体のライフサイクルについての内容です。
StatefulWidget
のライフサイクルについては、Flutter StatefulWidgetのライフサイクルにまとめていますので、参照してください。アプリのライフサイクル一覧とプラットフォームの対応関係
AppLifecycleState
の状態遷移と状態一覧を以下に示します。
基本的にプラットフォーム側の状態をFlutterアプリの状態として再定義している感じですが、iOSのライフサイクルに近いと思います。なお、iOSのライフサイクルは詳しくなく、AndroidエンジニアのためのiOSのUIViewControllerのライフサイクルとAndroidのActivityのライフサイクル比較を参考にさせていただきました。間違いがあればコメントでご指摘いただけると幸いです。
inactive
とpaused
の違いは、画面が表示されているか否かの違いの様子です。
状態 内容 Android iOS inactive アプリは表示されているが、フォーカスがあたっていない状態 onStart, onPause viewDidLoad paused アプリがバックグラウンドに遷移し(最前面に表示されてない)、入力不可な一時停止状態 onPause viewWillDisappear viewDidDisappear resumed アプリがフォアグランドに遷移し(paused状態から復帰)、復帰処理用の状態 onResume viewWillAppear viewDidAppear detached アプリが終了する時に通る終了処理用の状態 onDestroy dealloc AndroidのonSaveInstanceStateの様な仕組みはあるのか?
現状なさそうです。
AppLifecycleState
の状態を見て、必要であれば自分で対応する必要があります。状態遷移ユースケース
いくつかのユースケースで状態遷移の動作を確認してみました。
WidgetsBindingObserverを利用するとAppLifecycleStateの状態が取得出来ます。
その他、SystemChannels.lifecycle (詳細はこちらにまとめてます。) を利用しても取得可能です。確認のためのソースコード
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver { AppLifecycleState _state; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { print('state = $state'); }アプリ起動
イベント発生なし
画面回転
イベント発生なし
アプリ終了
I/flutter (13730): state = AppLifecycleState.inactive I/flutter (13730): state = AppLifecycleState.paused I/flutter (13730): state = AppLifecycleState.detachedホームボタンを押す
I/flutter (13730): state = AppLifecycleState.inactive I/flutter (13730): state = AppLifecycleState.paused画面OFF
I/flutter (15366): state = AppLifecycleState.inactive I/flutter (15366): state = AppLifecycleState.pausedアプリを履歴から復帰
I/flutter (13730): state = AppLifecycleState.resumed画面分割で起動
I/flutter (13252): state = AppLifecycleState.inactive画面分割状態でサブ画面 (下画面) のアプリを終了
I/flutter (13252): state = AppLifecycleState.inactive画面分割状態でサブ画面 (下画面) で新しいアプリを洗濯して、画面を2分割に戻る
I/flutter (13252): state = AppLifecycleState.resumed参考文献