一怕午、基礎(chǔ)
1、Scaffold淹魄、Widget
- 在Flutter中郁惜,大多數(shù)東西都是widget,widget是控件實(shí)現(xiàn)的基本邏輯單位甲锡。widget是不可變的兆蕉,所以當(dāng)渲染試圖發(fā)生變化時(shí),F(xiàn)lutter會(huì)重建widget樹(shù)進(jìn)行更新缤沦。
- Scaffold 是 Material library 中提供的一個(gè)widget, 它提供了默認(rèn)的導(dǎo)航欄虎韵、標(biāo)題和包含主屏幕widget樹(shù)的body屬性。
- widget的主要工作是提供一個(gè)build()方法來(lái)描述如何根據(jù)其他較低級(jí)別的widget來(lái)顯示自己缸废。
StatefulWidget和StatelessWidget:
StatefulWidget在調(diào)用setState更新數(shù)據(jù)時(shí)包蓝,會(huì)觸發(fā)整個(gè)界面視圖的銷(xiāo)毀+重建(build)。所以能用StatelessWidget就不要用StatefulWidget企量。
2测萎、屬性
Public和Private:聲明變量和方法時(shí),在前面加"_"表示【Private】梁钾,不加則默認(rèn)為【Public】
const和final:都是修飾不可變的變量绳泉。const修飾的變量在【編譯】時(shí)能確定逊抡,final修飾的變量在運(yùn)行時(shí)確定姆泻。兩者一旦確定都不能改
3.存儲(chǔ)屬性和計(jì)算屬性
class ShopCart {
String name; //存儲(chǔ)屬性
price() { //計(jì)算屬性
return 1;
}
}
3零酪、類(lèi)
繼承、接口拇勃、混入
class Point {
var a = 0;
var b = 0;
void printInfo() => print('$a' + '$b');
}
//繼承:可以擁有父類(lèi)的屬性和方法實(shí)現(xiàn)
class PointA extends Point {
var c = 0;
@override
void printInfo() => print('$a' + '$b' + '$c');
}
//接口實(shí)現(xiàn):獲取了Point的屬性和方法聲明四苇,需要自己給屬性賦值和實(shí)現(xiàn)方法
class PointB implements Point {
var a = 0;
var b = 0;
void printInfo() => print('$a' + '$b');
}
//混入:擁有另一個(gè)類(lèi)的成員屬性和方法【實(shí)現(xiàn)】,并且能解決多繼承問(wèn)題
class PointC with Point {} //繼承PointA混入Point
注:如果一個(gè)類(lèi)A混入多個(gè)類(lèi)方咆,且這些多個(gè)類(lèi)中有同名的方法月腋,當(dāng)A調(diào)這個(gè)同名方法時(shí)會(huì)怎樣?
會(huì)調(diào)用最后一個(gè)with的類(lèi)的方法實(shí)現(xiàn)
4瓣赂、函數(shù)
- 構(gòu)造函數(shù)
class Point {
num x,y,z;
//初始化Point的時(shí)候給x和y賦值榆骚,等同于在函數(shù)體內(nèi)執(zhí)行:this.x = x;this.y = y; “: z = 0”的意思是,如果z沒(méi)有初始化賦值煌集,給他設(shè)置默認(rèn)值0
Point(this.x, this.y) : z = 0;
Point.bottom(num x) : this(x, 0); //重定向構(gòu)造函數(shù):傳一個(gè)數(shù)字妓肢,再調(diào)用 Point(this.x, this.y)
void printInfo() => print('($x,$y,$z)');
}
var pp = Point.bottom(100);
pp.printInfo(); //(100,0,0)
2.可選參數(shù)和可選命名參數(shù)
//可選命名參數(shù):調(diào)用函數(shù)時(shí),{} 里的參數(shù)可以不傳苫纤,但是傳的時(shí)候要有參數(shù)名
void enableFlags1({bool bold, bool hidden}) => print('$bold, $hidden');
void enableFlags2({bool bold, bool hidden = true}) => print('$bold, $hidden');
//可選參數(shù):調(diào)用函數(shù)時(shí)碉钠,[] 里的參數(shù)可以不傳,參數(shù)名頁(yè)不需要有
void enableFlags3(bool bold, [bool hidden]) => print('$bold, $hidden');
void enableFlags6(bool bold, [bool hidden = true]) => print('$bold, $hidden');
//可選命名參數(shù),帶參數(shù)名
enableFlags1(bold: true, hidden: false); //true, false
enableFlags1(bold: true); //true, null
enableFlags2(bold: false); //false, true
//可選參數(shù)卷拘,不帶參數(shù)名
enableFlags3(true, false); //true, false
enableFlags3(true); //true, null
enableFlags6(true); // true, true
enableFlags6(true, false); // true, false
5喊废、State生命周期
State生命周期分為3個(gè)階段:創(chuàng)建、更新栗弟、銷(xiāo)毀
創(chuàng)建:
- initState:只調(diào)用一次污筷,負(fù)責(zé)初始化工作
- didChangeDependencies:處理State對(duì)象依賴(lài)關(guān)系變化
- build:負(fù)責(zé)構(gòu)建視圖
更新:
- setState:數(shù)據(jù)變化時(shí),更新UI
- didChangeDependencies:State對(duì)象依賴(lài)關(guān)系變化后會(huì)出發(fā)此方法觸發(fā)build乍赫。什么時(shí)候依賴(lài)會(huì)發(fā)生變化呢颓屑?典型的場(chǎng)景是應(yīng)用主題改變時(shí)執(zhí)行。
- didUpdateWidget:父Widget狀態(tài)發(fā)生變化重建時(shí)或熱重載時(shí)調(diào)用耿焊。
銷(xiāo)毀:
- deactivate和dispose:組件移除或頁(yè)面銷(xiāo)毀時(shí)系統(tǒng)會(huì)調(diào)用這兩個(gè)方法
如圖:左邊展示的是父Widget變化時(shí)忧勿,子Widget的變化。中間和右邊是push和pop時(shí)新舊Widget的變化
6较锡、跨組建傳遞數(shù)據(jù)
Notification:只能在父子Widget之間傳遞
class AboutUsPageState extends State<AboutUsPage> {
String _msg = '通知:';
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
//接收通知
return NotificationListener<CustomNotification>(
onNotification: (notification) {
setState(() {
_msg += notification.msg;
});
},
child: Column(
children: <Widget>[Text(_msg), NotiPoter()],
),
);
}
}
//寫(xiě)一個(gè)繼承自【Notification】的類(lèi)
class CustomNotification extends Notification {
final String msg;
CustomNotification(this.msg);
}
//發(fā)送通知
class NotiPoter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
CustomNotification('哈哈哈').dispatch(context);
},
child: Text('發(fā)送通知'),
);
}
}
EventBus:無(wú)需父子Widget關(guān)系月杉,可支持任意對(duì)象傳遞
//創(chuàng)建一個(gè)EventBus對(duì)象
EventBus eventBus = new EventBus();
class AboutUsPageState extends State<AboutUsPage> {
String _msg = '通知:';
@override
void initState() {
//監(jiān)聽(tīng)通知
eventBus.on<CustomBus>().listen((event) {
setState(() {
_msg += event.notiMsg;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[PushNoti(), Text(_msg)],
);
}
@override
void dispose() {
//銷(xiāo)毀通知
eventBus.destroy();
super.dispose();
}
}
//自定義任何類(lèi)
class CustomBus {
String notiMsg;
CustomBus(this.notiMsg);
}
class PushNoti extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return RaisedButton(
onPressed: () {
//發(fā)送通知
eventBus.fire(CustomBus('呵呵呵呵呵'));
},
child: Text('點(diǎn)擊發(fā)送通知'),
);
}
}
7、路由
基本路由
//ProductDetail為跳轉(zhuǎn)到的類(lèi)钩杰,可穿參
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProductDetail(item: i)),
);
命名路由
//注冊(cè)
routes: <String, WidgetBuilder>{
"news_page" : (BuildContext context) => NewsPage()
},
//跳轉(zhuǎn)
Navigator.pushNamed(context, 'home_page', arguments: '哈哈哈哈哈');
//跳轉(zhuǎn)到的頁(yè)面接受參數(shù)
Widget build(BuildContext context) {
//接收參數(shù)
String msg = ModalRoute.of(context).settings.arguments as String;
print(msg);
return Scaffold(
body: ListView(
children: <Widget>[
BannerWidget(),
HomeProductPage(listData),
],
),
);
}
8纫塌、Event Loop機(jī)制
Dart是單線(xiàn)程的,但也支持異步讲弄。為什么措左?
有個(gè)大前提,APP大多時(shí)間都在等待避除,比如等待網(wǎng)絡(luò)請(qǐng)求返回怎披,等待用戶(hù)點(diǎn)擊等胸嘁。等待的行為并不阻塞線(xiàn)程,所以單線(xiàn)程在等待時(shí)可以做很多事凉逛,等真正需要響應(yīng)結(jié)果了再去做對(duì)應(yīng)的處理性宏。等待這個(gè)行為時(shí)通過(guò)Event Loop驅(qū)動(dòng)的。在Dart中有兩個(gè)隊(duì)列:事件隊(duì)列(Event Queue)状飞、微任務(wù)隊(duì)列(Microtask Queue)毫胜。
微任務(wù)隊(duì)列表示一個(gè)短時(shí)間內(nèi)就完成的異步任務(wù)(比如,手勢(shì)識(shí)別诬辈、文字輸入酵使、滾動(dòng)視圖、保存頁(yè)面等)焙糟,在事件循環(huán)中優(yōu)先級(jí)是最高的凝化。
事件隊(duì)列優(yōu)先級(jí)較低,比如I/O酬荞、繪制搓劫、定時(shí)器等
如何實(shí)現(xiàn)異步?
把一個(gè)函數(shù)體放入Future混巧,就完成了從同步到異步的包裝枪向。Flutter還提供了鏈?zhǔn)秸{(diào)用,在異步執(zhí)行完后依次執(zhí)行鏈路上的其他函數(shù)體咧党。
Future(() => print('1111111'))
.then((_) => print('2222222'))
.then((_) => print('3333333')); //打印完1后秘蛔,一次打印2和3
將函數(shù)放入Future后,Dart會(huì)將異步任務(wù)放入事件隊(duì)列傍衡,【后續(xù)的代碼正常同步執(zhí)行】深员。同步代碼執(zhí)行完后,事件隊(duì)列會(huì)根據(jù)【事件加入的順序】依次取出事件蛙埂,然后執(zhí)行Future函數(shù)體及后續(xù)的then倦畅。
執(zhí)行順序
異步函數(shù)
對(duì)于一個(gè)異步函數(shù)來(lái)說(shuō),結(jié)果不會(huì)立即返回绣的,因此需要返回一個(gè)Future對(duì)象提供給調(diào)用者叠赐。調(diào)用者可以在Future對(duì)象注冊(cè)一個(gè)then等Future執(zhí)行完在進(jìn)行異步處理,或者使用await關(guān)鍵字一直等待Future執(zhí)行體結(jié)束屡江。
使用await一直等待芭概,常見(jiàn)于網(wǎng)絡(luò)請(qǐng)求。函數(shù)體用async關(guān)鍵字惩嘉,調(diào)用時(shí)使用await關(guān)鍵字
Future<FinancialTabEntity> getFinancialTab() async {
Map args = Map();
args["action"] = "50000";
args["path"] = "/inst_plat_app/v_1.8.0/head_tabs";
Map result = await HtflutterWebenginePlugin.sendReqXMLRequest(args);
String errorNo = result["ERRORNO"];
if (int.parse(errorNo) != 100) {
return null;
}
Map resultDic = jsonDecode(result["BINDATA"]);
if (int.parse(resultDic['code']) != 0) {
return null;
}
final model = FinancialTabEntity.fromJson(resultDic);
return model;
}
_financialTabEntity = await _apiClient.getFinancialTab();//調(diào)用時(shí)使用await關(guān)鍵字
函數(shù)體為什么要加async關(guān)鍵字罢洲?
因?yàn)閍wait的作用不是阻塞等待,而是異步等待(不會(huì)影響其他操作)文黎。Dart會(huì)將調(diào)用函數(shù)體的函數(shù)也視為異步函數(shù)惹苗,將等待語(yǔ)句放入Event Queue中殿较,一旦有了結(jié)果,Event Loop就把它從Event Queue中取出執(zhí)行鸽粉。
Isolate
盡管Dart基于單線(xiàn)程模型,也提供了多線(xiàn)程機(jī)制抓艳,即Isolate触机。Isolate之間不共享資源,只依靠消息機(jī)制通信玷或,因此也沒(méi)有資源搶占問(wèn)題儡首。
Isolate isolate;
start() async {
ReceivePort receivePort = ReceivePort(); //創(chuàng)建管道
isolate = await Isolate.spawn(getMsg, receivePort.sendPort); //將管道作為參數(shù)傳入
receivePort.listen((data) {
print('$data');
receivePort.close();
});
}
getMsg(sendPort) => sendPort.send("Hello");
Isolate通過(guò)發(fā)送管道(SendPort)實(shí)現(xiàn)消息通信機(jī)制。在啟動(dòng)Isolate時(shí)偏友,將發(fā)送管道作為參數(shù)傳給他蔬胯,這樣并發(fā)Isolate就可以在任務(wù)執(zhí)行完后利用發(fā)送管道給我們發(fā)消息了。
9位他、本地存儲(chǔ)
文件存儲(chǔ)
需要安裝:path_provider: ^xxx
//創(chuàng)建/獲取文件路徑
Future<File> _localFile() async {
String dir = (await getApplicationDocumentsDirectory()).path;
return new File('$dir/test.txt');
}
//將字符串寫(xiě)入文件
Future<File> writeString(String string) async {
final file = await _localFile();
return file.writeAsString(string);
}
//讀取文件里的字符串
Future<String> readString() async {
try {
final file = await _localFile();
String str = await file.readAsString();
print(str);
} catch (e) {
return '';
}
}
SharedPreferences (相當(dāng)于NSUserDefaults)
需要安裝:shared_preferences: ^xxx
Future<void> _saveString(String str) async {
SharedPreferences p = await SharedPreferences.getInstance();
p.setString('orange', str);
}
Future<String> _readString() async {
SharedPreferences p = await SharedPreferences.getInstance();
String str = p.getString('orange');
print(str);
}
數(shù)據(jù)庫(kù)
和iOS數(shù)據(jù)庫(kù)用法類(lèi)似氛濒,不再舉例
10、Flutter與原生交互
方法通道(Method Channel)
由于Flutter只接管了渲染層鹅髓,因此當(dāng)需要用到系統(tǒng)的底層功能如:藍(lán)牙舞竿、相機(jī),或者用到原生比較成熟的框架時(shí)窿冯,可以用方法通道(Method Channel)與原生交互骗奖。
Flutter端代碼
//創(chuàng)建方法通道
const platform = MethodChannel('orangeChannel');
handleClick() async {
try {
platform.invokeMethod('orangeMethod'); //命名方法名
} catch (e) {
print('error');
}
}
onTap: () {
handleClick();
}
iOS端代碼.
#import "AppDelegate.h"
#import "FlutterPluginRegistrant/GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
FlutterViewController *controller = (FlutterViewController *)self.window.rootViewController;
FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"orangeChannel" binaryMessenger:controller];
//監(jiān)聽(tīng)Flutter端方法調(diào)用
[channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
if ([call.method isEqualToString:@"orangeMethod"]) {
NSLog(@"okokokokokokokok");
result(@1);
}else {
result(FlutterMethodNotImplemented);
}
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
平臺(tái)視圖
將原生視圖嵌入到Flutter里面
Flutter端
UiKitView(
viewType: 'orangeView',
)
iOS端
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSObject<FlutterPluginRegistrar> *registrar = [self registrarForPlugin:@"samples.chenhang/native_views"];
OrangeFactory *viewFactory = [[OrangeFactory alloc] initWithMessenger:[registrar messenger]];
[registrar registerViewFactory:viewFactory withId:@"orangeView"];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
OrangeFactory.h
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface OrangeFactory : NSObject
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger> *)messager;
@end
@interface OrangeViewControl : NSObject<FlutterPlatformView>
- (instancetype)initWithWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args;
@end
NS_ASSUME_NONNULL_END
OrangeFactory.m
#import "OrangeFactory.h"
@interface OrangeFactory ()<FlutterPlatformViewFactory>
@end
@implementation OrangeFactory{
NSObject<FlutterBinaryMessenger> *_messenger;
}
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger> *)messager {
self = [super init];
if (self) {
_messenger = messager;
}
return self;
}
-(NSObject<FlutterMessageCodec> *)createArgsCodec{
return [FlutterStandardMessageCodec sharedInstance];
}
//創(chuàng)建原生視圖封裝實(shí)例
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args {
OrangeViewControl *ctl = [[OrangeViewControl alloc] initWithWithFrame:frame viewIdentifier:viewId arguments:args];
return ctl;
}
@end
//iOS視圖封裝類(lèi)
@interface OrangeViewControl ()
@end
@implementation OrangeViewControl{
UIView *_tempView;
}
//創(chuàng)建原生視圖
- (instancetype)initWithWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args {
if ([super init]) {
_tempView = [[UIView alloc] init];
_tempView.backgroundColor = [UIColor orangeColor];
}
return self;
}
- (UIView *)view {
return _tempView;
}
@end
二、布局
1醒串、MainAxisAlignment和CrossAxisAlignment
MainAxisAlignment:
1执桌、Row里面代表水平軸
2、Column里面代表垂直軸
CrossAxisAlignment:
1芜赌、Row里面代表垂直軸
2仰挣、Column里面代表水平軸
- CrossAxisAlignment.stretch
2、MainAxisSize屬性
Row和Column的主軸會(huì)盡可能大缠沈,cross軸會(huì)自適應(yīng)子空間最大的值,如果想要它的主軸也自適應(yīng)它的子控件椎木,就用MainAxisSize.min。
3博烂、Flutter Container 的alignment屬性
Container(
alignment: Alignment(0.0, 1.0),
child:Text('ss')
)
如下圖:左上角為(-1, -1)香椎,右下角為(1, 1)
4、Flex布局
5禽篱、Stack與Positioned
Stack提供了布局的容器畜伐,Positioned提供了子Widget設(shè)置坐標(biāo)位置的能力
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
color: Colors.orange,
width: 300,
height: 300,
),
Container(
color: Colors.blue,
width: 150,
height: 200,
),
//Positioned包裹的控件會(huì)在上面兩個(gè)范圍大的Container中布局
Positioned(
left: 10,
right: 20,
top: 30,
height: 100,
child: Container(
color: Colors.green,
),
),
Positioned(
left: 100,
right: 50,
top: 230,
bottom: 10,
child: Container(
color: Colors.red,
),
),
],
);
}
6、ListView滾動(dòng)監(jiān)聽(tīng)
ScrollController方式:將ScrollController對(duì)象賦值給ListView屬性
class AboutUsPageState extends State<AboutUsPage> {
ScrollController _controller;
bool _isTop;
@override
void initState() {
//利用ScrollController進(jìn)行監(jiān)聽(tīng)
_controller = ScrollController();
_controller.addListener(() {
print(_controller.offset);
if (_controller.offset > 100) {
_isTop = true;
} else {
_isTop = false;
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
onPressed: (() {
if (_isTop == true) {
//ScrollController也能控制ListView滾動(dòng)
_controller.animateTo(0,
duration: Duration(microseconds: 200), curve: Curves.ease);
}
}),
child: Text("Top"),
),
Expanded(
child: ListView.builder(
controller: _controller, //將ScrollController對(duì)象添加到ListView
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(title: Text('Item #$index'));
}),
)
],
);
}
@override
void dispose() {
_controller.dispose();
// TODO: implement dispose
super.dispose();
}
}
NotificationListener方式:將NotificationListener作為L(zhǎng)istView的父Widget可監(jiān)聽(tīng)其滾動(dòng)狀態(tài)
class AboutUsPageState extends State<AboutUsPage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (notify) {
if (notify is ScrollStartNotification) {
//滾動(dòng)開(kāi)始
print('Scroll Start');
} else if (notify is ScrollUpdateNotification) {
//滾動(dòng)更新
print('Scroll Update');
} else if (notify is ScrollEndNotification) {
//滾動(dòng)結(jié)束
print('Scroll End');
}
},
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(title: Text('Item #$index'));
}),
);
}
}
7躺率、繪圖
Flutter中 我們想要繪圖首先要寫(xiě)個(gè)類(lèi)XXX繼承自 CustomPainter玛界,然后在類(lèi)XXX里面實(shí)現(xiàn) Paint(畫(huà)筆)和繪圖方法:void paint(Canvas canvas, Size size) {} 万矾。最后我們將這個(gè)類(lèi)XXX實(shí)例成對(duì)象賦值給 CustomPaint 繪圖容器類(lèi)里的painter屬性。
class AboutUsPageState extends State<AboutUsPage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
//CustomPaint 是一個(gè)裝繪圖的容器
return CustomPaint(
size: Size(300, 300),
//把我們寫(xiě)的 CustomPainter 子類(lèi)的實(shí)例對(duì)象賦值給他
painter: DrawPicture(),
);
}
}
//想要繪制圖要寫(xiě)一個(gè)類(lèi)繼承自 CustomPainter
class DrawPicture extends CustomPainter {
//畫(huà)筆:可以設(shè)置畫(huà)筆顏色慎框,粗細(xì)等
Paint getColoredPaint(Color color) {
Paint paint = Paint();
paint.color = color;
paint.strokeWidth = 2;
return paint;
}
//在這里畫(huà)圖
@override
void paint(Canvas canvas, Size size) {
// canvas.drawLine(
// //繪制一條直線(xiàn)
// Offset(10, 10),
// Offset(100, 100),
// getColoredPaint(Colors.red));
canvas.drawPath( //繪制?
Path()
..moveTo(20, 100)
..lineTo(200, 100)
..lineTo(60, 220)
..lineTo(110, 20)
..lineTo(150, 220)
..lineTo(20, 100)
..close(),
getColoredPaint(Colors.red));
}
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}