MainAxisAlignment.spaceBetween
將主軸空白位置進行均分,排列子元素,首尾子控件距邊緣沒有間隙
MainAxisAlignment.spaceAround
將主軸空白區域均分,使中間各個子控件間距相等,首尾子控件距邊緣間距為中間子控件間距的一半
MainAxisAlignment.spaceEvenly
將主軸空白區域均分,使各個子控件間距相等
flutter column row布局的列表自適應寬高
mainAxisSize: MainAxisSize.min
通過runtimeType可以獲取當前數據類型
var e = [12.5,13.1];
print('e 的類型是: ${e.runtimeType}'); // e 的類型是: List<double>
flutter column嵌套listview不能滾動,或者不顯示的問題
因為 listview水平視口的寬度是無限的。
在listview外面嵌套一個expanded,或者一個container就可以了,尺寸計算的問題,expande就是listview有多大就有多大,container就是container多大listview就有多大,可以滾動
child: Row(
children: <Widget>[
Expanded(
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
itemCount: 120,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(8.0),
child: new Text('${index}'),
),
),
),
],
),
List 常見用法
List<dynamic> topTitles = ['審批單', '機票列表', '客服'];
//遍歷list
for(var value intopTitles){
print("---------<>---${value}");
}
topTitles.forEach((item) => {print(item)});
//遍歷得到一個新的List 用此方法 亦可動態加載Widget
List<dynamic> newList = topTitles.map((item) {
return item + "111";
}).toList();
//list的map方法不能獲取index 用Asmap 轉換成map 后
//動態加載Widget
Row(
children: topTitles
.asMap()
.keys
.map((index) => Expanded(
flex: 1,
child: Column(
children: <Widget>[Text(topTitles[index])],
),
))
.toList(),
),
Map
//遍歷map
mostCare.forEach((k, v) {
// print(k + "==" + v.toString()); //類型不一樣的時候就toString()
});
//map key遍歷生成Widget
Row(
children: mostCare.keys.map((key) {
// print('-----key--${key}');
return Expanded(
flex: 1,
child: Column(
children: <Widget>[Text(mostCare[key])],
),
);
}).toList(),
),
延遲
/* //延遲3秒
Future.delayed(Duration(seconds: 3), () {
});*/
軟鍵盤彈出頂掉內容、防止鍵盤超出屏幕
研究了半天發現十分簡單,只需兩行代碼。布局中最外層包裹一個SingleChildScrollView組件,然后在Scaffold里增加一個屬性 resizeToAvoidBottomPadding: false,即刻解決鍵盤遮擋問題,
類似于 Android 中的 android:windowSoftInputMode=”adjustResize”,控制界面內容 body 是否重新布局來避免底部被覆蓋了,比如當鍵盤顯示的時候,重新布局避免被鍵盤蓋住內容。默認值為 true。
Flutter TextField 光標和內容不能對齊問題
添加該屬性
TextField(
style: TextStyle(textBaseline: TextBaseline.alphabetic),
)
flutter中text設置overflow還是會超出屏幕解決方法
使用flex控件代替row控件,并且在文字外面包一層Expanded
,應該是橫向row沒有確定寬度,text根據內容來撐開row,所以就會超出,換成flex 使text最大寬度能占用剩下的所有寬度,所以達到最寬的時候就會顯示省略號。
這個錯誤就好像再column中使用listView一樣,會出現一個在無限高度的view中使用listView的錯誤,
屏幕適配原理
flutter_screenutil:
說一下適配方案, 比如我們設計師設計的UI是根據Iphone6來做的,我們知道 iPhone6的分辨率是750*1334(px)、
又或者是根據hdpi的設備來設計的UI,我們知道hdpi的 Android設備是 (240 dpi),像素密度是1.5,即hdpi設備的分辨率寬度是320px, 總之,無論設計稿的單位是px,或者是dp,我們都能夠轉換成px.
那么我們如果根據px來適配,ios和 android 就都可以兼容了.
假設,我們的設計稿手機是10801920 px.
設計稿上有一個540960 的組件, 即寬度和寬度是手機的一半. 如果我們直接寫的時候組件的尺寸這么定義,在其他尺寸的設備上未必是一半,或多,或少. 但是我們可以按比例來看,即我們要實現的寬度是實際設備的一半.
那么假設我們設備的寬度是deviceWidth和deviceHeight , 我們要寫的組件大小為: 寬:(540/1080)deviceWidth,高度: (960/1920)deviceHeight.
通過這個公式我們可以發現,我們要寫的組件寬度就是設計稿上的尺寸width(deviceWdith/原型設備寬度).那么每次我們寫ui的時候,只要直接哪來設計稿的尺寸(deviceWdith/設備原型)寬度即可
原理就是先獲取,實際設備與原型設備的尺寸比例.
構造方法
如果沒有自定義構造方法,則會有個默認構造方法
如果存在自定義構造方法 默認構造方法將失效
構造方法不能重載
命名構造方法
使用命名構造方法 可以實現多個構造方法
使用 類名.方法 的形式實現
class Person {
string name
int age
final string sex
/**
* 這里 使用this的語法糖可以對final類型賦值
* 但是寫在構造方法中就不行了,因為執行在構造方法之前
*/
Person (string name ,this.age ,this.sex) { // 第二種是語法糖,
this.name = name
print(age)
}
}
var per = new Person('jack', 20)
---------------------------------------
命名構造方法
class Person {
string name
int age
final string sex
Person (string name ,this.age ,this.sex) { // 第二種是語法糖,
this.name = name
print(age)
}
Person.withName(string name) {
this.name = name
}
}
<!-- 多個構造方法 類似oc -->
new Person('jack', 12, 'man')
new Person.withName('marry')
混合 mixins (with)
除了繼承和接口實現之外,Dart 還提供了另一種機制來實現類的復用,即“混入”(Mixin)。通過混入,一個類里可以以非繼承的方式使用其他類中的變量與方法,效果正如你想象的那樣。
混合的對象是類
可以混合多個
生命周期
State 的生命周期可以分為 3 個階段。
State 初始化時會依次執行 :構造方法 -> initState -> didChangeDependencies -> build,隨后完成頁面渲染。
Widget 的狀態更新,主要由 3 個方法觸發:setStState、didchangeDependencies 與 didUpdateWidget。
Widget 組件銷毀相對比較簡單,系統會調用 deactivate 和 dispose 這兩個方法,來移除或銷毀組件。
Widget 渲染過程
- Widget、是控件實現的基本邏輯單位,里面存儲的是有關視圖渲染的配置信息,包括布局、渲染屬性、事件響應信息等。
- Element :是 Widget 的一個實例化對象,它承載了視圖構建的上下文數據,是連接結構化的配置信息到完成最終渲染的橋梁。
- RenderObject 、主要負責實現視圖渲染的對象、通過控件樹中的每個控件創建不同類型的渲染對象,組成渲染對象樹。
Fluttter 將視圖樹的概念進行了擴展,把視圖數據的組織和渲染抽象為三部分,即 Widget,Element 和 RenderObject。
渲染對象樹在 Flutter 的展示過程分為四個階段,即布局、繪制、合成和渲染。
布局和繪制在 RenderObject 中完成,Flutter 采用深度優先機制遍歷渲染對象樹,確定樹中各個對象的位置和尺寸,并把它們繪制到不同的圖層上,布局和繪制完成后,再交給 Skia進行合成和渲
為什么需要增加中間的這層 Element 樹呢?直接由 Widget 命令 RenderObject 去干活兒不好嗎?
因為 Widget 具有不可變性,但 Element 卻是可變的,實際上,Element 樹這一層將 Widget 樹的變化(類似 React 虛擬 DOM diff)做了抽象,可以只將真正需要修改的部分同步到真實的 RenderObject 樹中,最大程度降低對真實渲染視圖的修改,提高渲染效率,而不是銷毀整個渲染視圖樹重建。
合成和渲染
隨著頁面越來越復雜、Flutter 的渲染樹層級很多,直接交付給渲染引擎進行多圖層渲染,可能會出現大量渲染內容的重復繪制,所以還需要先進行一次圖層合成,即將所有的圖層根據大小、層級、透明度等規則計算出最終的顯示效果,將相同的圖層歸類合并,簡化渲染樹,提高渲染效率,合并完成后,Flutter 會將幾何圖層數據交由 Skia 引擎加工成二維圖像數據,最終交由 GPU 進行渲染,完成界面的展示。
StatelessWidget與StatefulWidget區別
分別是組裝控件的容器
StatelessWidget 不帶綁定狀態,而 StatefulWidget 帶綁定狀態,其依賴的數據在 Widget 生命周期中可能會頻繁地發生變化,由 State創建視圖,數據驅動視圖更新。
單線程模型
// 聲明了一個延遲 3 秒返回的 Hello Flutter 的 Future,
Future<String> fetchContent() async {
await Future.delayed(new Duration(seconds: 3));
return 'Hello Flutter';
}
/**在Dart中,有await標記的運算,其結果值都是一個Future對象,
* 對于異步函數返回的 Future 對象,如果調用者決定同步等待, 則需要在調用處使用 await 關鍵字,
* 并且在調用處的函數體使用 async 關鍵字。
* Dart 中的 await 并不是阻塞等待,而是異步等待,Dart 會將調用體的函數也視作異步函數,
* 將等待語句的上下文放入 Event Queue 中,一旦有了結果,Event Loop 就會把它從 Event Queue 中取出,等待代碼繼續執行。
*/
//await 與 async 有效區間只對調用上下文的函數有效,并不向上傳遞
testAwaitAndAsync() async {
String data = await fetchContent();
print('-----${data}-----'); //等待3秒后打印Hello Flutter 然后打印123
print('----123------');
}
Event Loop 完整版的流程圖
在 Dart 中,實際上有兩個隊列,一個事件隊列(Event Queue),另一個則是微任務隊列(Microtask Queue)。在每一次事件循環中,Dart 總是先去第一個微任務隊列中查詢是否有可執行的任務,如果沒有,才會處理后續的事件隊列的流程。
Isolate
Dart 也提供了多線程機制,即 Isolate。在 Isolate 中,資源隔離做得非常好,每個 Isolate 都有自己的 Event Loop 與 Queue,Isolate 之間不共享任何資源,只能依靠消息機制通信,因此也就沒有資源搶占問題。
Isolate 通過發送管道(SendPort)實現消息通信機制。我們可以在啟動并發 Isolate 時將主 Isolate 的發送管道作為參數傳給它,這樣并發 Isolate 就可以在任務執行完畢后利用這個發送管道給我們發消息了。
,在 Isolate 中,發送管道是單向的:我們啟動了一個 Isolate 執行某項任務,Isolate 執行完畢后,發送消息告知我們。如果 Isolate 執行任務時,需要依賴主 Isolate 給它發送參數,執行完畢后再發送執行結果給主 Isolate,這樣雙向通信的場景我們如何實現呢?答案也很簡單,讓并發 Isolate 也回傳一個發送管道即可。
跨組件通訊
Flutter 與 Android iOS 原生的通信有以下三種方式
BasicMessageChannel 實現 Flutter 與 原生(Android 、iOS)雙向通信 ,主要是傳遞字符串json等數據和一些半結構體的數據,
MethodChannel 實現 Flutter 與 原生原生(Android 、iOS)雙向通信, 用于傳遞方法調用
EventChannel 實現 原生原生(Android 、iOS)向Flutter 發送消息 ,用于數據流(event streams)的通信
平臺視圖 Flutter端使用原生視圖
Flutter 提供了一個平臺視圖(Platform View)的概念。它提供了一種方法,允許開發者在 Flutter 里面嵌入原生系統(Android 和 iOS)的視圖
- 首先,由作為客戶端的 Flutter,通過向原生視圖的 Flutter 封裝類(在 iOS 和 Android 平臺分別是 UIKitView 和 AndroidView)傳入視圖標識符,用于發起原生視圖的創建請求;
- 然后,原生代碼側將對應原生視圖的創建交給平臺視圖工廠(PlatformViewFactory)實現;
- 最后,在原生代碼側將視圖標識符與平臺視圖工廠進行關聯注冊,讓 Flutter 發起的視圖創建請求可以直接找到對應的視圖創建工廠。
class SampleView extends StatelessWidget {
@override
Widget build(BuildContext context) {
//使用Android平臺的AndroidView,傳入唯一標識符sampleView
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(viewType: 'sampleView');
} else {
//使用iOS平臺的UIKitView,傳入唯一標識符sampleView
return UiKitView(viewType: 'sampleView');
}
}
}
我們分別創建了平臺視圖工廠和原生視圖封裝類,并通過視圖工廠的 create 方法,將它們關聯起來
//視圖工廠類
class SampleViewFactory extends PlatformViewFactory {
private final BinaryMessenger messenger;
//初始化方法
public SampleViewFactory(BinaryMessenger msger) {
super(StandardMessageCodec.INSTANCE);
messenger = msger;
}
//創建原生視圖封裝類,完成關聯
@Override
public PlatformView create(Context context, int id, Object obj) {
return new SimpleViewControl(context, id, messenger);
}
}
//原生視圖封裝類
class SimpleViewControl implements PlatformView {
private final View view;//緩存原生視圖
//初始化方法,提前創建好視圖
public SimpleViewControl(Context context, int id, BinaryMessenger messenger) {
view = new View(context);
view.setBackgroundColor(Color.rgb(255, 0, 0));
}
//返回原生視圖
@Override
public View getView() {
return view;
}
//原生視圖銷毀回調
@Override
public void dispose() {
}
}
protected void onCreate(Bundle savedInstanceState) {
...
Registrar registrar = registrarFor("samples.chenhang/native_views");//生成注冊類
SampleViewFactory playerViewFactory = new SampleViewFactory(registrar.messenger());//生成視圖工廠
registrar.platformViewRegistry().registerViewFactory("sampleView", playerViewFactory);//注冊視圖工廠
}