⚠️⚠️⚠️就是跟以前不一樣的⚠️⚠️⚠️
☘️☘️☘️就是類似的或替代品☘️☘️☘️
- 可以選擇用AS或VSCode
- 要自己下載SDK⚠️⚠️⚠️
- 環境建立的詳細步驟
- 從AS就可以執行iOS模擬器了
- 就算是接實機也可以hot reload⚠️⚠️⚠️
![](https://camo.githubusercontent.com/6cf7cd4c067fc406e52a4ce24e06cef0ea1b8d1f7b9b708700bd853084494ac9/68747470733a2f2f692e696d6775722e636f6d2f6e746b617531702e706e67)
- Flutter專案裡還是有iOS跟Android資料夾
- 所以換icon基本上還是跟以前一樣的方法
- 當然你想要兩個平台用不一樣的icon也可以
- 也可以用flutter_launcher_icons方便替換
- iOS還是用LaunchScreen.storyboard☘️☘️☘️
- Android有分:
launch screens(啟動頁):等待Android初始化
splash screens(閃屏頁):等待Dart初始化
![](https://camo.githubusercontent.com/23a7c1b5151f20f7f367da42ba52c0acefc7bec87548b825b1bce5411a9e0d63/68747470733a2f2f6670312e6667687273682e6e65742f323032302f30382f31372f61646335363961616363653237323937333339653963393537366266653838612e676966)
- Flutter裡的世界裡什麼都是Widget
就先把它當成UIView吧☘️☘️☘️
- 有分StatelessWidget 跟 StatefulWidget
盡量少用StatefulWidget
除非會根據User或API改變的畫面
- 跟ViewController比較像的東西應該算是Scaffold☘️☘️☘️
- Flutter畫面不能用拉的⚠️⚠️⚠️
- 有一些部分(像是頁面跳轉)會根據不同的平台自動轉換行為
- subview現在叫child/children☘️☘️☘️
- 跳頁的語法也滿像的
Navigator.push
![](https://camo.githubusercontent.com/0763b9a9e4742b9a6f38570c84ddfc2200035e793d13222ab82421372c20e5ac/68747470733a2f2f692e696d6775722e636f6d2f634335634d73336c2e706e67)
- 關於Layout我感覺:以前像是決定要把View放在哪裏, 現在像是要把畫布分割區塊⚠️⚠️⚠️
- 很常用到Column/Row(就是UIStackView☘️☘️☘️)
- Row是水平排列, Column是垂直排列
- Expanded
只能
在Flex裡使用
- 要把物件重疊就用Stack
![](https://camo.githubusercontent.com/f0d0313ca8593f9066eadb5d6ac03e41d183f0c8f0135d9e7712ac0f90f273e5/68747470733a2f2f692e696d6775722e636f6d2f7a754d524e694e2e6a7067)
- 以前iOS沒有下面這些UI類別⚠️⚠️⚠️
- Align讓子widget對齊特定位置(九宮格)
- Padding讓子widget留邊距
- DecoratedBox裝飾子widget, 背景色或實現一些以前要在UIView.layer陰影/邊線/圓角做的事☘️☘️☘️
- SizedBox指定子widget大小
- Container是綜合上述多功能的Widget, 有時候直接用Container省得改來改去
![](https://camo.githubusercontent.com/0001e20460b576f70eae48b26dd5c30151cd90ba9c44be660cc9f5831b4cc349/68747470733a2f2f692e696d6775722e636f6d2f6d71596b49504f6c2e706e67)
- Text不是String是UILable⚠️⚠️⚠️
- Text預設會自動折行, 不用怕忘記改行數了⚠️⚠️⚠️
- TextField要透過controller物件才能控制它⚠️⚠️⚠️
Android |
iOS |
Flutter |
TextView |
UILabel |
Text |
EditText |
UITextField |
TextField |
![](https://camo.githubusercontent.com/14fe5206b1d415a6f6a9cccdd8cde27e38fb5a43750d542582f42f2bd756d5bf/68747470733a2f2f692e696d6775722e636f6d2f5131636469536a2e676966)
- 按鈕透過onPressed callback來處理點擊事件
- onPressed為null按鈕就會強制變成disable
- 以前我們在用的UIControl都可以在這裡找到
- Alert要自己呼叫Navigator.pop才會關掉⚠️⚠️⚠️
- 使用async/await才能取得Alert的結果
Android |
iOS |
Flutter |
Button |
UIButton |
RawMaterialButton |
Dialog |
UIAlertController |
AlertDialog |
![](https://camo.githubusercontent.com/07dd3be2da5eb5e7581417a5aa782ea0ed81eb6dcf7beaccd08fb99b7f71b060/68747470733a2f2f692e696d6775722e636f6d2f30664941396e542e676966)
![](https://camo.githubusercontent.com/5a12ab8ed7e7e3441e340cb782dcf69a6c0f6e3b7270e185097a6fcb504a2620/68747470733a2f2f692e696d6775722e636f6d2f4b7a5a57536a442e706e67)
- UIImageView就是Image, UIImage就是ImageProvider☘️☘️☘️
- 使用圖片路徑跟副檔名都要⚠️⚠️⚠️
- 內建讀取網路圖片功能⚠️⚠️⚠️
Android |
iOS |
Flutter |
ImageView |
UIImageView |
Image |
Bitmap |
UIImage |
ImageProvider |
ScaleType |
UIViewContentMode |
BoxFit |
![Yes](https://camo.githubusercontent.com/8fb36b6c3849c042daa1b6f7464b629519b7885111fd2b40e2e3cf855e9f283f/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f614e5532355176484b41452f302e6a7067)
- 注意showBottomSheet跟showModalBottomSheet是兩個不一樣的方法
Android |
iOS |
Flutter(Material) |
Flutter(Cupertino) |
Spinner |
UIPickerView |
showBottomSheet |
CupertinoActionSheet |
DatePickerDialog |
UIDatePicker |
showDatePicker/showTimePicker |
CupertinoDatePicker |
![](https://camo.githubusercontent.com/650001f18a310638f66c457430f7301e2cd7314f5ca43c78d192b21cac52df99/68747470733a2f2f692e696d6775722e636f6d2f6947564c4d37312e6a7067)
![](https://camo.githubusercontent.com/512ffc07bb4e27d871c519b8d0dd796b0f2cc7bc6b6a1247bc29b74af48a7856/68747470733a2f2f692e6962622e636f2f766b67486237362f35616362612d6d687479352e676966)
- TableView叫List☘️☘️☘️
- cell叫ListTile☘️☘️☘️
- 可以橫的⚠️⚠️⚠️
- 也是要靠scrollController
- 有children(一次全建) 跟 builder(要顯示才建立)兩種建立方式
Android |
iOS |
Flutter |
ListView |
UITableView static cell |
ListView use children |
RecycleView |
UITableView dynamic cell |
ListView.builder |
![](https://camo.githubusercontent.com/42ecb90584b16337218220931386f3e41d8ca950ddfbaafd662d785d11e980da/68747470733a2f2f692e696d6775722e636f6d2f61656e397074372e676966)
![](https://camo.githubusercontent.com/029502e7b12d347ff57ee179dc9f13e8158d30ade97b33d5c3efe14069098312/68747470733a2f2f692e696d6775722e636f6d2f563170586b676f2e676966)
- children方式建立的ListView其TextField上的資料還是會跑掉
- 用ExpansionTile就可以做到folding效果
![](https://camo.githubusercontent.com/01079313d37656920e5a71e447eb6acaa45a7911d5a9a60786402e2c7f543434/68747470733a2f2f692e696d6775722e636f6d2f4c726b554f73722e676966)
![](https://camo.githubusercontent.com/7894fdec0589694c87755c959f556abb264769485d58b2e391bfe3cd5746f92f/68747470733a2f2f692e696d6775722e636f6d2f794c4e3273336d6c2e706e67)
- 直接幫你算好cell大小(爽爽的)⚠️⚠️⚠️
- 但是不能paging scroll⚠️⚠️⚠️
Android |
iOS |
Flutter |
RecycleView(set GridLayoutManager) |
UICollectionView |
GridView |
![](https://camo.githubusercontent.com/a2bb90bd138b7fee6212e1e338d298c5a931c6cfa7c7deec2da3c223befe5e62/68747470733a2f2f692e696d6775722e636f6d2f4c6f6f504974462e676966)
- CustomScrollView就像CompositionalLayout☘️☘️☘️
- CustomScrollView裡面一定要用Sliver Widget
- CupertinoSliverRefreshControl就是在CustomScrollView使用
![](https://camo.githubusercontent.com/580dee129435496dbde01ada9b5263994b244b61a8dff52bd7734466887b9423/68747470733a2f2f692e696d6775722e636f6d2f414872757157502e676966)
- BottomNavigationBar這個是TabBar☘️☘️☘️
- TabController這個不是TabBar...在iOS我不知道叫什麼, 就是那個可以用手勢換頁的
- BottomNavigationBar有分fixed 跟 shifting兩種Type
- 注意BottomNavigation超過三個就會轉成shifting
- BottomNavigation的換頁必須自己管理
Android |
iOS |
Flutter |
BottomNavigationView |
UITabBarController |
BottomNavigationBar |
TabbedActivity |
看套件叫什麼它就叫什麼 |
TabController |
![](https://camo.githubusercontent.com/959e13c384d5f8f15486837ed97f321aa93f9702ea528507a58cbc8a62c63e63/68747470733a2f2f692e696d6775722e636f6d2f7a3764576273782e676966)
- 唔...這天沒什麼重點XD, 就介紹一下怎麼轉Model跟打API
![](https://camo.githubusercontent.com/1d36cbe45f40f04f862d89d7459bc8a947d192232f2433031d3e053d0a8bd0a0/68747470733a2f2f692e696d6775722e636f6d2f727552735030672e676966)
- Flutter內建的
HttpClient
功能比較陽春, POST好像只支援application/json
- 完整一點的可以使用官方出的套件
http
- 或是第三方的Dio
![][https://i.imgur.com/lCg6cWh.gif]
- Widget生命週期跟iOS/Android不太一樣, 沒有will/did相關的
- App生命週期要widget透過WidgetsBindingObserver自己監聽
iOS |
Android |
Flutter |
init |
onCreate |
createState |
viewDidLoad |
|
initState |
viewWillAppear |
onStart |
這個沒有真的滿傷的⚠️⚠️⚠️ |
viewDidLayoutSubviews |
|
build |
viewDidAppear |
onResume |
|
viewWillDisappear |
onPause |
|
viewDidDisappear |
onStop |
|
removeFromSuperview |
|
deactivate |
deinit |
onDestroy |
dispose |
WillEnterForeground |
onRestart |
|
DidBecomeActive |
onStart |
resumed |
WillResignActive |
onPause |
這個沒有對金融業也滿傷的⚠️⚠️⚠️ |
DidEnterBackground |
onStop |
paused |
![](https://camo.githubusercontent.com/52cdf92b6ef722ee490f8dc45ee786538233e5429dd8c56cd1f38cfd0678e724/68747470733a2f2f692e696d6775722e636f6d2f76365675485a572e676966)
- UI樹一層包一層, 若底層的widget若想取得上層的state會很麻煩
- InheritedWidget讓底層widget可以方便取得上層的state
- 類似Singleton的作用☘️☘️☘️
- InheritedWidget更新了就會呼叫didChangeDependencies
![](https://camo.githubusercontent.com/345a422e7cc378c62697fcf2e3e1bf6c3769d11a5a4f4f43bab346e42654d69e/68747470733a2f2f692e696d6775722e636f6d2f59664d693962372e676966)
- 就是進階版的InheritedWidget套件
- 可以做到以前NotificationCenter的效果☘️☘️☘️
- 如果要做到push到2ndVC時TabBar還在
就要Scaffold/TabBar/TabView整套換成Cupertino
- 如果要做到push到2ndVC時點TabBarItem可以popToRootVC
就要用navigatorKey去找到navigator再去
popUntil
![](https://camo.githubusercontent.com/c17af5f7eaf05f14ab9847e1c05d93b55dad5da80fd724a469639cb7fd1fa3b9/68747470733a2f2f692e696d6775722e636f6d2f68576b4336594c2e676966)
- 也是類似NotificationCenter☘️☘️☘️
- 只是現在方向由下往上
- 畫這個金字塔比實作Notification麻煩XD
![](https://camo.githubusercontent.com/960d51b16055c2d102960814656f87d868166627f41d223d705d1b28d9c02b08/68747470733a2f2f692e696d6775722e636f6d2f38365433744e552e676966)
![Yes](https://camo.githubusercontent.com/463f4422a09607c01a66f6ee633b465f4fd62adaa813af5021632f331cd2224b/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f44717a7156565f4d5371772f302e6a7067)
![](https://camo.githubusercontent.com/5ffb11f0307f9cd3119543d54c47bd4d5a4b1729048da5622174eeacb339a014/68747470733a2f2f692e696d6775722e636f6d2f6930756c5850792e676966)
![](https://camo.githubusercontent.com/70e5afb87f02ef831bb1e258c5576c5ea7318a9771a8c2d27efdfb87be014e14/68747470733a2f2f692e696d6775722e636f6d2f416132506436782e676966)
![Yes](https://camo.githubusercontent.com/9783282cc752276da0925802e80fcdf86bfb998c81b604e190ce06af8613863a/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f6d4e535637504857766c492f302e6a7067)
![](https://camo.githubusercontent.com/6b714c25aa113f0e10fbf28dc81acaa83ae56392a8d00b6e8f44c54849437d86/68747470733a2f2f692e696d6775722e636f6d2f4f6a6f713248432e676966)
- 要用Google Maps要先去CGP上面申請API Key
- 有提供my location button
- 要注意要求定位權限的時機要晚一點, 才不會卡在過場動畫
![](https://camo.githubusercontent.com/fcfbd376c2da6fae2685e3b26fd8fd2cf8ec0421eb09dd322e3180d3aeae5bb4/68747470733a2f2f692e696d6775722e636f6d2f347a4d447263596c2e706e67)
- 體驗
Zonble
大大有趣的side project
- 詳細介紹可以看看他本人寫的這篇Medium