Flutter學(xué)習(xí)筆記(coderwhy)

邂逅FLutter

  • 萬物皆是Widget

一般縮進2個空格

文字居中 Widget Center()

MaterialApp使用Meterial風(fēng)格

  • Scaffold腳手架 幫助我們快速的搭建頁面
    • 傳appBar body

剛開始分不清那里寫, 那里寫;
一條語句結(jié)束之后寫的是;
屬性直接用,

  • 快捷鍵ctrl + R 啟動

Widget:

  • StatefulWidget有狀態(tài)的Widget: 在運行過程中有一些狀態(tài)(data)需要改變
  • StatelessWidget 內(nèi)容是確定沒有狀態(tài)(data)的改變

先用StatelessWidget

  • build方法
  • build方法什么時候被執(zhí)行?

重構(gòu)
一行代碼, 箭頭函數(shù)

  1. 最終形成的是Widget樹
  2. 組件化開發(fā)的思想 跟VUE乖菱、React很像
    VUE疾忍、React: 樣式和結(jié)構(gòu)進行分離
    代碼規(guī)范, 劃分清晰

Center居中

  • Row相對于Center是居中的
  • Row的內(nèi)容還要居中

CheckBox
@required: 必須的, 注解告訴某些可選命名參數(shù)是必須的, Flutter中才有
命名可選參數(shù)對順序沒有要求, 可忽略順序

點擊的時候發(fā)生改變
var flag = true;
本身就是錯誤的代碼
This class immutable不可變的類
@immutable關(guān)鍵字, 告訴當(dāng)前這個類一旦創(chuàng)建后定義的所有的東西都是不可變的使用final
StatelessWidget: 無狀態(tài)的Widget
StatefullWidget:

flag: 狀態(tài)
android/iOS 命令式變成, 不說狀態(tài), 屬性、數(shù)據(jù)
VUE... 聲明式變成 管好狀態(tài)
flutter開發(fā)中, 所有的Widget都不能定義狀態(tài)
StatefulWidget是Widget也不能定義狀態(tài) -> 創(chuàng)建一個單獨的類, 這個類負(fù)責(zé)管理狀態(tài)
抽象方法必須實現(xiàn)

沒有命名沖突, this可以省略
setState(){};回調(diào)函數(shù)中改變狀態(tài), 監(jiān)聽改變, 刷新界面
React虛擬DOM
當(dāng)前的State里面繼承過來的 本質(zhì)是this.state()

20.3.2

Flutter借鑒了很多React的思想
構(gòu)造方法傳入很多的參數(shù)
StatelessWidget案例
StatefulWidget案例
垂直排布Column
所有的數(shù)據(jù)暫時都是寫死的
child, 單個
children, 多個
展示的數(shù)據(jù)是不一樣的 -> 需要傳過來一些參數(shù)
Widget中定義的所有成員變量應(yīng)該是final的
顯示網(wǎng)絡(luò)圖片, 異步加載, 有專門的IO線程 Image.network(imageURL)
下面顯示一個黃條, Flutter里面的重點
黃條: 在Fultter里面, 內(nèi)容超出了屏幕的可顯示區(qū)域,
而你又沒有設(shè)置父Widget滾動, 就會顯示
安全區(qū)域 relayoutBoundary布局邊界
Flutter中的布局跟iOS紧显、Android不一樣
解決辦法, 超出后變成可滾動的Widget
ListView, 傳入children
變量放在不同的地方, 不同的含義, build執(zhí)行的時候就會創(chuàng)建變量

希望有間距
Flutter, 中間插入組件, 有很多種辦法
SizedBox(height: 8), 相當(dāng)于8個像素, 像素適配rpx, 相當(dāng)于iOS中的點像素

邊框
給Column包裹一個Container
decoration 裝飾
BoxDecoration boder
Border.all(
width,
color
)

內(nèi)容都是自動居中顯示
不想讓居中顯示,
文本 textAlignLTextAlign.right 不起作用
不是因為文本, 包裹內(nèi)容, 只有那么大
Column, 主軸上面垂直排布,
交叉軸 crossAxisAlignment
flex布局
Column、Row捌显、Flex
Column垂直方向就是一個主軸
Row水平方向是主軸
Flex決定主軸和交叉軸

StatefulWidget做個案例

計數(shù)器案例, 有狀態(tài)的改變 -> StatefulWidget
抽象方法createState返回StatefulWidget
做開頭, 在其他庫不能訪問
Widget是不加下劃線
, 暴露給別人使用
State是加下劃線_: 狀態(tài)這個類只是給Widget使用, 自己使用

為什么Flutter在設(shè)計的時候StatefulWidget的build方法放在State中

  1. build出來的Widget是需要以來State中的變量(狀態(tài)/數(shù)據(jù))
  2. 在Flutter的運行過程中, Widget是不斷地銷毀和創(chuàng)建的,
    當(dāng)我們自己的狀態(tài)改變時, 并不希望重新創(chuàng)建一個新的State

Widget相當(dāng)于描述信息

child, children
只能放一個元素, 放數(shù)組多個元素

抽取方法, 返回Widget

Column占據(jù)的是屏幕整個高度, 設(shè)置主軸居中

_counter+=放的位置,
看setState的實現(xiàn)
提前兩三行或延后兩三行代碼執(zhí)行

可以管理自己的狀態(tài)

全局變量, 整個應(yīng)用程序全部使用StatelessWidget來構(gòu)建
不合理, 全局狀態(tài)變得非常龐大, 耦合度太大
屬于自己的狀態(tài), 自己管理

StatefulWidget使用父Widget傳來的數(shù)據(jù)
this.widget
普通情況下this都可以省略, 有沖突的時候不可以省略

生命周期

hook回調(diào), 鉤子函數(shù), 某一刻調(diào)用這個函數(shù)
生命周期的函數(shù)對于開發(fā)者意義是什么?

  1. 初始化一些數(shù)據(jù), 變量, 狀態(tài)
  2. 發(fā)送網(wǎng)絡(luò)請求, 要知道widget什么時候創(chuàng)建完了創(chuàng)建網(wǎng)絡(luò)請求
  3. 進行一些事件的監(jiān)聽, controller添加監(jiān)聽事件
  4. 管理內(nèi)存: 一些定時器笛求、controller手動進行銷毀

Flutter里面只需要監(jiān)聽Widget的生命周期

StatelessWidget的生命周期

StatelessWidget只需要監(jiān)聽構(gòu)造函數(shù)、build方法被調(diào)用, 很少監(jiān)聽
調(diào)用了2次, AndroidStudio的bug?
VSCode運行只會調(diào)一次

StatefulWidget的生命周期

開發(fā)中用的最多的就是StatefulWidget的生命周期
不是一個類, 快捷stful
生成兩個類

  1. Stateful Widget Widget.createState()
  2. State object
  • constructor ...-> initState ...-> build ...-> dispose

init的super必須調(diào)用
初始化
不調(diào)會報警告, 注解限制 @mustCallSuper

setState做一個標(biāo)記, 重新顯示
小部件銷毀和重建的過程

didChangeDependence

點擊底部加號, 添加HYHomeContent, 讓父Widget結(jié)構(gòu)發(fā)生改變

final指向的引用不能改變, 里面是可以增加數(shù)據(jù)的

生命周期復(fù)雜版

dirty sate markNeedsBuild()
setSate中dirty false改成true build執(zhí)行

20.3.4 三

  1. MaterialApp home可以是StatefulWidget

  2. StatelessWidget改成StatefulWidget

  1. 將build出來的widget抽取到一個單獨的Widget中
  1. JHHomeContent打印了很多的生命周期函數(shù)
    官網(wǎng)文檔
    api.flutter.dev/flutter/widgets/State-class.html
    didUpdateWidget 8:10-8:22 網(wǎng)絡(luò)不好

  2. 在Android Studio里面運行Flutter項目多打印
    VSCode中不會執(zhí)行兩次

二. Flutter的編程范式

  1. 命令式編程:一步一步給計算機命令, 告訴它我們想做什么事情
    一步一步設(shè)置frame
  2. 聲明式編程: 描述目標(biāo)應(yīng)該長什么樣子
    依賴一些框架: Vue React Flutter
    配置信息
    Column告訴框架垂直排布
    特殊的地方寫好數(shù)據(jù)

前端的編程范式

jquery导披、原生JS 命令式編程
VueJS 聲明式編程
vue、react埃唯、angular 聲明式編程
2009年開始, 聲明式編程就開始流行起來
SwiftUI, 蘋果開發(fā)模式轉(zhuǎn)向聲明式編程
聲明式編程更簡單, 邏輯更清晰
慢慢來體會

基礎(chǔ)的Widget

1.1 文本W(wǎng)idget

Text() 不能設(shè)置寬度
不居中是因為沒有不存在寬度
maxLines: 2, 最大行數(shù)
overflow: TextOverflow.ellipsis, 最后顯示...
textScaleFactor: 1.5, 縮放1.5倍
TextStyle

1.2 富文本顯示

Text Widget -> RenderWidget 不是最終渲染的Widget
RichText widgets require a Directionality widget ancestor.
最終渲染的是build出來的東西
RichText extends MultiChildRenderObjectWidget
開發(fā)中: 富文本

Text.rich(
    TextSpan(

    )
)

咸魚boost_flutter框架

2.1按鈕的基礎(chǔ)

RaisedButton凸起的Button
必傳參數(shù)和@required: 必傳參數(shù)不穿就會報錯(編譯不通過)
@required編譯可以通過, 但是會報警告
onPressed: () {}, 不傳是disabled
兩種辦法改變顏色

FlatButton, 扁平的按鈕

OutlineButton, 邊框按鈕

FloatingActionButton, 懸浮的按鈕, 一直浮著

既有圖標(biāo)又有文本的按鈕, 自定義Button
FlatButton(
child: Row(
children:
)
)
FlatButton為什么占據(jù)整行, Row占據(jù)整行
mainAxisSize: MainAxisSize.min,
Column主軸, 交叉軸
圓角: ShapeBorder 抽象類
RoundedRectangleBorder(
borderRadius
)

MaterialButton
Button也不是一個直接渲染的Widget

三. 圖片Widget

Image.network/asset
ImageProvider 抽象類, 看看有哪些實現(xiàn)類
AssetImage
NetworkImage
抽象類在什么情況下實例化? 有工廠方法的時候, 工廠方法里面去調(diào)用另一個子類的東西
width: 200, 設(shè)置圖片寬度
height: 200, 設(shè)置成200, 看起來不是200, 填充模式
fit: BoxFit.fill,
fitWidth 寬度一定, 高度自適應(yīng)
fitHeight 高度一定, 寬度自適應(yīng)
alignment: Alignment.centerLeft, 對齊
Alignment(x, y) 左上角(-1, -1), 右下角(1, 1) 中心(0, 0)
(0, -2), 跑出去了
color: 顏色混入
fit 和 repeat一起使用, 重復(fù)填充

加載本地圖片
image: AssetImage("本地圖片地址"),

  1. 在Flutter項目中創(chuàng)建一個文件夾, 存儲圖片
  2. 在pubspec.yaml進行配置
    assets:前的空格要刪除
    要執(zhí)行Packages get
  3. 使用圖片

assets文件夾
iamges文件夾
fonts
iOS圖片類型怎么區(qū)分?
@1x放在外面
2.0x文件夾
3.0x文件夾

44行 注釋打開, 空格刪除
assets:
- assets/images/juren.jpeg
- assets/2.0x/juren.jpeg
- assets/3.0x/juren.jpeg
執(zhí)行 flutter packeges get 依賴重新安裝一下
沒有配置2x圖片就刪除依賴

  • assets/images/ 通用, 加載里面所有的資源

20.3.6 五

Flutter三層樹結(jié)構(gòu)
Element
知識內(nèi)容

  1. Button-Image-TextField
    Button: 小知識點的補充
    Button的小間距, 沒有完全放在左上角
    默認(rèn)有間距, 屬性 MaterialTapTargetSize 默認(rèn)48 垂直方向
    設(shè)置成緊縮, shrinkWrap
    Column中心對齊 文本1和2占據(jù)的空間不一樣
    默認(rèn)情況下Button上下有一定的間距

沒有內(nèi)容的時候Button有尺寸, Button如何變小
文檔看默認(rèn)值
Flat buttons have a minimum size of 88 36
繼承自 MaterialButton build方法
上下文包裹Button
ButtonTheme(
minWidth: 30,//最小寬度
height: 30
)

去除Button的內(nèi)邊距

Image補充2個知識點

  1. 占位圖的問題
    FadeInImage默認(rèn)淡入淡出效果
    milliseconds傳0會報錯, 傳1

  2. 圖片緩存問題
    flutter默認(rèn)會對圖片進行緩存
    圖片和縮放都一致的時候, 直接使用之前的圖片
    最多緩存1000張圖片, 最大100M
    iOS中內(nèi)存占用內(nèi)存過大, iPhone直接殺死App

Icon的補充
IconData
Icon字體圖標(biāo)和圖片圖標(biāo)

  1. 字體圖標(biāo)矢量圖(放大的時候不會失真) size: 300
  2. 字體可以設(shè)置顏色
  3. 圖標(biāo)很多時, 占據(jù)的空間會更小
    自己來創(chuàng)建IconData, 0xe91d 傳進來的是16進制數(shù)字
    Text("0xe91d"), 不能顯示圖標(biāo)?
  • 0xe91d -> unicode編碼 \ue91d
  • 設(shè)置對應(yīng)的字體 fontFamily

TextField
Dart中所有類繼承自O(shè)bject
Material風(fēng)格

點擊登錄獲取輸入框里的東西
SizedBox調(diào)整間距
怎么設(shè)置按鈕的寬度和高度? 沒有width和height
Container包裹按鈕, 按鈕子元素填充Container

  1. 獲取用戶名和密碼
    聲明式編程, 很少專門搞一個引用 => Controller
    全局變量里面 usernameTextEditController
    TextField的controller屬性
    邊框顏色 Theme()包裹, child中設(shè)置TextField data:

Color white 顏色常量
Color()xff ff ff ff)
Color.fromRGBO()
Colors.red[100], 為什么有[], => 運算符沖在 ColorSwatch中

FlatButton設(shè)置顏色沒用

葉子Widget
所有的Widget最終形成一個樹
LeafRenderObject

2. 布局Widget

官方文檔, 布局組件特別多
Flutter很少用Rect設(shè)置位置

Align

Center的源碼 常量構(gòu)造方法 傳給的所有屬性都傳給了父類Align, 沒有做任何的改變
為什么搞個Align就居中了?
Align是占據(jù)屏幕整個區(qū)域的
單子組件
alignment: Alignment(0, 0)中心點 左上角-1, -1, 支持小數(shù)
Container包裹Align, Align設(shè)置widthFactory沒反應(yīng)
widthFactory child的size倍數(shù)

Center相當(dāng)于不設(shè)置AlignAlignment的值

Padding

Dart2.0+ const可以省略
文字行高的概念
EdgeInsets.all()全部加內(nèi)邊距
EdgeInsets.symmetric(vertical: 5)
EdgeInsets.only(bottom: 10)

Container

child是多大Container就是多大
Flutter的布局方式, 最終形成的是一個樹結(jié)構(gòu)
RenderWidget(父Widget) -> RenderObject
constraints: 約束 BoxConstraints(minWidth: maxWidth ...) 傳遞給子組件
子組件根據(jù)父組件給的約束調(diào)整自己的大小, 之后報告給父組件調(diào)整大小, 超出會出現(xiàn)黃色警告
不同的子組件有不同的表現(xiàn)
alignment
padding設(shè)置邊距
margin: EdgeInsets.all(10) 外邊距
transform: Matrix4.rotationZ(50) 旋轉(zhuǎn) 工廠構(gòu)造方法

decoration: BoxDecoration() 跟color屬性沖突, 只能提供一個
提供一個color的時候本質(zhì)是在創(chuàng)建decoration
BoxDecoration(
color: ,
border: Border.all(
width:
color
),
borderRadius: BorderRadius.circular(50),//設(shè)置圓角
boxShadow: [
BoxShadow(color: Colors.blue, offset: Offset(10, 10), spreadRadius),
BoxShadow(color: Colors.red, offset: Offset(10, 10), spreadRadius)
]
)

Container是很多組件的大雜燴

如果沒有設(shè)置alignment,

20.3.9 一

多子布局組件

2.1 Flex組件

Flex Widget
Row和Column繼承自Flex
Flex: CSS Flex布局
必傳參數(shù)direction
direction: Axis.horizontal: Row
direction: Axis.vertical: Column
開發(fā)中直接使用Flex比較少
多直接使用Column撩匕、Row

主軸和交叉軸的概念

主軸: Main Axis
交叉軸: Cross Axis
剩余的間距平均分給Row的子組件
以前用浮動float來做 -> Flex布局

mainAxisAlignment: 6個值
start(默認(rèn)): 沿著主軸的開始位置, 緊挨著排每一個組件
end: 沿著主軸的結(jié)束位置, 挨著擺放組件
center: 主軸的中心點對齊
spaceBetween: 將左右兩面的間距為0, 其他組件之間平分間距
spaceAround: 左右兩邊的間距, 是其他組件之間的間距的一半
spaceEvenly: 所有的間距平分空間

Row的特點: 水平方向盡可能占據(jù)比較大的空間, 垂直方向包裹內(nèi)容
水平方向包裹內(nèi)容
-> 設(shè)置mainAxisSize = min; (默認(rèn)max)
設(shè)置后就沒有剩余空間

CrossAxisAlignment:
start: 交叉軸起始位置對齊
end: 交叉軸的結(jié)束位置對齊
center(默認(rèn)值): 中心點對齊

baseline: 基線對齊, (必須有文本的時候才起效果)
文字的排版, 文字的行距, 文字排版有很多的線, 頂線/底線, 之間是行高
line-height: 行高高于文字的高度text-height
(line-height - text-height) / 2
vertical-align:
文字下沉, x最底部的線是基線, 不是底線 alphabetic ideographic

stretch: 先讓Row占據(jù)交叉軸盡可能大的空間, 將所有的子組件交叉軸的高度, 拉伸到最大
用Container包裹一個Row限制最大拉伸高度

Column
verticalDirection VerticalDirection.up, 從下到上排
垂直方向上很少用基線對齊

textDirection: TextDirection.rtl,閱讀方向改成從右往左, 這個屬性用的少

需求: 剩余的寬度全部分給第一個紅色的組件

  1. Expanded
    包裹一個Flexible, 里面的屬性flex
    fit: FlexFit.tight, 默認(rèn)loose
    多個Flexible, 會預(yù)留一些空間
    比例的問題, 包裹的組件的寬度一致, 等分
    flex: 1, 只和flex有關(guān)系, 跟寬度沒有關(guān)系

Expanded(用的更多) -> Flexible(fit: FlexFit.tight)
超出區(qū)域出現(xiàn)黃色條狀警告
用Expanded包裹, 避免警告

2.3 Stack組件

默認(rèn)情況下, 不會出現(xiàn)組件之間的重疊
Stack默認(rèn)的大小是包裹內(nèi)容
alignment: AlignmentDirectional.center,// 從什么位置開始排布所有的子Widget
fit: StackFit.expand, // 默認(rèn)loose 很少用 將子組件拉伸到盡可能大
overflow: Overflow.visible, 超出部分如何處理 超出可顯示
Positioned 布局Widget

hasSize錯誤

20.3.11 三

點擊按鈕, Icon變成紅色
StatelessWidget -> StatefulWidget
變量做記錄, 真實開發(fā)中用模型來記錄防止影響其他數(shù)據(jù)
setState() {

}
讓界面刷新
能用StatelessWidget盡量使用StatelessWidget

滾動的Widget

  1. ListView 列表
  2. GridView 網(wǎng)格, 九宮格
  3. sliver 分片 本質(zhì)上是Sliver
  4. 滾動的監(jiān)聽

先不講JSON的解析

ListView組件

  • 默認(rèn)構(gòu)造方法: ListView() children比較少的時候使用, 一次創(chuàng)建
  • ListView.builder() 即將展示的時候創(chuàng)建
  • ListView.separated()
  • ListView.custom() 需要傳入代理

List.generate(100, generator()) List的構(gòu)造函數(shù)
Flutter提供的 ListTile
總高度超過界面高度, 自動滾動
Flutter實現(xiàn)滾動, 需要包裹ListView
水平滾動需要確定Item寬度 itemExtent 范圍(高度/寬度)
reverse: true, 反轉(zhuǎn)

ListView.builder()
itemBuilder: () {
}

ListView.separated()
separatorBuilder: () {}
Divider 分割線
一般傳入回調(diào)函數(shù)的時候, 都是等到需要的時候自動調(diào)用回調(diào)函數(shù)

GridView

  • GridView()
  • GridView.count()
  • GridView.extent()
  • GridView.builder()

gridDelegate: ,必傳參數(shù)
SliverGridDelegateWithFixedCrossAxisCount
固定的個數(shù), 寬度根據(jù)不同的屏幕改變
Random().nextInt(256) 隨機數(shù)
高度怎么確定?
childAspectRatio: ,寬高比
crossAxisSpacing: 8, 交叉軸間距
mainAxisSpacing: 8, 主軸間距
兩邊緣間距, 包裹一個Padding
padding: EdgeInsets.symmetric(horizontal:8)

傳入寬度, 不同的屏幕放的個數(shù)不同,
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100, 最大寬度, 414 100放5個

)
Item

count()

sliver

學(xué)習(xí)方法
ListView, GridView -> buildSliver抽象方法, 子類實現(xiàn)
extends BoxScrollView 不是Widget就是一個普通的類
extends ScrollView
extends
看build方法 返回了一個Scrollable
繼承關(guān)系理清楚
本質(zhì)是sliver

CustomScrollView()
slivers
dart/math包 隨機數(shù)
安全區(qū)域 劉海SafeArea包裹
Sliver的安全區(qū)域 SliverSafeArea() 默認(rèn)情況安全區(qū)域內(nèi)顯示, 可以滾動到劉海

內(nèi)邊距出現(xiàn)了覆蓋, 不想要這個效果
不用Padding -> SliverPadding
SliverAppBar

滾動的監(jiān)聽

兩種方式可以監(jiān)聽:
controller:
1. 可以設(shè)置默認(rèn)值offset
2. 監(jiān)聽滾動, 也可以監(jiān)聽滾動的位置
NotificationListener
1. 開始滾動和結(jié)束滾動
ScrollController

作業(yè):
Body的Widget抽取了, 監(jiān)聽的東西

20.3.13

異步
網(wǎng)絡(luò)請求
越來越流行的異步處理方式
JS、Dart 單線程+事件循環(huán)
一個應(yīng)用程序大部分時間都是處于空閑的狀態(tài), 并不是無限制的在和用戶進行交互
非阻塞式調(diào)用
操作系統(tǒng) 文件操作

  • 阻塞式調(diào)用 非阻塞式調(diào)用
    并不會等待結(jié)果, 直接返回, 不阻塞當(dāng)前線程
    維護了"事件循環(huán)", 將需要處理的一系列事件, 放到一個Event queue中

對Future認(rèn)識

發(fā)送一個網(wǎng)絡(luò)請求
import 'dart:io';
sleep();

Future(){
//耗時操作的代碼
return "結(jié)果";
}
抽象類看工廠構(gòu)造方法
聯(lián)合類型

Future的then函數(shù)
沒有指定類型的時候是dynamic
then后面的回調(diào)函數(shù)什么時候被執(zhí)行?
需要在Future(函數(shù))有結(jié)果, 財智星下面的回調(diào)函數(shù)
Future源碼里面是各種各樣的回調(diào)

  1. 只要有返回結(jié)果, 那么就執(zhí)行Future對應(yīng)的then的回調(diào)(Promise - resolve)
  2. 如果沒有結(jié)果返回(有錯誤信息), 需要在Future回調(diào)中拋出一個異常(Promise - reject)
    throw Exception;
    拋出異常, 沒有處理異常, 程序報錯
    catchError((err){})
    代碼執(zhí)行完成
    whenComplete((){})

鏈?zhǔn)秸{(diào)用
寫開不可以, 程序崩潰

Future的鏈?zhǔn)秸{(diào)用

中間發(fā)生了異常
鏈?zhǔn)秸{(diào)用解決"回調(diào)地獄"

Future的其它API

Future.value('哈哈哈').then()(res) {
print(res);
};
Future.error('錯誤信息')

延遲執(zhí)行
delayed()

關(guān)鍵字 await墨叛、async

同步的代碼格式去實現(xiàn)異步的調(diào)用過程

解決兩個問題:

  1. await必須在async函數(shù)中才能使用
  2. async函數(shù)返回的結(jié)果必須是一個Future

語法糖, 做了一個包裹

sleep操作, 即使是Future也會做一個等待

拿到第一次的結(jié)果, 做參數(shù)拿到第二次結(jié)果
模擬鏈?zhǔn)秸{(diào)用 -> async止毕、await

Dart的異步補充

import 'rootBundle'
多核CPU的利用
一般前端不要處理太多非常復(fù)雜的數(shù)據(jù), 交給后端處理
后端處理邏輯只要寫一次各端都能用

Dart開啟另一個線程
Isolate(隔離)的概念
Root Isolate
每個Isolate都有自己的Event Loop 和Queue

創(chuàng)建Isolate

Isolate.spawn(calc, 100);
把函數(shù)傳進去 函數(shù)的參數(shù)
在一個新的線程中執(zhí)行

Isolate之間的通信

雙向通信

  1. 創(chuàng)建管道
  2. 創(chuàng)建Isolate
  3. 監(jiān)聽管道

Dart主要是單線程的,
Flutter是單線程的嗎? 至少是4個線程
UI Runner
GPU Runner
IO Runner
Platform Runner

網(wǎng)絡(luò)請求

pub.dev搜索dio庫
所有規(guī)范的第三方庫, 都有一個主文件, 做導(dǎo)入

  1. 創(chuàng)建dio對象
  2. 發(fā)送網(wǎng)絡(luò)請求

真實開發(fā)中

  1. 參數(shù) - 攔截器 (對網(wǎng)絡(luò)請求進行封裝)
  2. 在開發(fā)中只要用到第三方庫, 建議大家都進行一層封裝

考慮到換庫
20個Widget都需要換?
iOS 網(wǎng)絡(luò)請求ASI庫不更新 -> AFN
HttpTools依賴第三方庫

dart文件一般用_進行分割
類名用駝峰
命名可選參數(shù), 默認(rèn)get
Flutter的習(xí)慣, 不直接搞常量, 創(chuàng)建一個類, 里面放常量
生產(chǎn)環(huán)境/開發(fā)環(huán)境

全局?jǐn)r截器
dio.interceptprs.addAll

dependencies:
dev_dependencies: 開發(fā)時依賴, 不會打包進去

20.3.16 一

豆瓣電影列表界面

  1. 對前面內(nèi)容的回顧
  2. 對一些Widget, 其他知識點補充
  3. 代碼的結(jié)構(gòu)如何進行組織

異步compute函數(shù)執(zhí)行報錯
原因: compute接收的回調(diào)函數(shù)要是全局的, 不是對象的方法
沒有對象, 不能執(zhí)行對象方法

啟動文件main.dart配置
Edit Config...

豆瓣兩個難點, 先做封裝

  1. 評分五顆星顯示
  2. Flutter邊框不支持虛線
    抽成獨立的組件, 以后也可以復(fù)用

1. 評分Widget的封裝

具備通用性的特點
評分控制星數(shù)
星的顏色
星的大小

狀態(tài)有可能發(fā)生變化, 用StatefulWidget

如何封裝的呢?
絕對定位
Stack, 元素可以重疊
搞2個Widget橫向重疊 => Row
按比例裁剪

需要傳過來的參數(shù)
分?jǐn)?shù)(必傳) rating
滿分(可以給默認(rèn)值) maxRating
展示多少個星(可默認(rèn)) count
星的大小(可默認(rèn)) size
未選中顏色(可默認(rèn)) unselectedColor
選中顏色 selectedColor

顏色16進制 Color(0xffbbbbbb)
默認(rèn)值const

List.generate(widget.count, )
buildUnselectedStar()

  1. 創(chuàng)建stars

  2. 構(gòu)建滿填充的star
    滿的星
    向下取整 floor
    向上取整 ceiling

  3. 構(gòu)建部分填充star
    ClipRect()
    自定義子類
    抽象方法, 子類必須實現(xiàn)
    當(dāng)成一個矩形裁剪
    getClip()

要不要重新裁剪, 寬度不一致在重新裁剪
shouldReclip()

如果傳入圖片使用傳入圖片, 沒有傳入使用默認(rèn)圖片 ??

完成功能和復(fù)用性

創(chuàng)建dart文件用下劃線命名
star_rating.dart

虛線的封裝

實現(xiàn)目標(biāo)
提供定制
確定虛線的方向
虛線的寬度
虛線的高度
多少個虛線, 密度
虛線的顏色

只是負(fù)責(zé)展示的 => StatelessWidget

DashedLine
final Axis axis; 必須傳或給個默認(rèn)值
final double dashWidth;
final double dashHeight;
final int count;
final Color color;

SizeBox來做

用不上的用_替代

默認(rèn)情況下SizeBox沒有color屬性
寫個child: DecoratedBox
decoration: BoxDecoration(color: color)
)

TabBar實現(xiàn)說明

分析:
home屬性直接改不合適
Scaffold: body -> IndexedStack
切換就是一個狀態(tài)改變 -> StatefulWidget

重啟重新打包時會重新打包資源

代碼抽取

  1. 抽成一個函數(shù)
    Widget buildBottomItem() {}

  2. 抽成一個類
    items有具體的要求
    class JHBottomBarItem extends BottomNavigationBarItem {

}
創(chuàng)建一個bottom_bar_item.dart文件
文字不見的原因? 超過4個的時候, 有一個屬性控制文本隱藏

initialize_items.dart文件
單獨的文件里做管理

引用方式
絕對路徑 package:
相對路徑

home.dart是StatelessWidget
home_content.dart
應(yīng)該是StatefulWidget

ui文件夾

core文件夾
網(wǎng)絡(luò)
工具

文本選中時的顏色用的是主題的顏色
ThemeData()

BottomNavigationBar中
選中和未選中的字體大小不一樣
main.dart中
unselectedFontSize 默認(rèn)12
selectedFontSize 默認(rèn)14
設(shè)置成一致

默認(rèn)水波紋高亮效果
MaterialApp()
highlightColor:Colors.transparent

20.3.18 三
豆瓣列表實現(xiàn)
網(wǎng)絡(luò)請求JSON轉(zhuǎn)Model

ListView.builder創(chuàng)建LiveView
itemBuilder: (ctx, index) {} 類型可以省略

網(wǎng)絡(luò)請求的封裝, 封裝精細(xì)點, 每個對應(yīng)的頁面發(fā)送網(wǎng)絡(luò)請求
直接在頁面里面使用工具類?
每個里面都封裝一個模塊
好處: 在模塊里面完成轉(zhuǎn)化過程, 更加面向?qū)ο?br> home_request.dart 首頁的請求都在這個文件里面封裝
config.dart里面放請求HOST地址
請求了多次

  1. 構(gòu)建URL

  2. 發(fā)送網(wǎng)絡(luò)請求獲取結(jié)果 await 配合 async使用
    subjects數(shù)組存儲請求結(jié)果
    JS里面直接用subjects數(shù)組了
    面向?qū)ο蟮拈_發(fā), 轉(zhuǎn)成模型對象

  3. 將Map數(shù)據(jù)轉(zhuǎn)成Model
    手動進行轉(zhuǎn)化, 看需要的參數(shù), 屬性定義模型
    優(yōu)點: 完全可以控, 獲取自己需要的, 繼承關(guān)系一目了然
    缺點: 麻煩, 可能出錯
    暫時使用手動轉(zhuǎn)換

報錯dynamic轉(zhuǎn)化

電影No.1排序, rank 加上的字段

快捷鍵: 直接生成ToString方法 cmd + n

ListTile換成自定義的Widget
信息特別多封裝成Widget movieItem.dart
只有在首頁使用, 把封裝好的widget放在home文件夾中
在另外的界面也會用到, 封裝到widgets文件夾里面

狀態(tài)不會變化, 數(shù)據(jù)展示是靜態(tài)的數(shù)據(jù), => StatelessWidget

只是在首頁中使用, 命名 JHHomeMovieItem

封裝分析
Widget樹結(jié)構(gòu)
先包裹一個Container, 設(shè)置背景顏色方便, 設(shè)置內(nèi)邊距方便, 擴展性更強
垂直排布 => Column
Container
Container
Row
Container
Widget -> 渲染時是RenderWidget 性能好
xib存在的問題該需求, 嵌套, 布局改起來麻煩
各有利弊
Flutter寫熟練之后, 寫布局是更簡單的
Auto Layout手寫起來更難
CSS寫布局最好寫, 布局和結(jié)構(gòu)完全分離
Flutter布局和結(jié)構(gòu)寫在一起
前端朝著聲明式發(fā)展
await請求出錯拋出異常catch中拿到異常
目前沒有比較好用的JSON解析庫

調(diào)試先用占位的Text()
簡單的優(yōu)化
交叉軸對齊方式設(shè)置
padding做間距
底部間距 Container加邊框
decoration
寫很多布局, 代碼量多, 嵌套層級深 => 函數(shù)封裝

  1. 頭部的布局 buildHeader()
    文本邊緣有內(nèi)邊距
    有背景顏色, 有圓角 color和decoration不能共存
    BoxDecoration(
    color: Colors.fromARGB
    borderRadius:
    )

  2. 內(nèi)容的布局
    buildContent()
    水平排布Row
    確定的寬度, 中間適應(yīng)寬度 => Expanded來做
    東西有點多, 再劃分一下

2.1內(nèi)容圖片
Widget buildContentImage()
圖片默認(rèn)按照比例顯示
給圖片圓角 => 很多種方式設(shè)置圓角, 比較簡單的 => ClipR(Radius)Rect
忘記了多去看源碼多去查文檔, 學(xué)習(xí)方法

2.2內(nèi)容信息
Widget buildContentInfo() {}
Row默認(rèn)交叉軸垂直對齊 => 頂部對齊

嵌套多, 結(jié)果清晰 => 代碼劃分
buildContentInfoTitle()
playDate, json轉(zhuǎn)模型, 自己看數(shù)據(jù)

Row之后的小問題, 不能換行, 只能在一行顯示
解決辦法 => 換成Text.rich

buildContentInfoRate()
Column交叉軸居中對齊 => 改對齊方式成start
SizedBox(width: 6)橫向間距

buildContentInfoDesc()

  1. 是一段文本, 字符串拼接 空格分隔
    join自己查源碼
    超出邊界, 要顯示兩行
    報錯原因, 整個東西是個Row, 沒有固定寬度
    沒有寬度的情況下是內(nèi)容的寬度
    Expanded包裹Column

2.3內(nèi)容的虛線
buildContentLine()包裹一個Container, 給個固定的高度
child: JHDashedLine
傳好方向

2.4內(nèi)容的想看
buildContentWish()

  1. 底部的布局
    buildFooter()
    背景顏色里面有個文本
    Container

上拉下拉監(jiān)聽滾動

toList轉(zhuǎn)換

item默認(rèn)自適應(yīng)高度
動態(tài)控制item里面的widget的顯示與隱藏
if判斷, 添加

json轉(zhuǎn)模型官方

一個網(wǎng)站轉(zhuǎn)換, 自動生成.dart文件
復(fù)雜的數(shù)據(jù)結(jié)構(gòu)會漏掉一下東西, 沒有導(dǎo)演這個類
單獨找到, 單獨轉(zhuǎn)換

編輯器插件AS FlutterJsonBeanFactory
也有問題 int 1; int 2;
沒有網(wǎng)頁好用

20.3.20

臨時加一節(jié)課
Flutter的Widget-Element-RenderObject
很多都是理論, 面試也會問

BuildContext
Widget頻繁的創(chuàng)建會不會影響性能?
StatefulWidget兩個類 Widget模蜡、State的關(guān)系
Key可選參數(shù), 有什么樣的作用?

上節(jié)課的疑惑, 豆瓣補充

豆瓣, 電影名前的圖標(biāo)文字對的不是很齊
WidgetSpan 放上Icon TextSpan TextSpan
三個沒有居中對齊, 怎么做對齊
三個東西全用WidgetSpan包裹 alignment屬性
也可能和CSS文字下沉類似

電影名字太長, 三個東西沒有放在一行
一個WidgetSpan本身就是一個整體, 要么同一行顯示, 要么換一行
奇淫技巧, 每個文字單獨拿出來, 每個文字轉(zhuǎn)成WidgetSpan 編碼runes,在一個數(shù)組中
每一個編碼轉(zhuǎn)成WidgetSpan 最后toList(), 轉(zhuǎn)成列表
...展開, 類似ES6
dart2.3.0開始不再支持這種寫法, 還可以這樣寫
開發(fā)中建議使用其他方法

底部itemBar第一次點擊出現(xiàn)閃爍, 再點擊就不閃了
gapless
如何解決問題?

  1. 嘗試真機會不會閃爍(也會閃爍)
  2. 嘗試圖片換成圖標(biāo)會不會閃爍(圖標(biāo)不會閃爍)
    => 用了圖片, 圖片的特點, 加載的時候會稍微慢點, 圖片讀到內(nèi)存, 渲染
    => 點到圖片的屬性里, 關(guān)于圖片加載的, => gaplessPlayback默認(rèn)false
    false時: 圖片刪掉 顯示空白 加載上圖片
    true時: 中間沒有空白間隔, 一直顯示原來的圖片,

代碼調(diào)試的時候, 打印代碼行數(shù)
打印所在文件、所在行
自己封裝一個小工具log.dart 正則表達(dá)式去匹配文件及所在行數(shù), 調(diào)用棧
jhLog StackTrace
OC file 當(dāng)前文件
定義debug和release模式

虛線高度和其他如何一致?
Row包裹一層Expanded, 中間的child: IntrinsicHeight(Row)

Flutter的渲染流程

樹結(jié)構(gòu)
Flutter Engine要把WidgetTree渲染成最終的界面
并不直接渲染W(wǎng)idgetTree, 這個樹結(jié)構(gòu)特別不穩(wěn)定
動不動就會重新調(diào)用build方法, 引擎直接解析不合適, 耗性能
最終渲染的不是WidgetTree

=> 還有一個樹結(jié)構(gòu) RenderObjectTree 渲染對象
不是所有的WidgetTree都會轉(zhuǎn)化成RenderTree
并不是所有的Widget都會變成RenderObject
Text StatelessWidget build方法 看
不是一個最終渲染的Widget

渲染layout - paint -
Widget 沒有 layout - paint

Element Tree
React的虛擬Dom概念
Flutter借鑒了
inspiration from React??

JS生成的HTML代碼 轉(zhuǎn)成真實的Dom 非常消耗性能的過程 代價昂貴
改一點代碼, 每次都要操控Dom
React -> 虛擬DOM 對代碼結(jié)構(gòu)生成的虛擬DOM
difference修改只需要修改的東西
patch -> 真實DOM反應(yīng)
最小的開銷生成DOM

HTML -> 虛擬DOM -> 真實DOM

Widget Tree -> Element Tree -> Render Tree
key是否相同
最小的開銷更新RenderObject

理論 -> 源碼
看看在哪里創(chuàng)建

=> 多去查文檔

組件Widget:
Text
Container() -> StatelessWidget -> Widget

渲染的Widget: 生成RenderObject
Padding extends SingleChildRenderObjectWidget
-> RenderObjectWidget -> Widget

RenderObjectWidget里面有個核心方法
createRenderObject 抽象方法
讓子類實現(xiàn)

Element Tree在哪里創(chuàng)建?
只要是一個Widget, 里面都需要有個方法 createElement 子類或者父類實現(xiàn)
所有的Widget都會創(chuàng)建一個對應(yīng)的Element對象, 不同Widget創(chuàng)建的Element對象不一樣
StatelessElement
StatefulElement 有一個屬性 state

  1. 自己寫Widget
  2. 某些Widget中會創(chuàng)建renderObject
  3. 每一個Widget都會創(chuàng)建一個Element對象
    4.1 ComponentElement: Flutter引擎自己調(diào)用mount方法
    做了什么事情? firstBuild -> rebuild -> performRebuild
    -> 抽象類看實現(xiàn) build -> _widget.build()創(chuàng)建Element時傳進的build
    4.2 RenderObjectElement: mount方法 -> _widget.createRenderObject
    掛載
    4.3 StatefulElement: extends ComponentElement
    構(gòu)造方法中 _state widget.createState()
    _state._widget = widget; 所以可以 => this.widget
    mount方法

簡單的小總結(jié):
Widget -> Element -> mount方法 ->
回頭調(diào)用Widget的build(BuildContext context)方法,
renderElement -> RenderObject

-> 傳的Context 就是Element

一定要看源碼, 自己走一遍

Dart垃圾回收機制 V8引擎也是這樣的機制
VSCode怎么開發(fā)的?
TypeScript寫的 github上看 93.9%
解釋器執(zhí)行TS代碼, -> JS 最終V8引擎執(zhí)行JS代碼

Widget的key

提一個需求:
界面上顯示3條數(shù)據(jù)
點擊按鈕刪除第一條數(shù)據(jù)
再次點擊再刪除一條
map返回的要toList()
隨機的顏色
刪除后顏色改了

StatefulWidget
刪除后顏色沒改
=> 復(fù)用
canUpdate方法
Element State 復(fù)用

diff算法
runtimeType
默認(rèn)刪除最后一個
如果綁定了Element key, 對比, 把不一樣的刪除

key的另一個作用
點擊刪除, 所有東西都重建
給隨機的key, 強制刷新Element
每次都重新創(chuàng)建state
好好寫一遍案例, 加深理解

key的分類

Key抽象類
LocalKey

GlobalKey
來到上一個層級JHHomePage, 增加一個按鈕
點擊按鈕拿到JHHomeContent扁凛、State屬性的值
靜態(tài)變量 static 弊端 屬于類
=> homepage中
final GlobalKey homeKey
創(chuàng)建Widget綁定key
本質(zhì)里放了幾個Map
也可以調(diào)方法
父調(diào)用子
組件之間的相互引用
evenbus
借鑒React事件總線

Flutter的設(shè)計理念很先進
命令式編程越來越少
聲明式編程越來越多

20.3.23 一

狀態(tài)管理是聲明式編程里非常重要的一個概念
命令式編程 -> 聲明式編程
UI = f( state )
數(shù)據(jù)忍疾、成員變量 -> 狀態(tài)
build方法展示 -> UI界面
setState()

1.2 不同狀態(tài)管理分類

1.2.1 短時狀態(tài) Ephemeral state

某些狀態(tài)只需要在自己的Widget中使用
計數(shù)器counter
PageView
動畫記錄當(dāng)前的進度
BottomNavigationBar中記錄當(dāng)前被選中的tab
缺點: Widget樹中其他Widget不能訪問

1.2.2 應(yīng)用狀態(tài)App state

開發(fā)中也有非常多的狀態(tài)列甩開多個部分進行共享

  • 用戶一個個性化選項
    根據(jù)選擇過濾一部分東西

  • 用戶的登錄狀態(tài)信息
    登錄信息, token, 用戶信息, 多個界面都進行展示

  • 電商應(yīng)用的購物車

  • 新聞應(yīng)用的一度消息或者未讀消息

對狀態(tài)進行統(tǒng)一的管理和應(yīng)用

1.2.3如何選擇不同的管理方式

并沒有一個明確的規(guī)則, 可能會升級
怎么簡單怎么來

共享狀態(tài)管理

2.1 InheritedWidget

counter共享管理
官方提供兩種方式

  1. InheritedWidget
  2. Provider 官方更推崇

抽象方法必須實現(xiàn)
JHCounterWidget
updateShouldNotify 方法
封裝一個靜態(tài)方法獲取對象

特點
繼承自 InheritedWidget
必須實現(xiàn)update...fy方法
定義一個共享狀態(tài)
通過靜態(tài)方法拿到對象

共享一個counter

找到共同的祖先, 祖先包裹一個Widget
傳進來context
樹結(jié)構(gòu)往上去找, 找到最近的對象
靜態(tài)方法返回的null(有錯誤!) -> 返回 context.dependOnInheritedWidgetOfExactType
作用: 沿著Element樹, 去找到最近的JHCounterElement
從Element中取出Widget對象

Widget里的屬性都是不能改變的 -> 重新創(chuàng)建

返回false數(shù)據(jù)改了, 不執(zhí)行didChangeDependence方法
返回true, 執(zhí)行didChangeDependence方法
如何選? 看第一次傳入的counter和第二次傳入的有沒有改變

  1. 共享的數(shù)據(jù)
  2. 自定義構(gòu)造方法
  3. 獲取組件最近的當(dāng)前InheritedWidget
  4. 要不要回調(diào)State中的didChangeDependencies

如何找的? 點到dependOnInheritedWidgetOfExact

結(jié)構(gòu)理清楚, 寫一下代碼
并不是很復(fù)雜, 第一次用可能覺得麻煩點

2.2 Provider

官方已開始推薦的是Provide
相當(dāng)于React中的Redux
官方提供的一個第三方的東西

添加依賴

Provider怎么用?
可能共享的數(shù)據(jù)有很多

一般在最頂層, MyApp
打開箭頭函數(shù)
runApp(
ChangeNotifierProvider {
child: MyApp(),
}
);

使用步驟

  1. 創(chuàng)建自己需要共享的數(shù)據(jù)
  2. 在應(yīng)用程序的頂層ChangeNotifierProvider
  3. 在其他位置使用共享的數(shù)據(jù)
    • Provider.of
    • Consumer
    • Selector

創(chuàng)建一個文件夾 viewmodel或store(商店, 倉庫)
MVVM架構(gòu)
在這個文件夾里創(chuàng)建需要共享的數(shù)據(jù)

快捷鍵: cmd + N, 快速生成getter、setter谨朝、toString卤妒、Constructor

第三步
of 泛型方法

底層依賴InheritedWidget, 做了很多優(yōu)化

Provider.of更簡潔,
當(dāng)Provider中的數(shù)據(jù)發(fā)生改變時, 所在的Widget整個build都會被重新執(zhí)行
開發(fā)中更多的使用Consumer(相對推薦)
當(dāng)Provider中的數(shù)據(jù)發(fā)生改變是, 只會重新執(zhí)行傳入的builder

沒有必要重新構(gòu)建的進行優(yōu)化

  1. child: Icon優(yōu)化
    Consumer還有一個參數(shù) child
    Icon移到這里來, 點擊加號, Icon就不會重新構(gòu)建了
    builder中的參數(shù)child就是Consumer的參數(shù)child

  2. FloatingActionButton也不需要重新構(gòu)建
    Consumer -> Selector

Selector有2個作用

  1. 對原有的JHCounterViewModel進行一個轉(zhuǎn)換
  2. shouldRebuild要不要重新構(gòu)建, 返回false
    shouldRebuild: (prev, next) => false,

2.2.4 MultiProvider

方式一: 多個Provider之間嵌套
弊端, 不方便維護, 擴展性差

方式二: MultiProvider
搞一個initialize_providers.dart文件
統(tǒng)一維護

Consumer2
...
Consumer5
Consumer6
傳多個共享數(shù)據(jù)

網(wǎng)絡(luò)請求放在Provider里面
拿到請求后, 通知一下
類似于觀察者模式

第一次寫的時候會不習(xí)慣,
先看源碼, 后看老師的代碼

20.3.25 三

事件監(jiān)聽
路由導(dǎo)航

豆瓣在5s小屏上有問題
如何修復(fù)?
中間是可伸縮的, 看Row
Row一旦布局好, 還是有一個固定的寬度
五顆星的寬度是固定死的
三種思路來解決

  1. 手動更改子Widget的大小
    FittedBox做適配, 包裹Row, 盡可能占據(jù)寬度, 可以壓縮
  2. FittedBox
    更好的解決方案, 不同屏幕, 不同尺寸 Container大小變化
    后期會開一節(jié)課講, 自己定義一個單位RPX, 把屏幕分成多少分
  3. rpx/rem/vw 100*rpx

事件監(jiān)聽

原始指針事件(Pointer Events):
描述了屏幕上由觸摸板、鼠標(biāo)字币、指示筆等處罰的指針的位置和移動
手勢識別(Gesture Detector): 在原始事件上的一種封裝

2.1指針事件

代表的是人機界面交互的原始數(shù)據(jù). 一共有四種指針事件
Pointer的原理是什么?
hit test, 確定與屏幕發(fā)生接觸的位置上有哪些Widget
冒泡
不存在取消或停止
原始指針事件使用Listener來監(jiān)聽
按下去的時候來電話了则披、鬧鐘響了 onPointerCancel

監(jiān)聽復(fù)雜事件還是需要手勢

2.2手勢識別 Gesture

官方建議開發(fā)中盡可能使用Gesture, 而不是Pointer
長按手勢長按多久? 官方文檔沒有寫

補充個小的知識點
黃色的Container里面放一個小的紅色的Container
直接寫紅色會撐滿
tight約束
alignment: Alignment.center
監(jiān)聽紅色的點擊也不想監(jiān)聽黃色的點擊, 不想做冒泡
默認(rèn)點擊事件不是每次都會傳到黃色, 偶爾會傳到外面
如何解決?
官方文檔有時也會出錯, 翻譯的是老的
不要讓兩個存在嵌套關(guān)系
=> Stack

跨組件事件的傳遞

兩個Widget, Widget層級多
回調(diào)函數(shù)傳好幾層不合適
=> EventBus, 事件總線
訂閱者模式, 通過一個全局的對象來管理
全局事件的傳遞和監(jiān)聽
第三方event_bus

需求: ...

  1. 創(chuàng)建全局的EventBus對象
    開發(fā)中可以搞個全局的文件夾
  2. 發(fā)出事件
  3. 監(jiān)聽事件的類型
    開發(fā)中一般會搞個類

一般不銷毀, 只要程序運行, 就有可能用到

Flutter路由導(dǎo)航

前端朝著路由發(fā)展

路由管理

路由的概念由來已久, 包括網(wǎng)絡(luò)路由、后端路由, 到現(xiàn)在廣為流行的前端路由
核心是一個路由映射表
如: 名字detail映射到DetailPage頁面等
有了這個映射表之后, 我們就可以方便的根據(jù)名字來完成路由的轉(zhuǎn)發(fā)
Flutter中, Route和Navigator
頁面包裝在路由里面, 包裹成一個Route對象
官方的說法: An abstraction for an entry managed by a Navigator

文件夾改命名

1.2 Route

Route抽象類用的最多的MaterialPageRoute

1.3 Navigator

管理所有的Route的Widget, 通過Stack棧結(jié)構(gòu)來進行管理
不需要手動創(chuàng)建Navigator
MaterialApp默認(rèn)是有插入Navigator, 需要的時候直接使用
創(chuàng)建一個詳情頁面
默認(rèn)增加了一個返回按鈕

  1. 普通的跳轉(zhuǎn)方式
    push纬朝、pop
    傳遞過來一些參數(shù), push, 構(gòu)造器直接傳遞參數(shù)

詳情頁返回數(shù)據(jù) pop
Future的返回值
點擊頂部返回按鈕如何也攜帶數(shù)據(jù)?

  1. 自己來決定返回按鈕是什么樣子

  2. WillPopScope包裹Scaffold
    // 當(dāng)返回為true時, 可以自動返回(flutter幫助我們執(zhí)行返回操作)
    // 當(dāng)返回為false時, 自行寫返回代碼
    onWillPop: () {
    return
    }

除了普通的跳轉(zhuǎn)方式還有命名路由跳轉(zhuǎn)
開發(fā)中普通的跳轉(zhuǎn)方式不常用
存在弊端, 商品類App,
首頁 推薦頁 分類頁
點擊都跳轉(zhuǎn)到詳情頁

命名路由使用

基本跳轉(zhuǎn)

routes:
首頁也可以配進去, home可以不寫
=> initialRoute: "/"

字符串常量, 很容易寫錯, => 定義成常量

  1. 把所有的常量放在一個文件夾里
    把路由的名稱定義常量
    頁面里static const String routeName = "/about"
    代碼規(guī)范

參數(shù)傳遞
多個參數(shù), 放到對象里面

跳轉(zhuǎn)時需要動態(tài)參數(shù)
鉤子函數(shù) onGenerateRoute

onUnknownRoute

跳轉(zhuǎn)到一個統(tǒng)一的錯誤頁面
跳轉(zhuǎn)到不存在的設(shè)置頁面

代碼規(guī)范
router文件夾
router.dart
class Router {
static final Map<String, >
}
兩個好處,

  1. 使用的地方代碼間距
  2. 方便增加路由映射

20.3.27

Flutter實現(xiàn)動畫

Flutter有自己的渲染閉環(huán)

一. 動畫API認(rèn)識

1. Animation

抽象類
監(jiān)聽動畫值的改變
監(jiān)聽動畫狀態(tài)的改變
value
status
addListener
removeListener
addStatusListener

2. AnimationController

繼承自Animation
構(gòu)造器里面的必傳參數(shù) vsync
同步信號, 屏幕刷新率, 下層渲染

AnimationController(vsync: this);
多繼承, 混入 with SingleTickerProviderStateMixin

屏幕繪制, 退到后臺收叶、鎖屏狀態(tài)不需要繪制, 不收到同步信號不繪制

forward(): 向前執(zhí)行動畫
reverse(): 反轉(zhuǎn)執(zhí)行動畫

1.3 CurvedAnimation

作用: 設(shè)置動畫執(zhí)行的速率(速度曲線)
官網(wǎng)有g(shù)if圖示例

默認(rèn)情況下, AnimationController動畫生成的值所在區(qū)間是0.0到1.0

1.4 Tween: 設(shè)置動畫執(zhí)行的value范圍

begin: 開始值
end: 結(jié)束值

文檔注釋對應(yīng)類, 有警告

二. 動畫案例練習(xí)

2.1 動畫的基本使用

心跳動畫, 可以反復(fù)執(zhí)行
心的size變化

  1. 創(chuàng)建AnimationController, 做動畫的基石
    this寫在方法里才有用

每次值發(fā)生變化, 界面要執(zhí)行刷新

  1. 設(shè)置Curve的值
    CurvedAnimation(parentL_controller, curve: Curves.elasticInOut)

  2. Tween
    Tween(begin: 50.0, end: 150).animation(animation);
    50要寫成 50.0, 泛型 成了int類型, 出問題

監(jiān)聽動畫值的改變
_controller.addListener
不需要重新構(gòu)建的后期做優(yōu)化

監(jiān)聽動畫的狀態(tài)改變
addStatusListener((status) {})

點擊按鈕動畫暫停, 再次點擊開始
優(yōu)化改變方向

StatelessWidget做動畫?
不可以做動畫

最后記得要
dispose() {
}
銷毀, 調(diào)用的目的不是讓controller對象銷毀, 回收操作

2.2 AnimatedWidget

動畫的另一個知識點
之前做的存在兩個問題

  1. 每次寫動畫, 都需要寫一段代碼
  2. setState => Build方法, 所有東西都會重新構(gòu)建
    只是icon做了個動畫
    優(yōu)化方案:
  3. AnimatedWidget
    • 將需要執(zhí)行動畫的Widget放到一個車AnimatedWidget中的build方法里進行返回
      JHAnimatedWidget
      父類中的屬性 listenable
      缺點:
    1. 每次都需要創(chuàng)建一個類
    2. 如果構(gòu)建的Widget有子類, 那么子類依然會重復(fù)的build
  4. AnimateBuilder
    不重新構(gòu)建

3.1 交織動畫

集合了透明度變化骄呼、大小變化共苛、顏色變化、旋轉(zhuǎn)動畫等
我們這里是通過多個Tween

文檔Cookbook -> Animation ->

  1. 大小變化動畫
  2. 顏色變化動畫
  3. 透明度變化動畫

transform: Matrix4.rotationZ(pi/4);
默認(rèn)左上角作為坐標(biāo)原點旋轉(zhuǎn), 希望以中心點旋轉(zhuǎn)
并不是特別好做
alignment: Alignment.center

  1. 創(chuàng)建AnimationController

  2. 設(shè)置Curve的值

  3. Tween
    3.1 創(chuàng)建size的tween
    3.2 創(chuàng)建color的ColorTween
    3.3 創(chuàng)建opacity的tween
    3.4 創(chuàng)建radians的tween

build方法瘋狂執(zhí)行
-> AnimatedBuilder
有一些動畫是不支持設(shè)置Curve的屬性的, 設(shè)置會報錯
curve: Curves.elasticInOut

知識補充:
轉(zhuǎn)場動畫
iOS中的Push
->Present 兩種做法 Modal

  1. 自己自定義動畫
  2. 比較簡單的方式, Material
    fullscreenDialog: true;

漸變的方式彈出頁面
-> Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (ctx, animation1, animation2) {
//return JHModalPage(); 一下彈出
return FadeTransition(
opacity: animation1,
child:
);
}
))

3.2 Hero動畫

會飛的動畫
遇到的需求:
點擊商品列表的一張圖片, 跳到另一個頁面大圖展示
iOS中自定義轉(zhuǎn)場動畫
Flutter里面直接用Hero動畫直接就可以做出來
這種跨頁面共享的動畫被稱之為享元動畫(Shared Element Transition)
在Flutter中蜓萄,有一個專門的Widget可以來實現(xiàn)這種動畫效果:Hero

圖片網(wǎng)站:
https://picsum.photos/500/500?random=

普通的push效果 -> 動畫效果
非常簡單
綁定Hero
tag不能重復(fù)
改push效果 -> 漸變效果
-> PageRouteBuilder

Flutter中文網(wǎng)站
官方文檔錯誤

20.3.30 一
initState里面不能使用ModalRoute.of()

  1. 官方說了不能使用
    還沒有準(zhǔn)備好InheritedWidget
    還沒有放到映射里面

Timer.run()不會阻塞線程
加入到事件隊列
代碼的執(zhí)行順序問題
執(zhí)行好了以后才能拿到

三四天的時間來做一個小項目練習(xí)
收藏狀態(tài)管理
路由跳轉(zhuǎn)
左側(cè)彈出菜單
三個目標(biāo)

  1. 對前面所學(xué)的東西進行練習(xí), 屏幕適配
  2. 麻雀雖小五臟俱全, 對項目目錄結(jié)構(gòu), 組件化思想劃分, 可擴展性, 可維護性
  3. 其他相關(guān)的東西, 移動端, web測試版本, 圖標(biāo)設(shè)置, 啟動圖設(shè)置, 各種細(xì)節(jié)

先來講解兩個知識點

Flutter主題風(fēng)格

一. Theme主題的使用

Theme氛圍: 全局Theme, 局部Theme

MaterialApp中的
title: "Flutter Demo"
在哪里使用? => 查文檔
安卓中使用的
On iOS this value cannot be used.

  1. 一旦設(shè)置了主題, 那么應(yīng)用程序中的某些Widget, 就會直接使用主題的樣式
    1.1 亮度, 枚舉類型 dark
    brightness
    根據(jù)系統(tǒng)是否是暗黑模式, 寫出兩套樣式

  2. primarySwatch傳入的不是Color, 而是MaterialColor
    (包含了primaryColor和accentColor)
    primarySwatch
    可以設(shè)置很多東西的顏色. 官方?jīng)]有總結(jié)
    自己總結(jié), StackOverFlow中, 官方人員Issue
    靠經(jīng)驗

開關(guān)跟iOS的樣式不一樣
CupertinoSwitch
iOS中顯示的就是綠色
activeColor

MaterialColor
父類的引用指向一個子類的對象
顏色劃分為不同的等級
重寫了一個操作符
Color operator [](T index) => _swatch[index];

  1. primaryColor: 單獨設(shè)置導(dǎo)航和TabBar的顏色

  2. accentColor: 單獨設(shè)置FloatingActionButton/Switch

  3. 某些Widget主題, Button的主題
    buttonTheme: ButtonThemeData(
    height: 25,
    minWidth: 10,
    buttonColor: Colors.yellow
    )

  4. Card的主題
    cardTheme: CardTheme(
    color: Colors.green,
    elevation: 10, //統(tǒng)一設(shè)置陰影
    )

  5. Text的主題
    默認(rèn)字體大小
    textTheme: TextTheme(
    body1: TextStyle(fontSize: 16),
    body2: TextStyle(fontSize: 20),
    )
    Display4
    Display3
    可以通過Theme.of(context).textTheme

實際開發(fā)中方便管理

文檔
Cookbook
Widgets Catalog

API查類

頁面跳轉(zhuǎn), 繼承過來了主題設(shè)置
body1 設(shè)置了紅色
好幾個顏色都發(fā)生了變化
不管包裹了幾層, 都生效

局部主題將全局主題覆蓋
一般情況下不創(chuàng)建新的data, 所有的東西先拷貝過來, 設(shè)置了后會覆蓋
data: Theme.of(context).copyWith(
primaryColor: Colors.purple
)

floatingActionButton單獨包裹一個Theme
也是改不掉
= > 查資料 flutterchina.club
最早的時候官方文檔是有錯誤的
為什么改不掉?
Don't know why this is but accept that it is :)

暗黑模式的適配

Flutter開發(fā)如何適配暗黑模式?
最簡單的適配
theme: ThemeData(
primarySwatch: Colors.yellow,
textTheme: TextTheme(
boyd1: TextStyle(fontSize: 20, color: Colors.red)
)
)
darkTheme: ThemeData(
primarySwatch: Colors.grey,
textTheme: TextTheme(
boyd1: TextStyle(fontSize: 20, color: Colors.blue)
)
)
直接寫兩套
開發(fā)中封裝, 抽取, 搞一個文件夾
JHAppTheme
字體大小抽成常量
static const double smallFontSize = 16;
static const double normalFontSize = 20;
static const double largeFontSize = 24;

屏幕適配

一個適配方案
封裝一個工具類

Flutter中的單位
iphone6 dpr * 2

MyApp的build方法中拿到屏幕寬高
//1. 手機的無力分辨率
window.physicalSize.width;

//2. 手機屏幕的大小(邏輯分辨率)
final width = MediaQuery.of(context).size.width;
直接報錯, 為什么?
context還沒有初始化
JHHomePage中就可以拿到
MaterialApp還沒有初始化完成
of方法里面是怎么拿的

怎么啟動Debug? 點擊小蟲子

就是想在報錯的地方拿到寬高
如何操作?

//2. 獲取dpr, 直接通過window拿
final dpr = window.devicePixelRatio;

//3. 寬度和高度
final width = physicalWidth / dpr;
final height = physicalHeight / dpr;

//4. 狀態(tài)欄高度
final statusHeight = window.padding.top / dpr;

抽取封裝, shared文件夾
size_fit.dart
static靜態(tài)好處是, 不需要創(chuàng)建實例

適配方案

前端里面的屏幕適配經(jīng)驗

  1. rem:
    給根標(biāo)簽(HTML標(biāo)簽)設(shè)置一個字體的大小
    但是不同的屏幕要動態(tài)設(shè)置不同的字體大小
    其他所有的單位都是用rem單位
    html font-size: 20 div font-size: ->

  2. vw隅茎、wh
    將屏幕分成100等份, 一個1vw相當(dāng)于是1%的大小;
    其他所有的單位都是用vw或wh單位

  3. rpx
    rpx是小程序中的適配方案, 它將750px作為設(shè)計稿, 1rpx = 屏幕寬度/750
    其它所有的單位都是用rpx單位
    小程序以iphone6作為標(biāo)準(zhǔn) 750
    原理?
    不管是什么屏幕, 統(tǒng)一分成750份
    在iPhone5上:1rpx = 320/750 = 0.4266 ≈ 0.42px
    在iPhone6上:1rpx = 375/750 = 0.5px
    在iPhone6plus上:1rpx = 414/750 = 0.552px

小程序中所有的單位都要寫成2倍
封裝的通用點
setRpx(400)、setPx(200)
想要封裝的擴展性更高, 以6+為設(shè)計稿
傳一個標(biāo)準(zhǔn)的尺寸
standardSize()

第三方庫: flutter_screenutil

20.4.1 三

升級XCode
iOS -> Flutter -> App.framework文件夾刪除重新運行
升級后的問題, 別人也會遇到

屏幕適配工具類
dart2.6之后的extension語法
更簡單的來做適配
Swift的語法extension
對現(xiàn)有類來做一個擴展

class Person {

}

extension PersonE1 on Person {

}

系統(tǒng)的類來做擴展
String/int
jh_split(" ")
this, 誰調(diào)用我, 就代表誰

對屏幕適配現(xiàn)有的工具類進行擴展
=> 200.px()
對int/double進行擴展

考慮到項目開發(fā)中對很多東西進行擴展
項目目錄建立extension文件夾
int_extension.dart
double_extension.dart
string_extension.dart

double_extension.dart中
import "..size_fit.dart"
extension DoubleFit on double {
double px() {
return JHSizeFit.setRpx(this);
}
}
get寫法更簡單 20.0.px

dart中沒有隱式轉(zhuǎn)換
int不會自動轉(zhuǎn)成double類型
this.toDouble()
Swift中也可以用這種方式做個適配
Java在不斷更新, 語言相對還是比較落后

再來說一個知識點
豆瓣解構(gòu)的方法, 擴展, 當(dāng)時翻譯錯了, 有問題
從2.3.0版本后才開始支持這個語法
...movie.title.runes.map()
報警告的原因, pubspec.yaml
environment:
sdk: ">=2.1.0 < 3.0.0"
改成2.3.0開始支持
警告就消失了
extension直到2.6.0開始才支持
改成2.6.0

新的項目
固定的事情
一. 新建項目
推薦通過終端來新建, 不會生成一些亂七八糟的東西
flutter create favorcate

二. 對項目進行配置
Flutter移動端 => App
都需要配置一些特殊的信息

  • appid App在手機里的唯一標(biāo)示
  • 應(yīng)用名稱, 默認(rèn)顯示項目名字
  • app icon
  • launcher啟動圖

針對Android和iOS還不一樣的

Android中:

AS中, android文件夾

  1. appid修改 app文件夾 -> build.gradle
    -> defaultConfig -> applicationId "com.coderjh.favorcate"
    報錯不用管

  2. main文件夾 AndroidMainifest.xml -> android: lable="美食廣場"

  3. App圖標(biāo) 根據(jù)文件夾名字找對應(yīng)的圖標(biāo) icon="@mipmap/ic_launcher"

  4. 啟動圖 android/app/src/main/res/drawable/launch_background.xml
    規(guī)律: 根據(jù)不同的分辨率, 來到對應(yīng)的文件夾加載圖片, 沒有就到其他分辨率中找

iOS中的配置

不建議在AS中修改
建議在XCode中修改更加方便

  1. Bundle Identity
    XCode打開, 點到文件夾不用到workspace

info.plist文件

  1. Bundle name 美食廣場

  2. AppIcon
    網(wǎng)上工具生成圖標(biāo)

  3. iOS啟動圖, 早期LaunchImage, iOS13開始LaunchScreen.storyboard

開始做項目了
三. 項目結(jié)構(gòu)目錄劃分

  • pages
  • widgets
  • services/network
  • router
  • viewmodel
  • model
  • theme
  • shared
    開發(fā)過程中其他的文件夾
    constants
    utils
    ...
    另外一層考慮, 有一些目錄結(jié)構(gòu)會深一點, 結(jié)構(gòu)更清晰
    core
    ui
    AS建包
    建議來到文件夾里, 創(chuàng)建好文件夾

四. 主題相關(guān)
//
canvasColor:

五. 路由配置
類型推導(dǎo), Stirng可以省略
static const String routeName = "/";

lib/main非常的簡潔
app.dart可以抽可以不抽, 盡量不想讓lib文件盡可能簡單

六.
希望在最開始就把項目劃分的更精細(xì)點
單一職責(zé)原則 initialize_items.dart

一般先搞成StatelessWidget
有結(jié)構(gòu)的時候都用Scaffold

再講一個東西
如果想做一個首頁
JSON解析, 不從服務(wù)器請求
也是一個異步的
category.json
建立assets文件夾

home.dart抽取
home_content.dart

services文件夾中
class JsonParse {
靜態(tài)方法 getCategoryDate() 獲取分類數(shù)據(jù)
1. 加載json文件

    final jsonString = rootBundle.loadString("assets/json/category.json");

    導(dǎo)入convert
    2.將jsonString轉(zhuǎn)成Map/List
    json.decode(jsonString);

    3. 將Map中的內(nèi)容轉(zhuǎn)成一個個對象
    final resultList = result["category"];
    快速建模型的網(wǎng)站
    javiercbk.github.io/json_to_dart/
}

導(dǎo)入屏幕適配工具類extension文件夾

對JHSizeFit進行初始化

顏色不一樣, 還有漸變效果
不是隨機的
顏色來自json文件
字符串 -> Color對象

  1. 截取計算對應(yīng)的255數(shù)字
  2. Color(16進制數(shù)字)
    1. 將color字符串轉(zhuǎn)成十六進制的數(shù)字
      final colorInt = int.parse(color, radix: 16)
    2. 將透明度加進去
      |或運算符
      cColor = Color(colorInt | 0xFF000000);

沒有生效, 重新跑一下, hot restart

20.4.3 五

項目架構(gòu)搭建, 首頁搭建
展示內(nèi)容, 讀取JSON文件

加個類前綴做區(qū)分

首頁代碼改進
抽到一個Widget里面, 減少嵌套層級
有時候快捷鍵抽取有時并不是特別好
依賴的東西太多了
一些東西會作為參數(shù)放在里面
依賴數(shù)組, 會把整個數(shù)組傳給我們
home_category_item.dart

冗余代碼
StatefulWidget 定義變量 加載數(shù)據(jù)
新的知識點
繼承StatelessWidget
異步加載數(shù)據(jù), 根據(jù)異步加載的數(shù)據(jù)進行展示
=> FutureBuilder
未來構(gòu)建的東西, 網(wǎng)絡(luò)請求, 有數(shù)據(jù)的時候構(gòu)建對應(yīng)界面
兩個必傳參數(shù)
future: 發(fā)送的異步請求
builder: (ctx, snapshot) {
return
}
沒有數(shù)據(jù)展示加載中, 加載完了之后再展示
=> snapshot.hasData
snapshot.data 泛型類, 上面就寫好具體類型
請求失敗有error
if (snapshot.error) return Center(child: Text("請求失敗"));

局限性
不是所有場景都適用
Container/padding
上拉加載更多
傳過來參數(shù) page: 1
page的值不斷地變化

一些場景下只能用StatefulWidget

下一個頁面,
加載json文件
meal.json
服務(wù)器請求

123.207.32.32:9001/api/meal

數(shù)據(jù)在多個地方進行共享
加載多次數(shù)據(jù)沒必要
把數(shù)據(jù)加載一次, 放在一個共同的地方共享
過濾出來你想要的數(shù)據(jù)
=> Provider => ViewModel
思路: 服務(wù)器 -> 網(wǎng)絡(luò)請求 -> 數(shù)據(jù)共享

之前封裝的網(wǎng)絡(luò)請求工具類
依賴dio
meal_request.dart文件
靜態(tài)方法 static

  1. 發(fā)送網(wǎng)絡(luò)請求
  2. json轉(zhuǎn)modal
    網(wǎng)站轉(zhuǎn)換, 數(shù)據(jù)特別復(fù)雜的時候轉(zhuǎn)換的有問題

女裝商城數(shù)據(jù), 電商
123.207.32.32:8000/api/h3/detail?iid=1lwwv82
使用網(wǎng)站轉(zhuǎn)換, 報一大堆錯誤,
生成了一個類 List, DartCore里面有List

給另一個網(wǎng)站, 更有效
各種語言的模型都可以轉(zhuǎn)換
app.quicktype.io
這次的模型沒有報錯, 可以直接用
還生成了兩個全局函數(shù)模型json互轉(zhuǎn)

拿到的數(shù)據(jù)放到對應(yīng)的ViewModel中
對數(shù)據(jù)做一個共享
=> Provider官方維護, 添加相關(guān)依賴

Provider -> ViewModel/Provider/Consumer(Selector)
MVVM
viewmodel文件夾
meal_view_model.dart文件
class JHMealViewModel extends ChangeNotifier

Provider
ChangeNotifierProvier(
create:
child: MyApp()
)
懶加載, 不影響首屏加載數(shù)據(jù)

數(shù)據(jù)間的關(guān)系Category和Meal
模型傳遞到下一個頁面
category: id -> meals 過濾

監(jiān)聽點擊, 首頁Item點擊

懶加載, 沒有打印"請求攔截"

導(dǎo)入引用
結(jié)構(gòu)清晰

ModalRoute.of(context), 拿到的是棧頂層的路由傳遞的參數(shù)
拿到的是同一個, 重新拿或傳過來都行
Consumer拿到的是ViewModel
高階函數(shù)做鍋爐類似filter
where((meal) => meal.categories.contains(category.id))
拿到的meals不是數(shù)組類型, 是一個抽象類
轉(zhuǎn)成toList
不想用Consumer

  1. 只要是數(shù)據(jù)發(fā)生改變, 必然重新構(gòu)建
  2. 現(xiàn)在的過濾, 直接的過濾

換成Selector
Selector<JHMealViewModel(原始數(shù)據(jù)), List<JHMealModel>>
要寫兩個泛型
必傳參數(shù)
selector: 前面是上下文ctx, 用來做過濾 A傳進來, 做各種轉(zhuǎn)化, 最后返回S
(mealVM) => mealVM.meals.where((meal) => )
箭頭函數(shù)可以寫開

shouldRebuild: (prev, next) =>
包含的數(shù)據(jù)不同, 才進行刷新
兩個List
List<JHMealModel> prev = ["abc", "cba", ];
List<JHMealModel> next = [""];

  1. 遍歷, 把其中一個遍歷一下
    => 查了一下API, 專門比較兩個列表
    -> import '.../collection.dart';
    不同的時候需要重新刷新 !ListEquality().equals(prev, next),
    builder: (ctx, meals, child) {
    return ListView.builder(
    itemCount:meals.length,
    itemBuilder: (ctx, index) {
    return ListTile();
    }
    );
    }

=>封裝一個Widget
meal_item.dart
暫時沒有數(shù)據(jù)改變 => StatelessWidget

Card的效果比較好看
Column
Stack 存在層疊
Row
先分清結(jié)構(gòu)再寫布局
Card有個margin: EdgeInsets.all(10.px),
陰影elevation: 5,
很多東西不需要記, 經(jīng)常點到源碼看繼承關(guān)系

圖片沒有圓角, 問題,
稍微復(fù)雜一點的東西, 圖片下面沒有圓角, 上面有圓角
=> 圖片進行裁剪
又是一個新的API, 單獨裁剪某些圓角
BorderRadius.only(
topLeft: Radius.circular(12.px),

)

Positioned()絕對定位
會在多個地方用到的, 單獨封裝一個Widget
operation_item.dart
class JHOperationItem extends StalessWidget{}
顯示簡單和復(fù)雜
complexity: 0, 數(shù)據(jù)中的復(fù)雜程度 0, 1, 2
搞一個字符串類型, 創(chuàng)建模型的時候, 直接賦值過去一個值
一個簡單的方法
List<String> complexs = ["簡單", ...]; 0,1,2
不是從0開始就別這樣干
確定沒有問題報警告, moreAction, 保存單詞到字典里面

再稍微講一會
當(dāng)點擊Card時, 跳轉(zhuǎn)到一個詳情頁面
detail.dart
Card默認(rèn)沒有點擊事件, 監(jiān)聽點擊, 手勢
一般放到最后
改路由的東西需要HotRestart

20.4.6 一

今天內(nèi)容還是比較多的
項目里還有什么東西沒有做?
菜譜詳情頁面
過濾頁面, 過濾掉一些食物
右滑出的菜單頁面
收藏顯示

菜譜詳情頁面
兩個地方都會使用
局部變量傳遞來傳遞去

圖片
食材
制作步驟
超過了屏幕的高度, 滾動
方案非常多, 滾動的東西不是相同的

Column超過屏幕高度, 報錯超過安全區(qū)域
如何解決?SingleChildScrollView
Column作為子widget
知識點補充

先把代碼劃分清晰, 用Text占位, 之后進行填充

  1. 橫幅圖片 Widget buildBannerImage() {}
    Image包裹一個Container

  2. 制作材料 Widget buildMakeMaterial() {}
    小標(biāo)題, 內(nèi)容
    兩個標(biāo)題長得差不多
    兩種處理方式: 單獨封裝一個Widget/抽成一個公共的方法
    Widget buildMakeTitle() {}
    上下有一定的間距, 包裹Container
    通過Theme去拿, 需要把上下文傳到方法里
    設(shè)置垂直方向的padding
    希望是粗體 -> copyWith()
    分析: 搞一個ListView來做
    Column嵌套ListView會出現(xiàn)一個常見的問題

有一個邊框
Container里面放一個ListView
用Card做出來的東西比較好看

重點!!!
跑一下就報錯
Flutter中非常經(jīng)典的一個錯誤 hasSize
錯誤產(chǎn)生的原因
把所有Widget放在一個Column里面, 最大占據(jù)控件, 屏幕的高度
SingleChildScrollView只是讓內(nèi)容可以滾動而已
Column需要有一個固定的高度
放了一個ListView后出問題, ListView很特別, 滾動方向上盡可能大的占據(jù)空間, 自己也不知道占據(jù)多少空間
Container的高度是300
里面放個ListView, 盡可能占據(jù)大空間 => 只能占據(jù)300
Column的特點, 需要所有子Widget有個明確的高度,
有了明確的高度才能知道如何擺放, mainAxisAligment
產(chǎn)生了矛盾, ListView希望盡可能多的占據(jù)高度, Column希望ListView給一個固定的高度
Column里面放了一個ListView/ListView中放一個ListView
hasSize: 子Widget不能有一個明確的高度, 父Widget希望子Widget有個明確的高度

給Container一個明確的高度300, 會產(chǎn)生另一個問題
ListView的高度可能會超過300, ListView可以在Container里面滾動, 沒有超過也可以滾動
出現(xiàn)局部滾動的效果
現(xiàn)在不要局部滾動
高度不能寫死, 內(nèi)容多高, 撐多高

如何解決?
讓ListView有一個固定的高度, 不是盡量多的占據(jù)高度
=> 一個屬性shrinkWrap: 范圍內(nèi)進行包裹, 默認(rèn)false
單獨拖動的時候可以進行滾動
=> 一個屬性physics: NeverScrollableScrollPhysics(),設(shè)置不能滾動
每一個cell本來就存在的內(nèi)邊距設(shè)置為0

距離邊緣太近, 給Container一個固定的寬度或者
媒體查詢 MediaQuery 左右兩邊各有15

  1. 制作步驟 Widget buildMakeSteps() {}
    Container里面搞個ListView
    和上面制作材料相似的地方
    Widget buildMakeContent(BuildContext context, Widget child) {}
    間的的重構(gòu)
    共享一個外面的Container, 傳入child
    比較好看 => 寫成可選參數(shù),

圓形的步驟編號
圓角文字, 類似于圓角圖片
leading: CircleAvatar(
child: Text()
)
背景默認(rèn)情況下用導(dǎo)航欄顏色
也可以單獨設(shè)置 backgroundColor: Orange
很多好用的Widget
學(xué)會去搜索, 最好用英文用Google搜索StackOverFlow

底部空間去除 EdgeInsets.zero

和制作材料是重復(fù)代碼
搞個Widget繼承ListView, 有些重復(fù)的東西寫死

收藏按鈕
FloatingActionButton
改了主題, 需要重新跑一下
監(jiān)聽點擊
布局需要多練

不適合用Column, 盡可能大, 循環(huán)創(chuàng)建

Flutter的定位
做一個成套的東西, 最開始不想調(diào)用原生
原來的App是用原生開發(fā)的全線轉(zhuǎn)向Flutter成本高, 模塊開發(fā)
混合開發(fā)
Flutter的第三方插件在越來越豐富
開源出去的機會
Web端如果也發(fā)展的比較好的話, 很看好
混合開發(fā)會講, 橋梁, 原理, 知道為什么要這樣寫
開源的, 讀源碼, 里面有很多東西

收藏的功能
共享收藏過哪些東西
多個地方使用共享
狀態(tài)的共享
有幾種做法

  1. 單獨搞一個Provider, 記錄著所有的收藏, 繼續(xù)收藏繼續(xù)添加
    相當(dāng)于在搞一個ViewModel
  2. 給每一個食物添加一個屬性, isFavor屬性, 默認(rèn)false
    點擊了某個食物, 設(shè)置為true, 刷新界面setState
    考慮一個問題, 采用第二個方案, 需要改StatelessWidget為StatefulWidget
    選擇第一種方案, 統(tǒng)一的管理, 數(shù)組添加移除

favor_view_model.dart
class JHFavorViewModel extends ChangeNotifier {}
操作方法
void addMeal(JHMealModel meal) {}
void removeMeal(JHMealModel meal) {}
操作后發(fā)出通知 notifyListeners()
搞一個車方法返賬bool
bool isFavor(JHMealModel meal) {
return
}

detail.dart中

添加floatingActionButton: Consumer<JHFavorViewModel>(

    return ;
)
  1. 判斷是否是收藏狀態(tài)
    final iconData = favorVM.isFavor(meal) ? Icons.favorite : Icons.favorite_border;
    final iconColor = favorVM. ? Colors.red : Colors.black;
    寫的過程中進行重構(gòu)
    報錯, 找不到Provider
    main.dart中改東西
    providers: [
    多個Provider
    ],

布局的一個問題
Column交叉軸上確實是個Center
默認(rèn)占據(jù)的寬度不是整個屏幕的寬度
是最大的子Widget的寬度
有圖片加載完后, 圖片會撐大寬度
不能直接設(shè)置寬度, 給里面的一個Widget設(shè)置一個盡可能大的寬度infinity
遇到問題分析問題

再次點擊收藏按鈕判斷進行收藏或者取消操作
if判斷其他地方可能也需要使用
在favor_view_model.dart中
void handleMeal(JHMealModel meal) {}

detail_floating_button.dart
JHDetailFloatingButton
detail.dart就比較簡潔了

菜譜列表頁的收藏
之前是直接寫死的
之前封裝的Widget里面
meal_item.dart
很多東西需要做一個改變
搞一個方法

Widget buildFavorItem() {}
Consumer<JHFavorViewModel>(
    builder: (ctx, favorVM, child) {
        //1. 判斷時候收藏狀態(tài)
        final iconData = favorVM.isFavor(_meal) ? Icons.favorite :
        final iconColor = ...
        final title = favorVM.isFavor(_meal) ? "收藏" : "未收藏";

        return JHOperationItem(Icon(iconData, color: iconColor,), title);
    }
)

可選參數(shù)的名字不能以下劃線開頭
operation_item.dart修改

監(jiān)聽點擊GestureDetector(child:JHOperationItem())
文字的長度發(fā)生改變的世邦, 其他會一起變

  1. 給收藏按鈕一個確定的寬度
  2. 文本長度改成一樣 未收藏 已收藏
    Row里面Space
    一開始給一個固定的寬度
    operation_item.dart中給一個固定的寬度, 要寫成.px
    很容易點到旁邊, 給包裹一個padding, 讓收藏按鈕的高度增大
    開發(fā)中的常見做法, 返回按鈕的點擊區(qū)域

favor.dart
創(chuàng)建favor_content.dart
JHFavorContent
拿到數(shù)據(jù), 展示列表
沒有收藏時空的判斷處理

另一個方案, 模型中搞個isFavor, 可以自己嘗試一下

再講一個知識點
抽屜效果
Flutter里面實現(xiàn)起來非常的簡單
添加到首頁, 導(dǎo)航欄抽屜圖標(biāo)
home.dart
drawer: Drawer()
默認(rèn)情況下有一定的寬度
包裹一個Container, 調(diào)整寬度

如何改首頁導(dǎo)航欄上抽屜的圖標(biāo)?
appBar中
leading: Icon(Icons.setting)
直接改了以后Drawer不彈出來了
遇到問題自己搜索
谷歌flutter drawer icon
不能搞一個Icon => IconButton(icon: Icon(Icons.build), onPressed: () {})
怎么彈出?
Navigator.of(context).openDrawer();
依然不可以
解決思路, 自己學(xué)習(xí)方法
Scaffold上下文拿到的不一樣
如何處理?
leading: Builder(
builder: (ctx) {
return IconButton
}
)

抽屜里面的東西做一下
再做一個封裝
AppBar也做一個封裝
home_app_bar.dart
JHHomeAppBar : super中寫
home_drawer.dart
JHHomeDrawer
child: Drawer(
child: Column
)

Widget buildHeaderView() {}
alignment: Alignment(0, 0.5)
增加一個超大字體主題, 需要重新跑一下, 字體比較細(xì), 需要加粗

進餐嫉沽、過了
Widget buildListTile(Widget icon, String title) {}
彈出Drawer
點擊傳入函數(shù)
Navigator.of(context).pop()

過濾下節(jié)課來說

20.4.8 三

繼續(xù)講項目
點擊按鈕彈出Drawer
應(yīng)該是覆蓋上底部欄
下課試著做了出來
很簡單

思路:
之前drawer寫在JHHomeScreen中
包含下面的是MainScreen
給JHMainScreen一個drawer
導(dǎo)航上的按鈕是屬于JHHomeScreen
事件傳遞, 剛好是上層的

還有一個功能沒有做, 過濾
一些食品不喜歡, 過濾掉
學(xué)習(xí)思想, 項目架構(gòu)的思路

底部模態(tài)出選擇界面
創(chuàng)建filter文件夾, filter.dart
JHFilterScreen
filter_content.dart, JHFilterContent
路由, 默認(rèn)的方式彈出, push
自定義, 從下面彈出
generateRoute做一個監(jiān)聽
如果setting.name == ... 做一些自定義
統(tǒng)一管理
改路由, 需要HotRestart

上面是固定
下面是滾動列表
Column布局, 下半部占據(jù)剩余所有空間
Widget buildChoiceTitle() {}

Widget buildChoiceSelect() {}
返回一個ListView
hasSize報錯, shrinkWrap不用了, 換個寫法
Expanded包裹一個ListView,
重復(fù)的東西抽取
Widget buildListTitle(String title, String subtitle, Function onChange)

布局完成后做一些事情
選中, 做過濾
三個界面
布爾類型, 值在多個界面進行共享
數(shù)據(jù)保存在哪里合適?
=> Provider MealViewModel
這樣做會出現(xiàn)問題
相互依賴, 耦合性太過, 只是想使用布爾類型, 卻要依賴整個ViewModel

=> 再搞一個FilterViewModel

生成setter辟犀、getter, 快捷生成
Switch的value不要寫死, 根據(jù)之前的選擇顯示

對meals數(shù)據(jù)進行一個過濾 _meals.where((meal) {
//過濾
}).toList();
相互依賴, 改一個代碼 JHMealViewModel依賴JHFilterViewModel
main.dart
ChangeNotifierProxyProvider
刪除JHMealViewModel,
有個update必傳參數(shù)
ChangeNotifierProxyProvider(
create: (ctx) => JHMealViewModel(),
update: (ctx, filterVM, mealVM) {

}

)

meal_view_model中
搞一個全局變量
void updateFilters(JHFilterViewModel filterVM) {
}
前面對返回false, 最終return true;

收藏沒有過濾掉,
如果需求就是這樣就沒有問題
如果收藏也需要過濾掉
JHFavorViewModel依賴JHFilterViewModel
代碼都是拷貝的, 重復(fù)代碼 => 抽取
兩個類里面有重復(fù)代碼 => 搞一個基類
class BaseMealViewModel extends ChangeNotifer
notifyListenses()放在基類中

過濾之后的meals生成新的對象

在搞一個getter, 拿原始的meals
originMeals {
return _meals;
}

多個Provider之間相互的依賴

Flutter沒有iOS中的ViewWillAppear

K appstore---大象期貨

國際化

i18n
在寫一個簡單的例子, 新建一個項目

想要做國際化, 根據(jù)當(dāng)前系統(tǒng)的語言
點擊按鈕顯示時間選擇器
里面的文本默認(rèn)顯示英文
希望在不同語言下顯示不同語言
當(dāng)前的語言就是中文, 沒有顯示中文
國際化的依賴
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter

supportedLocales: [
Local("zh"),
Locale("en")
]
告訴Widget需要國際化
設(shè)置delegate
localizationsDelegates: [
GlobalWidgetsLocalizations.delegate
]

iOS非常的特殊
需要修改info.plist文件

i18n文件夾放所有國際化相關(guān)的東西
localizations.dart
class JHLocalizations {
static Map<String, Map<String, String>>
}
初始化對象的時候傳入一個local

localizations.dart

創(chuàng)建一個實例, 共享一個實例
參考MaterialApp的做法

isSupport判斷是否支持
顯示默認(rèn)的語言
shouldReload有點不好理解
load方法, 真正加載數(shù)據(jù)
資源是異步加載的, 返回Future

localizations_delegate.dart

抽取
static JHLocalization of(BuildContext context)

數(shù)據(jù)如果是放在服務(wù)器或json文件中, 異步加載
以json來舉例子

  1. 加載json文件
  2. 對json進行解析
    cast函數(shù)

抽到j(luò)son文件中還不是最優(yōu)的方案
getter還是要手動寫
最優(yōu)的方案: 將數(shù)據(jù)抽到arb文件中(應(yīng)用資源包)
開發(fā)時依賴
AS的IDE插件
arb文件支持傳參數(shù)
執(zhí)行了shell腳本

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绸硕,隨后出現(xiàn)的幾起案子堂竟,更是在濱河造成了極大的恐慌,老刑警劉巖玻佩,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件出嘹,死亡現(xiàn)場離奇詭異,居然都是意外死亡咬崔,警方通過查閱死者的電腦和手機税稼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垮斯,“玉大人郎仆,你說我怎么就攤上這事《等洌” “怎么了扰肌?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長熊杨。 經(jīng)常有香客問我曙旭,道長墩剖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任夷狰,我火速辦了婚禮岭皂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沼头。我一直安慰自己爷绘,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布进倍。 她就那樣靜靜地躺著土至,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猾昆。 梳的紋絲不亂的頭發(fā)上陶因,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音垂蜗,去河邊找鬼楷扬。 笑死,一個胖子當(dāng)著我的面吹牛贴见,可吹牛的內(nèi)容都是我干的烘苹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼片部,長吁一口氣:“原來是場噩夢啊……” “哼镣衡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起档悠,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤廊鸥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辖所,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惰说,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年奴烙,在試婚紗的時候發(fā)現(xiàn)自己被綠了助被。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡切诀,死狀恐怖揩环,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情幅虑,我是刑警寧澤丰滑,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響褒墨,放射性物質(zhì)發(fā)生泄漏炫刷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一郁妈、第九天 我趴在偏房一處隱蔽的房頂上張望浑玛。 院中可真熱鬧,春花似錦噩咪、人聲如沸顾彰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涨享。三九已至,卻和暖如春仆百,著一層夾襖步出監(jiān)牢的瞬間厕隧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工俄周, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吁讨,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓栈源,卻偏偏與公主長得像挡爵,于是被迫代替她去往敵國和親竖般。 傳聞我的和親對象是個殘疾皇子甚垦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353