前言
Flutter是Google開(kāi)發(fā)的一套全新的跨平臺(tái)開(kāi)源UI框架,使用Dart語(yǔ)言開(kāi)發(fā),支持IOS萨脑、Android、Web饺饭、桌面以及嵌入式平臺(tái)渤早,是未來(lái)Google新操作系統(tǒng)Fuchsia的默認(rèn)開(kāi)發(fā)框架。
2017年5月發(fā)布第一個(gè)版本瘫俊,并且在2018年12月初發(fā)布1.0穩(wěn)定版鹊杖,目前最新版本是2020年10月發(fā)布的1.22
Flutter優(yōu)勢(shì)
-
提高開(kāi)發(fā)效率
- 同一份代碼開(kāi)發(fā)iOS和Android
- 熱重載
- 頁(yè)面每次改動(dòng),不需要重新編譯APK,可快速刷新扛芽。即支持開(kāi)發(fā)過(guò)程中熱重載骂蓖。
-
統(tǒng)一的UI,創(chuàng)建美觀川尖,高度定制的用戶體驗(yàn)
- 受益于使用Flutter框架提供的豐富的Material Design和Cupertino(iOS風(fēng)格)的widget
- 實(shí)現(xiàn)定制登下、美觀、品牌驅(qū)動(dòng)的設(shè)計(jì)叮喳,而不受原生控件的限制
Flutter架構(gòu)
Flutter的架構(gòu)主要分成三層:Framework被芳,Engine和Embedder。
Framework使用dart實(shí)現(xiàn)馍悟,包括Material Design風(fēng)格的Widget,Cupertino(針對(duì)iOS)風(fēng)格的Widgets畔濒,文本/圖片/按鈕等基礎(chǔ)Widgets,渲染锣咒,動(dòng)畫(huà)侵状,手勢(shì)等赞弥。此部分的核心代碼是:flutter倉(cāng)庫(kù)下的flutter package。
Engine使用C++實(shí)現(xiàn)壹将,主要包括:Skia,Dart和Text嗤攻。Skia是開(kāi)源的二維圖形庫(kù),C++ 的2D繪圖引擎诽俯,調(diào)用GPU來(lái)完成渲染妇菱,提供了適用于多種軟硬件平臺(tái)的通用API。
Embedder是一個(gè)嵌入層暴区,即把Flutter嵌入到各個(gè)平臺(tái)上去闯团,這里做的主要工作包括渲染Surface設(shè)置,線程設(shè)置,以及插件等仙粱。從這里可以看出房交,F(xiàn)lutter的平臺(tái)相關(guān)層很低,平臺(tái)(如iOS)只是提供一個(gè)畫(huà)布伐割,剩余的所有渲染相關(guān)的邏輯都在Flutter內(nèi)部候味,這就使得它具有了很好的跨端一致性。
Dart語(yǔ)言介紹
Flutter選用Dart作為其開(kāi)發(fā)語(yǔ)言隔心,除了其語(yǔ)法開(kāi)源等方面的因素外白群,主要還關(guān)乎Dart語(yǔ)言的編譯方式,Dart語(yǔ)言有兩種編譯時(shí):
- JIT(Just in time硬霍,即時(shí)編譯):可以動(dòng)態(tài)下發(fā)和執(zhí)行代碼帜慢,開(kāi)發(fā)測(cè)試效率高,但運(yùn)行速度和執(zhí)行性能則會(huì)因?yàn)檫\(yùn)行時(shí)即時(shí)編譯受到影響唯卖。
- AOT (Ahead of time粱玲,運(yùn)行前編譯): 在開(kāi)發(fā)時(shí)就將代碼編譯成機(jī)器碼,運(yùn)行速度快拜轨,但編譯耗時(shí)抽减。
在開(kāi)發(fā)模式下,F(xiàn)lutter會(huì)使用JIT的編譯方式橄碾,支持熱重載卵沉,能更快速、高效的做UI開(kāi)發(fā)堪嫂。而在打包APK的時(shí)候偎箫,使用AOT的編譯方式木柬,確保APK的性能跟原生媲美皆串。
Dart基礎(chǔ)語(yǔ)法
內(nèi)置數(shù)據(jù)類(lèi)型
在Dart中,所有能夠使用變量引用的都是對(duì)象眉枕,每個(gè)對(duì)象都是一個(gè)類(lèi)的實(shí)例恶复。數(shù)字怜森、函數(shù)和 null 也都是對(duì)象。所有的對(duì)象都繼承于Object類(lèi)谤牡。
要注意副硅,沒(méi)有初始化的變量默認(rèn)值為 null。數(shù)值類(lèi)型變量的默認(rèn)值也是 null翅萤。
數(shù)值類(lèi)型num有兩個(gè)具體子類(lèi)恐疲,分別為int和double,其中int為整數(shù)值套么,范圍是-253至253之間培己;double則是64位的雙精度浮點(diǎn)數(shù)。
變量與常量
定義變量
// 1.通過(guò)顯式指定類(lèi)型來(lái)定義變量
String name = "張三";
int age = 18;
// 2.使用關(guān)鍵字var胚泌,不指定類(lèi)型
var address = "李雷";
var id = 100;
/* 使用var定義變量省咨,即使未顯式指定類(lèi)型,一旦賦值后類(lèi)型就被固定
* 因此使用var定義的變量不能改變數(shù)據(jù)類(lèi)型
*/
var number = 19;
// 以下代碼錯(cuò)誤玷室,無(wú)法運(yùn)行零蓉,number變量已確定為int類(lèi)型
number = "2019";
如想動(dòng)態(tài)改變變量的數(shù)據(jù)類(lèi)型,應(yīng)當(dāng)使用dynamic或Object來(lái)定義變量穷缤。
// dynamic聲明變量
dynamic var1 = "hello";
var1 = 19;
print(var1); // 19
// Object聲明變量
Object var2 = 20;
var2 = "world";
print(var2); // world
定義常量
Dart中定義常量也有兩種方式敌蜂,一種使用final關(guān)鍵字,同Java中的用法绅项, 一個(gè) final 變量只能賦值一次紊册;另一種是Dart的方式,使用const關(guān)鍵字定義快耿。
// 1.使用final關(guān)鍵字定義常量
final height = 10;
// 2.使用const關(guān)鍵字定義常量
const pi = 3.14;
需要注意囊陡,final定義的常量是運(yùn)行時(shí)常量,而const常量則是編譯時(shí)常量掀亥,也就是說(shuō)final定義常量時(shí)撞反,其值可以是一個(gè)變量,而const定義的常量搪花,其值必須是一個(gè)字面常量值遏片。
final time = new DateTime.now(); // 正確
const time = new DateTime.now(); // 錯(cuò)誤
const list = const[1,2,3]; // 正確
const list = [1,2,3]; // 錯(cuò)誤
字符串
Dart中提供的字符串插值表達(dá)式使字符串格式化變得異常方便且豐富
// 1.Dart可以使用單引號(hào)或雙引號(hào)來(lái)創(chuàng)建字符串
var s1 = "hello";
var s2 = 'world';
// 2.類(lèi)似Python,Dart可以使用三引號(hào)來(lái)創(chuàng)建包含多行的字符串
var multiLine1 = """你可以像這樣撮竿,創(chuàng)建一個(gè)
包含了多行的字符串內(nèi)容
""";
var multiLine2 = '''你也可以使用三個(gè)單引號(hào)吮便,創(chuàng)建一個(gè)
包含了多行的字符串內(nèi)容
''';
// 3.類(lèi)似Python,還可以在字符串字面值的前面加上`r`來(lái)創(chuàng)建原始字符串幢踏,則該字符串中特殊字符可以不用轉(zhuǎn)義
var path = r'D:\workspace\code';
// 4.Dart支持使用"+"操作符拼接字符串
var greet = "hello" + " world";
// 5.Dart提供了插值表達(dá)式"${}"髓需,也可以用于拼接字符串
var name = "王五";
var aStr = "hello,${name}";
print(aStr); // hello,王五
// 當(dāng)僅取變量值時(shí),可以省略花括號(hào)
var aStr2 = "hello,$name"; // hello,王五
// 當(dāng)拼接的是一個(gè)表達(dá)式時(shí)房蝉,則不能省略花括號(hào)
var str1 = "link";
var str2 = "click ${str1.toUpperCase()}";
print(str2); // click LINK
// 6. 與Java不同僚匆,Dart使用"=="來(lái)比較字符串的內(nèi)容
print("hello" == "world");
列表
Dart中列表操作與JavaScript中的數(shù)組相似微渠。
// 創(chuàng)建列表
var list = [1, 2, 3];
// 下標(biāo)從0開(kāi)始。使用length可以訪問(wèn)list的長(zhǎng)度
print(list[0]);
print(list.length);
// 可以使用add添加元素
list.add(5);
// 可在list字面量前添加const關(guān)鍵字咧擂,定義一個(gè)不可改變的 列表(編譯時(shí)常量)
var constantList = const [1, 2, 3];
constantList[1] = 1; // 報(bào)錯(cuò)
映射
又稱(chēng)為關(guān)聯(lián)數(shù)組逞盆,相當(dāng)于Java中的HashMap
// 1.通過(guò)字面量創(chuàng)建Map
var gifts = {
'first' : 'partridge',
'second': 'turtledoves',
'fifth' : 'golden rings'
};
// 2.使用Map類(lèi)的構(gòu)造函數(shù)創(chuàng)建對(duì)象
var pic = new Map();
// 往Map中添加鍵值對(duì)
pic['first'] = 'partridge';
pic['second'] = 'turtledoves';
pic['fifth'] = 'golden rings';
// 3.獲取Map的長(zhǎng)度
print(pic.length);
// 4.查找Map
pirnt(pic["first"]);
print(pic["four"]); // 鍵不存在則返回 null
類(lèi)和函數(shù)
Dart中類(lèi)和函數(shù)的定義跟java類(lèi)似
// Dart中定義一個(gè)類(lèi)
class Person {
/**
* Dart中沒(méi)有private、public修飾符
* 默認(rèn)是可以直接被外部訪問(wèn)的松申,在變量前加上 “_”表示是私有的云芦,不能被外部直接訪問(wèn)
**/
String name;
int _age;
// 構(gòu)造函數(shù)寫(xiě)法一
Person(String name, int age) {
this.name = name;
this._age = age;
}
// 在構(gòu)造方法中初始化成員變量時(shí),可使用如下寫(xiě)法簡(jiǎn)化
Person(this.name, this.age);
// 定義函數(shù)
String greet(String name){
return "hello,$name";
}
// 在Dart中贸桶,類(lèi)型是可選焕数,可以省略顯式的類(lèi)型,但仍然建議顯式指定類(lèi)型刨啸。
greet(name){
return "hello,$name";
}
}
注意:
Dart中沒(méi)有構(gòu)造方法的重載堡赔,不能寫(xiě)兩個(gè)同名的構(gòu)造方法。
函數(shù)也是對(duì)象设联,所有函數(shù)都有返回值善已。當(dāng)沒(méi)有指定返回值的時(shí)候,函數(shù)會(huì)返回null离例。當(dāng)然换团,如果你強(qiáng)行使用void來(lái)修飾函數(shù),則函數(shù)真的沒(méi)有返回值宫蛆,這種情況就另當(dāng)別論了艘包。
命名構(gòu)造方法
上面已經(jīng)說(shuō)過(guò),Dart類(lèi)中兩個(gè)同名構(gòu)造方法不能重載耀盗,但是Dart語(yǔ)言為類(lèi)新增了一種稱(chēng)為命名構(gòu)造方法的東西想虎。
class Person {
String userName;
int age;
Person(this.userName, this.age);
// 命名構(gòu)造方法
Person.fromData(Map data) {
this.userName = data['name'];
this.age = data['age'];
}
}
// 可以存在多個(gè)命名構(gòu)造方法
Person.now(int age){
this.age = age;
}
void main() {
// 使用命名構(gòu)造方法創(chuàng)建對(duì)象
var p = new Person.fromData({
"name":"Bob",
"age":19
});
print(p.userName);
}
函數(shù)的參數(shù)
Dart中支持兩種可選參數(shù)
- 命名可選參數(shù)
- 位置可選參數(shù)
在Java中通常使用方法重載來(lái)實(shí)現(xiàn)同名方法的不同參數(shù)調(diào)用,Dart中則可以通過(guò)可選參數(shù)來(lái)實(shí)現(xiàn)相同效果叛拷。
命名可選參數(shù)
命名可選參數(shù)是使用花括號(hào)來(lái)定義參數(shù)列表
// 定義一個(gè)函數(shù)舌厨,參數(shù)列表用花括號(hào)包裹
show({bool bold, bool hidden}) {
// do something
}
// 調(diào)用方式,傳參時(shí)使用"參數(shù)名:值"的形式
show(hidden:true,bold:false);
如果在定義函數(shù)時(shí)忿薇,給參數(shù)列表中的參數(shù)設(shè)置默認(rèn)值裙椭,則該參數(shù)就是可選的,函數(shù)調(diào)用時(shí)可以忽略該參數(shù)署浩,使用默認(rèn)的值揉燃。
// 定義add函數(shù)
add({int x, int y=1, int z=0}){
print(x + y + z);
}
// 調(diào)用
add(x:18); // 19
add(x:18, y:2, z:10); // 30
位置可選參數(shù)
位置可選參數(shù)使用中括號(hào)來(lái)定義參數(shù)列表,中括號(hào)中的參數(shù)是可選的
// 定義add函數(shù)
add(int x, [int y, int z]){
int result = x;
if (y != null){
result = result + y;
}
if (z != null){
result = result + z;
}
print(result);
}
// 調(diào)用
add(18); // 18
add(18,12); // 30
add(18, 12, 15); // 45
當(dāng)然筋栋,你也可以給位置可選參數(shù)設(shè)置默認(rèn)值
// 定義add函數(shù)
add(int x, [int y=0, int z=0]){
print(x +y+z);
}
級(jí)聯(lián)運(yùn)算符
我們通常使用.操作符調(diào)用對(duì)象的方法炊汤,這在Dart中也是支持的,但是Dart另外增加了一種級(jí)聯(lián)運(yùn)算符..,用兩個(gè)點(diǎn)表示婿崭。
級(jí)聯(lián)運(yùn)算符可以在同一個(gè)對(duì)象上連續(xù)調(diào)用多個(gè)方法以及訪問(wèn)成員變量。 使用它可以避免創(chuàng)建臨時(shí)變量肴颊, 寫(xiě)出更流暢的代碼氓栈。
new Person()..setName("Bob")..setAge(20)..save();
異步編程
Dart與JavaScript一樣,是一個(gè)單線程模型婿着。但這并不意味著Dart中不能進(jìn)行異步編程授瘦,只是這種異步編程區(qū)別于傳統(tǒng)的多線程異步方式。簡(jiǎn)單說(shuō)就是在某個(gè)單線程中存在一個(gè)事件循環(huán)和一個(gè)事件隊(duì)列竟宋,事件循環(huán)不斷的從事件隊(duì)列中取出事件來(lái)執(zhí)行提完,這里的事件就好比是一段代碼,每當(dāng)遇到耗時(shí)的事件時(shí)丘侠,事件循環(huán)不會(huì)停下來(lái)等待結(jié)果徒欣,它會(huì)跳過(guò)耗時(shí)事件,繼續(xù)執(zhí)行其后的事件蜗字。當(dāng)不耗時(shí)的事件都完成了打肝,再來(lái)查看耗時(shí)事件的結(jié)果。因此挪捕,耗時(shí)事件不會(huì)阻塞整個(gè)事件循環(huán)粗梭,這讓它后面的事件也會(huì)有機(jī)會(huì)得到執(zhí)行。
Dart 是事件驅(qū)動(dòng)的體系結(jié)構(gòu)级零,該結(jié)構(gòu)基于具有單個(gè)事件循環(huán)和兩個(gè)隊(duì)列的單線程執(zhí)行模型断医。 Dart雖然提供調(diào)用堆棧。 但是它使用事件在生產(chǎn)者和消費(fèi)者之間傳輸上下文奏纪。 事件循環(huán)由單個(gè)線程支持鉴嗤,因此不需要同步和鎖定。
Dart 的兩個(gè)隊(duì)列分別是
MicroTask queue 微任務(wù)隊(duì)列
Event queue 事件隊(duì)列
Dart事件循環(huán)執(zhí)行如上圖所示
- 先查看MicroTask隊(duì)列是否為空序调,不是則先執(zhí)行MicroTask隊(duì)列
- 一個(gè)MicroTask執(zhí)行完后躬窜,檢查有沒(méi)有下一個(gè)MicroTask,直到MicroTask隊(duì)列為空炕置,才去執(zhí)行Event隊(duì)列
- 在Evnet 隊(duì)列取出一個(gè)事件處理完后荣挨,再次返回第一步,去檢查MicroTask隊(duì)列是否為空
我們可以看出朴摊,將任務(wù)加入到MicroTask中可以被盡快執(zhí)行默垄,但也需要注意,當(dāng)事件循環(huán)在處理MicroTask隊(duì)列時(shí)甚纲,Event隊(duì)列會(huì)被卡住口锭,應(yīng)用程序無(wú)法處理鼠標(biāo)單擊、I/O消息等等事件。
Dart中使用async和await關(guān)鍵字來(lái)實(shí)現(xiàn)異步任務(wù)
// 導(dǎo)入io庫(kù)鹃操,調(diào)用sleep函數(shù)
import 'dart:io';
// 模擬耗時(shí)操作韭寸,調(diào)用sleep函數(shù)睡眠2秒
doTask() async{
await sleep(const Duration(seconds:2));
return "Ok";
}
// 定義一個(gè)函數(shù)用于包裝
test() async {
var r = await doTask();
print(r);
}
void main(){
print("main start");
test();
print("main end");
}
運(yùn)行結(jié)果
main start
main end
Ok
需要注意,async 不是并行執(zhí)行荆隘,它是遵循Dart 事件循環(huán)規(guī)則來(lái)執(zhí)行的恩伺,它僅僅是一個(gè)語(yǔ)法糖,簡(jiǎn)化Future API的使用椰拒。
Isolate
如果將非常耗時(shí)的任務(wù)添加到事件隊(duì)列后晶渠,仍然會(huì)拖慢整個(gè)事件循環(huán)的處理,甚至是阻塞燃观“可見(jiàn)基于事件循環(huán)的異步模型仍然是有很大缺點(diǎn)的,這時(shí)候我們就需要Isolate缆毁,這個(gè)單詞的中文意思是隔離番川。
簡(jiǎn)單說(shuō),可以把它理解為Dart中的線程脊框。但它又不同于線程爽彤,更恰當(dāng)?shù)恼f(shuō)應(yīng)該是微線程,或者說(shuō)是協(xié)程缚陷。它與線程最大的區(qū)別就是不能共享內(nèi)存适篙,因此也不存在鎖競(jìng)爭(zhēng)問(wèn)題,兩個(gè)Isolate完全是兩條獨(dú)立的執(zhí)行線箫爷,且每個(gè)Isolate都有自己的事件循環(huán)嚷节,它們之間只能通過(guò)發(fā)送消息通信,所以它的資源開(kāi)銷(xiāo)低于線程虎锚。
Flutter 中創(chuàng)建Isolate
import 'package:flutter/foundation.dart';
import 'dart:io';
// 創(chuàng)建一個(gè)新的Isolate硫痰,在其中運(yùn)行任務(wù)doWork
create_new_task() async{
var str = "New Task";
var result = await compute(doWork, str);
print(result);
}
String doWork(String value){
print("new isolate doWork start");
// 模擬耗時(shí)5秒
sleep(Duration(seconds:5));
print("new isolate doWork end");
return "complete:$value";
}
創(chuàng)建 Flutter app
flutter項(xiàng)目結(jié)構(gòu)
一個(gè)簡(jiǎn)單的頁(yè)面
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Welcome to Flutter'),
),
body: new Center(
child: new Text('Hello World'),
),
),
);
}
}
運(yùn)行應(yīng)用程序,你應(yīng)該看到如下界面
main函數(shù)使用了箭頭函數(shù)(=>), 這是Dart中單行函數(shù)或方法的簡(jiǎn)寫(xiě)窜护。
該應(yīng)用程序繼承了 StatelessWidget效斑,這將會(huì)使應(yīng)用本身也成為一個(gè)widget。 在Flutter中柱徙,大多數(shù)東西都是widget缓屠,包括對(duì)齊(alignment)、填充(padding)和布局(layout)
Scaffold 是 Material library 中提供的一個(gè)widget, 它提供了默認(rèn)的導(dǎo)航欄护侮、標(biāo)題和包含主屏幕widget樹(shù)的body屬性敌完。widget樹(shù)可以很復(fù)雜。
widget的主要工作是提供一個(gè)build()方法來(lái)描述如何根據(jù)其他較低級(jí)別的widget來(lái)顯示自己羊初。
本示例中的body的widget樹(shù)中包含了一個(gè)Center widget, Center widget又包含一個(gè) Text 子widget滨溉。 Center widget可以將其子widget樹(shù)對(duì)其到屏幕中心。
添加一個(gè)有狀態(tài)的widget(StatefulWidget)
import 'package:flutter/material.dart';
import 'pan.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Center(child: Text("Hello World")
));
}
}