Flutter学习

一、基础

1、Scaffold、Widget

  1. 在Flutter中,大多数东西都是widget,widget是控件实现的基本逻辑单位。widget是不可变的,所以当渲染试图发生变化时,Flutter会重建widget树进行更新。
  2. Scaffold 是 Material library 中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。
  3. widget的主要工作是提供一个build()方法来描述如何根据其他较低级别的widget来显示自己。

StatefulWidget和StatelessWidget:
StatefulWidget在调用setState更新数据时,会触发整个界面视图的销毁+重建(build)。所以能用StatelessWidget就不要用StatefulWidget。

2、属性

  1. Public和Private:声明变量和方法时,在前面加"_"表示【Private】,不加则默认为【Public】

  2. 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、函数

  1. 构造函数
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生命周期
State生命周期分为3个阶段:创建、更新、销毁

创建:

  • initState:只调用一次,负责初始化工作
  • didChangeDependencies:处理State对象依赖关系变化
  • build:负责构建视图

更新:

  • setState:数据变化时,更新UI
  • didChangeDependencies:State对象依赖关系变化后会出发此方法触发build。什么时候依赖会发生变化呢?典型的场景是应用主题改变时执行。
  • didUpdateWidget:父Widget状态发生变化重建时或热重载时调用。

销毁:

  • deactivate和dispose:组件移除或页面销毁时系统会调用这两个方法

如图:左边展示的是父Widget变化时,子Widget的变化。中间和右边是push和pop时新旧Widget的变化

state生命周期

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);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容