一、基础
1、Scaffold、Widget
- 在Flutter中,大多数东西都是widget,widget是控件实现的基本逻辑单位。widget是不可变的,所以当渲染试图发生变化时,Flutter会重建widget树进行更新。
- Scaffold 是 Material library 中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。
- widget的主要工作是提供一个build()方法来描述如何根据其他较低级别的widget来显示自己。
StatefulWidget和StatelessWidget:
StatefulWidget在调用setState更新数据时,会触发整个界面视图的销毁+重建(build)。所以能用StatelessWidget就不要用StatefulWidget。
2、属性
Public和Private:声明变量和方法时,在前面加"_"表示【Private】,不加则默认为【Public】
const和final:都是修饰不可变的变量。const修饰的变量在【编译】时能确定,final修饰的变量在运行时确定。两者一旦确定都不能改
3.存储属性和计算属性
class ShopCart {
String name; //存储属性
price() { //计算属性
return 1;
}
}
3、类
继承、接口、混入
class Point {
var a = 0;
var b = 0;
void printInfo() => print('$a' + '$b');
}
//继承:可以拥有父类的属性和方法实现
class PointA extends Point {
var c = 0;
@override
void printInfo() => print('$a' + '$b' + '$c');
}
//接口实现:获取了Point的属性和方法声明,需要自己给属性赋值和实现方法
class PointB implements Point {
var a = 0;
var b = 0;
void printInfo() => print('$a' + '$b');
}
//混入:拥有另一个类的成员属性和方法【实现】,并且能解决多继承问题
class PointC with Point {} //继承PointA混入Point
注:如果一个类A混入多个类,且这些多个类中有同名的方法,当A调这个同名方法时会怎样?
会调用最后一个with的类的方法实现
4、函数
- 构造函数
class Point {
num x,y,z;
//初始化Point的时候给x和y赋值,等同于在函数体内执行:this.x = x;this.y = y; “: z = 0”的意思是,如果z没有初始化赋值,给他设置默认值0
Point(this.x, this.y) : z = 0;
Point.bottom(num x) : this(x, 0); //重定向构造函数:传一个数字,再调用 Point(this.x, this.y)
void printInfo() => print('($x,$y,$z)');
}
var pp = Point.bottom(100);
pp.printInfo(); //(100,0,0)
2.可选参数和可选命名参数
//可选命名参数:调用函数时,{} 里的参数可以不传,但是传的时候要有参数名
void enableFlags1({bool bold, bool hidden}) => print('$bold, $hidden');
void enableFlags2({bool bold, bool hidden = true}) => print('$bold, $hidden');
//可选参数:调用函数时,[] 里的参数可以不传,参数名页不需要有
void enableFlags3(bool bold, [bool hidden]) => print('$bold, $hidden');
void enableFlags6(bool bold, [bool hidden = true]) => print('$bold, $hidden');
//可选命名参数,带参数名
enableFlags1(bold: true, hidden: false); //true, false
enableFlags1(bold: true); //true, null
enableFlags2(bold: false); //false, true
//可选参数,不带参数名
enableFlags3(true, false); //true, false
enableFlags3(true); //true, null
enableFlags6(true); // true, true
enableFlags6(true, false); // true, false
5、State生命周期
State生命周期分为3个阶段:创建、更新、销毁
创建:
- initState:只调用一次,负责初始化工作
- didChangeDependencies:处理State对象依赖关系变化
- build:负责构建视图
更新:
- setState:数据变化时,更新UI
- didChangeDependencies:State对象依赖关系变化后会出发此方法触发build。什么时候依赖会发生变化呢?典型的场景是应用主题改变时执行。
- didUpdateWidget:父Widget状态发生变化重建时或热重载时调用。
销毁:
- deactivate和dispose:组件移除或页面销毁时系统会调用这两个方法
如图:左边展示的是父Widget变化时,子Widget的变化。中间和右边是push和pop时新旧Widget的变化
6、跨组建传递数据
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()],
),
);
}
}
//写一个继承自【Notification】的类
class CustomNotification extends Notification {
final String msg;
CustomNotification(this.msg);
}
//发送通知
class NotiPoter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
CustomNotification('哈哈哈').dispatch(context);
},
child: Text('发送通知'),
);
}
}
EventBus:无需父子Widget关系,可支持任意对象传递
//创建一个EventBus对象
EventBus eventBus = new EventBus();
class AboutUsPageState extends State<AboutUsPage> {
String _msg = '通知:';
@override
void initState() {
//监听通知
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() {
//销毁通知
eventBus.destroy();
super.dispose();
}
}
//自定义任何类
class CustomBus {
String notiMsg;
CustomBus(this.notiMsg);
}
class PushNoti extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return RaisedButton(
onPressed: () {
//发送通知
eventBus.fire(CustomBus('呵呵呵呵呵'));
},
child: Text('点击发送通知'),
);
}
}
7、路由
基本路由
//ProductDetail为跳转到的类,可穿参
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProductDetail(item: i)),
);
命名路由
//注册
routes: <String, WidgetBuilder>{
"news_page" : (BuildContext context) => NewsPage()
},
//跳转
Navigator.pushNamed(context, 'home_page', arguments: '哈哈哈哈哈');
//跳转到的页面接受参数
Widget build(BuildContext context) {
//接收参数
String msg = ModalRoute.of(context).settings.arguments as String;
print(msg);
return Scaffold(
body: ListView(
children: <Widget>[
BannerWidget(),
HomeProductPage(listData),
],
),
);
}
8、Event Loop机制
Dart是单线程的,但也支持异步。为什么?
有个大前提,APP大多时间都在等待,比如等待网络请求返回,等待用户点击等。等待的行为并不阻塞线程,所以单线程在等待时可以做很多事,等真正需要响应结果了再去做对应的处理。等待这个行为时通过Event Loop驱动的。在Dart中有两个队列:事件队列(Event Queue)、微任务队列(Microtask Queue)。
微任务队列表示一个短时间内就完成的异步任务(比如,手势识别、文字输入、滚动视图、保存页面等),在事件循环中优先级是最高的。
事件队列优先级较低,比如I/O、绘制、定时器等
如何实现异步?
把一个函数体放入Future,就完成了从同步到异步的包装。Flutter还提供了链式调用,在异步执行完后依次执行链路上的其他函数体。
Future(() => print('1111111'))
.then((_) => print('2222222'))
.then((_) => print('3333333')); //打印完1后,一次打印2和3
将函数放入Future后,Dart会将异步任务放入事件队列,【后续的代码正常同步执行】。同步代码执行完后,事件队列会根据【事件加入的顺序】依次取出事件,然后执行Future函数体及后续的then。
执行顺序
异步函数
对于一个异步函数来说,结果不会立即返回,因此需要返回一个Future对象提供给调用者。调用者可以在Future对象注册一个then等Future执行完在进行异步处理,或者使用await关键字一直等待Future执行体结束。
使用await一直等待,常见于网络请求。函数体用async关键字,调用时使用await关键字
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();//调用时使用await关键字
函数体为什么要加async关键字?
因为await的作用不是阻塞等待,而是异步等待(不会影响其他操作)。Dart会将调用函数体的函数也视为异步函数,将等待语句放入Event Queue中,一旦有了结果,Event Loop就把它从Event Queue中取出执行。
Isolate
尽管Dart基于单线程模型,也提供了多线程机制,即Isolate。Isolate之间不共享资源,只依靠消息机制通信,因此也没有资源抢占问题。
Isolate isolate;
start() async {
ReceivePort receivePort = ReceivePort(); //创建管道
isolate = await Isolate.spawn(getMsg, receivePort.sendPort); //将管道作为参数传入
receivePort.listen((data) {
print('$data');
receivePort.close();
});
}
getMsg(sendPort) => sendPort.send("Hello");
Isolate通过发送管道(SendPort)实现消息通信机制。在启动Isolate时,将发送管道作为参数传给他,这样并发Isolate就可以在任务执行完后利用发送管道给我们发消息了。
9、本地存储
文件存储
需要安装:path_provider: ^xxx
//创建/获取文件路径
Future<File> _localFile() async {
String dir = (await getApplicationDocumentsDirectory()).path;
return new File('$dir/test.txt');
}
//将字符串写入文件
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 (相当于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);
}
数据库
和iOS数据库用法类似,不再举例
10、Flutter与原生交互
方法通道(Method Channel)
由于Flutter只接管了渲染层,因此当需要用到系统的底层功能如:蓝牙、相机,或者用到原生比较成熟的框架时,可以用方法通道(Method Channel)与原生交互。
Flutter端代码
//创建方法通道
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];
//监听Flutter端方法调用
[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
平台视图
将原生视图嵌入到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];
}
//创建原生视图封装实例
- (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视图封装类
@interface OrangeViewControl ()
@end
@implementation OrangeViewControl{
UIView *_tempView;
}
//创建原生视图
- (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的主轴会尽可能大,cross轴会自适应子空间最大的值,如果想要它的主轴也自适应它的子控件,就用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设置坐标位置的能力
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包裹的控件会在上面两个范围大的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滚动监听
ScrollController方式:将ScrollController对象赋值给ListView属性
class AboutUsPageState extends State<AboutUsPage> {
ScrollController _controller;
bool _isTop;
@override
void initState() {
//利用ScrollController进行监听
_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滚动
_controller.animateTo(0,
duration: Duration(microseconds: 200), curve: Curves.ease);
}
}),
child: Text("Top"),
),
Expanded(
child: ListView.builder(
controller: _controller, //将ScrollController对象添加到ListView
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(title: Text('Item #$index'));
}),
)
],
);
}
@override
void dispose() {
_controller.dispose();
// TODO: implement dispose
super.dispose();
}
}
NotificationListener方式:将NotificationListener作为ListView的父Widget可监听其滚动状态
class AboutUsPageState extends State<AboutUsPage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (notify) {
if (notify is ScrollStartNotification) {
//滚动开始
print('Scroll Start');
} else if (notify is ScrollUpdateNotification) {
//滚动更新
print('Scroll Update');
} else if (notify is ScrollEndNotification) {
//滚动结束
print('Scroll End');
}
},
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(title: Text('Item #$index'));
}),
);
}
}
7、绘图
Flutter中 我们想要绘图首先要写个类XXX继承自 CustomPainter,然后在类XXX里面实现 Paint(画笔)和绘图方法:void paint(Canvas canvas, Size size) {} 。最后我们将这个类XXX实例成对象赋值给 CustomPaint 绘图容器类里的painter属性。
class AboutUsPageState extends State<AboutUsPage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
//CustomPaint 是一个装绘图的容器
return CustomPaint(
size: Size(300, 300),
//把我们写的 CustomPainter 子类的实例对象赋值给他
painter: DrawPicture(),
);
}
}
//想要绘制图要写一个类继承自 CustomPainter
class DrawPicture extends CustomPainter {
//画笔:可以设置画笔颜色,粗细等
Paint getColoredPaint(Color color) {
Paint paint = Paint();
paint.color = color;
paint.strokeWidth = 2;
return paint;
}
//在这里画图
@override
void paint(Canvas canvas, Size size) {
// canvas.drawLine(
// //绘制一条直线
// 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);
}