在2019年底对于Flutter的Dart语言进行了基础的学习,转眼又来到了2020年3月中下旬了,对于Flutter的学习又断了几个月了,今年目标一定得要把它能学透学精通达到能做商业项目的能力,如今在各大招聘JD上对于会Flutter的安卓程序猿都会优先考虑,可见其它的地位是越来越高了,所以接下来脚踏实地的沿着之前打好的基础继续前行。
官网DEMO解读:
这里重新创建一个工程,Flutter的学习从默认的官网DEMO熟悉开始:
然后用Android打开运行一下:
经典的官方默认运行就如上面所示,而这个小小的案例中能看到Flutter的一个大致的开发流程,另外不是之前已经对于Dart的基础语法进行学习了嘛,然后通过这个小DEMO正好来复习一下Dart语法,看我们之前学的各种语法在Flutter系统源码中都是如何体现的,这样就能让我们之前学的枯燥的语法给串活了,所以接下来好好的对官方生成的这个示例代码进行一下大概解读:
pubspec.yaml:
对于Flutter工程来说:
这个是一个很重要的配置文件,先来瞅一瞅它:
这里有一个小细节需要注意,由于注释#后面跟了一个空格,而打开注释进行图片配置的时候这块需要注意:
另外有个问题?难道所有的图片都需要在这注册么?是的,但是这里可以直接配置一个文件夹中的图片,也就是:
main.dart:
这里面的代码目前还看不太懂,没关系,先整体来读一读,多少会有些收获的,先全部贴出来:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below // so that the display can reflect the updated values. If we changed // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counter++; }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( // Column is also layout widget. It takes a list of children and // arranges them vertically. By default, it sizes itself to fit its // children horizontally, and tries to be as tall as its parent. // // Invoke "debug painting" (press "p" in the console, choose the // "Toggle Debug Paint" action from the Flutter Inspector in Android // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) // to see the wireframe for each widget. // // Column has various properties to control how it sizes itself and // how it positions its children. Here we use mainAxisAlignment to // center the children vertically; the main axis here is the vertical // axis because Columns are vertical (the cross axis would be // horizontal). mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
接下来走读一下:
那哪些都用了这个库呢,咱们把这个导包的语句去掉看报错就晓得了:
继续往下分析:
所以这里面就定义了一个这样的Widget类:
无状态的Widget是个啥呢?这里官方有一句话来描述了:
而这个无状态的Widget里面有个build()方法,一看就是用来进行构建的:
直接返回它,那来看一下这个MaterialApp类:
它当然是承载可变的元素喽,比如说图片的显示,文字的变化,点击事件的响应之类的,基本上跟用户有交互的都要用这个,所以此时则构建了一个Material风格的Widget了,来看一下它的构建细节:
这里就正好可以复习一下我们之前几次学的Dart语法啦,先看一下MaterialApp的构造定义:
这是啥构造函数呀,其实它叫常量构造,可以到这https://www.cnblogs.com/webor2006/p/11981709.html回忆一下:
那它有啥好处呢?
嗯,然后这个构造中的参数是定义在了一对花括号当中了:
这又是什么语法呢?其实是叫可选命名参数,这里可以在这https://www.cnblogs.com/webor2006/p/11975291.html回忆一下Dart语法了:
它有一个注意点就是参数调用时一定得要显示指定参数名,像这样调用是不行的:
哦,原来我们之前学的枯燥的语法最终都是能派上用场的嘛,所以,此时对于这块的写法就不陌生了:
其中这个title是不生效的,因为由home中的MyHomePage进行了设置了,然后theme则是主题,看一下ThemeData是个啥?
其实它叫做工厂构造,复习一下:
有啥用也可以看到了,是一个单例对象,好,再来分析最后一个参数:
先看一下这个参数接收的是个什么类型:
所以此时又定义了一个类,不过这次是一个有状态的,因为准备要构建带变化的视图了:
其中有一个createState()方法,关于这块之后还会讲,这里理解系统会调用它,然后我们需要定义一个State类:
其中它里面的代码可能看着很怪,因为缺少一些理论的东东,这里就简单看一下便好:
它里面有一个Build()方法,可以理解成Android里面View的onDraw(),所以里面直接用了一个系统库中的Widget:
它的中文意思是脚手架,也就是页面的骨架。
然后此时就要以看到视图的构建细节了,也就是最终运行所看到的,首先是一个AppBar:
也就是它:
看一下AppBar是个什么东东:
其中有一个比较奇怪的,就是它的标题的设置:
那这个Text又是啥呢?
往下再分析:
也就是:
它用了一个Center来构建的,我猜它肯定又是一个Widget:
很明显,这个Widget的作用是为了将里面的元素居中显示:
然后它里面的孩子是一个列布局,有点像Android里面的LinearLayout的垂直布局:
此Column肯定又是Widget啦:
然后它的第一个参数是控制主轴方向的:
也就是y轴方向,很显然咱们的孩子就是上下居中的:
那横轴怎么控制呢?这里可以用这个属性:
此时效果就发生变化了:
而看到的两个文本则是由这块来指定的:
其中这里面的写法好奇怪,先来看一下children是啥类型:
它的构建确实就是可以放到[]中来构建的,所以也不足为奇,其中第二个次数显示的则使用了一个表达式,动态的来根据实际count的数量来进行变化:
好,最后一个元素则是那个点击按钮了:
它当然也是一个Widget喽:
第一个参数为onPressed,它其实是一个点击事件的回调方法:
所以这里就指向了count++那个函数了:
然后第一个参数指定的是一个tooltip,其实它就是长按之后的提示:
最后一个则为按钮的图标,由系统提供的,决结一下此DEMO的一个视图树的层次结构:
其中对于Flutter来说,记住一句话,一切皆是Widget,这个从上面的DEMO分析就可以看到,而在之前我们学习Dart语言时也有一句话,一句皆是对象!!虽说目前还没有手动编写过Flutter的代码,但是通过细品这官方的代码,也能或多或少的感受到它的一个编写规则,还是很有意义的。
核心概念了解:
上面在分析官方代码时,是不是感觉还是有些吃力,比如说:
要搞清楚这些东东,其实还是需要有一些理论基础才行的,所以下面来了解一些理论化的概念,有助于更好的来学习Flutter。
视图树:
这块就跟Android完全不一样了,稍稍有点不好理解,但是相当的重要,先来看一下图:
如上图所示,视图树是包含三种,一种是Widget树,第二种是Element树,第三种是Render树,其整个过程如下:
创建widget树。调用runApp(rootWidget),将rootWidget传给rootElement,做为rootElement的子节点,生成Element树,由Element树生成Render树,Render树的根是一个RenderView。也就是说我们代码中编写的Widget它是不负责渲染的,最终渲染是得靠Render树,而且每个树结点都会有一个对应的转换结点:
而对于这三者视图树都发挥啥作用呢?下面描述一下:
- Widget:存放渲染内容、视图布局信息,widget的属性最好都是immutable。
- Element:存放上下文,通过Element遍历视图树,Element同时持有Widget和RenderObject。
- RenderObject:根据Widget的布局属性进行layout,对widget传入的内容进行渲染绘制。
这块需要好好感受一下,跟Android的不同。
树更新:
- 全局更新:调用runApp(rootWidget),一般flutter启动时调用后不会再调用。
如咱们程序所示: - 局部子树更新:将子树作StatefulWidget的一个子Widget,并创建对应的State类实例,通过调用State.setState()触发子树的刷新。
有这条做为理解依据,所以官方DEMO的这个代码就可以理解了:
也就是当我们点击按钮时则会触发_counter++这个动作,而这个动作最终要让TextView进行刷新就必须将这个++放到setState()方法当中了。
StatelessWidget:
这个是指无状态的Widget:
看一下它的理论化描述:
- StatelessWidget:无中间状态变化的widget,初始状态设置以后就不可再变化。
- StatelessWidget用于不需要维护组件状态的场景。
- createElement()创建StatelessElement对象。
- 一个StatelessWidget对应一个StatelessElement。
提示:Flutter推荐尽量使用StatelessElement,性能好,但是吧也得看实际情况。
StatefulWidget:
看一下它理论化的描述:
- StatefulWidget:存在中间状态变化的widget。
- createElement()创建StatelfulElement对象。
- createState()创建State对象(可能调用多次)。
- 一个StatefulElement对应一个State实例。
所以Demo中就有这样的代码:
当一个StatefulWidget同时插入到widget树的多个位置时,Flutter framework就会调用createState方法为每一个位置生成一个独立的State实例。
State:
它有两个重要的属性:
- widget:表示与该State实例关联的widget实例。
也就是:
- context:BuildContext的一个实例。
另外关于Widget、State、setState()、BuildContext之间的关系,描述如下:
- 可以手动调用其setState()方法通知Flutter framework状态发生改变,Flutter framework在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新UI的目的。
- Widget与State的关联不是永久的。UI树上的某一个节点的widget实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。
- BuildContext表示构建widget的上下文,它是操作widget在树中位置的一个句柄,它包含了一些查找、遍历当前Widget树的一些方法。每一个widget都有一个自己的context对象。
State生命周期【重要】:
任何关于生命周期的知识都是非常重要的,所以下面好好的来看一下,这里分为三个维度来看:
StatefulWidget插入到widget树:
initState:当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。
didChangeDependencies:initState后立刻调用,state依赖的对象发生变化时调用。
build:构建Widget时调用,调用后控件会显示。
点击⚡️按钮热重载:
reassemble:此回调是专门为了开发调试而提供的,在热重载时会被调用,此回调在Release模式下永远不会被调用。
didUpdateWidget:组件状态改变时候调用,可能会调用多次。
从widget中移除StatefulWidget:
deactive:当State对象从树中被移除时,会调用此回调。
dispose:当State对象从树中被永久移除时调用,通常在此回调中释放资源。
常用组件学习之container、image:
好,在详读了官方的默认DEMO的代码之后,接下来则手动来撸码进行操练了,还记得之前说过Flutte里一切皆组件么?所以这里先从一些学用的组件(Widget)开始学起,就像Android里面的图片,文件,列表,滑动之类的,虽说也有点小枯燥,但是这是打好基础的必经之路。
container:
容器组件Container包含一个子widget,自身具备alignment、padding等属性,方便布局过程中摆放child。
下面新建一个新的文件来进行代码的编写:
这里就直接校仿官网的写法,其中这里有个技巧,就是对于这个StatelessWidget类要自己定义稍麻烦,其实是可以有快捷键快速的生成模板的,如下:
这里来试一下效果:
好,继续,这里先来简单显示一下:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Container示例'), ), ), ); } }
这里就直接先弄了个脚手架,之前官网的DMEO也是一样的,只是比这要稍微麻烦一点,运行看下效果:
其中右上角有一个DEBUG的标识:
其实它是可以去掉的,这样弄:
此时点一下则就消失了:
这里是要学习Container的用法,那接下来内容部分就用一下它:
运行:
Center这个Widget就是它里面的元素是一个居中布局的,然后里面有个Container组件,这里再次强调一下,为啥代码可以这样写,毕境是刚入门学习节奏可以慢一点,重点是要搞懂:
这是因为Container的命名构造有一个child参数,它接收的类型是Widget:
所以这里就靠熟练生巧了,对于组件的学习也是非常之必要的,接下来继续,貌似这个Container的文字太小了,改变一下它的大小:
改完之后,Flutter有一个非常好的功能就是热重载,就像前端开发人员一样,当然在Android中也类似的热重载功能,比如Instant run,但是。。貌似不太好用,而在Flutter上相当好用,像上面改了代码要生效,按control+s等个一两秒立马就能看到效果了,反正我在平常搞Android开发中对于更改是比较痛苦的,基本都得等个几分钟才能运行
设置Container背景色:
好。接下来咱们来改一下Container的颜色:
设置Container宽高和Padding:
设置Container的decoration:
设置边框:
它是干嘛的?下面直接看效果既可:
运行,发现报错了:
也就是说这里的color冲突了,这里就得注意了:
运行:
设置圆角:
其中这里又得复习一下Dart语法了:
还记得这是啥语法么?这里回忆一下:
查看一下它们的定义是否符合命名构造的语法:
嗯,确实是,另外为啥要用const来修饰呢?这里还记得const的好处了么?回忆一下:
transform:设置Container的变换矩阵,类型为Matrix4
关于Container的具体用法之后再挖掘。
image:图片组件
这再建一个Dart文件来学习一下图片组件。
加载网络图片:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text("Image示例"), ), body: Center( child: Column( children: <Widget>[ Image.network( "https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3034071742,1954908597&fm=26&gp=0.jpg", 100.0, height: 100.0, ) ], ), ), ), ); } }
运行:
其中看一下Image的网络的命名构造的定义:
加载assets资源图片:
先定义一张assets的图片:
运行:
另外这里还有一个能配置UI调试的东东:
加载Uint8List内存资源图片:
这里先要将图片转成Uint8List类型,这里打算采用一个有状态的Widget来实现,先定义一个有状态的Widget:
其中initState()就派上用场了,这里回忆一下它的生命周期:
另外还用了一个条件判断mouted这又是啥意思呢?
另外还用到了then来监听数据加载完,这块的语法也复习一下:
数据加载好了,接下来则需要更新到图片视图中,也就是处理build()方法:
接下来将其定义到Column中:
运行一下:
从相册中选图片进行加载:
这里还是先来搭建一个有状态的Widget:
然后添加到Column中来瞅一下:
运行:
此时咱们点击“选择图片”时,则需要跳到相册中来进行图片的选择,该如何做呢?这里需要用到一个image_picker三方开源库,所以咱们集成一下:
好,接下来咱们则可以来实现这个点击事件了:
其中getImage方法使用了async await异步处理方式,这个在之前Dart也学过了。
运行:
那在IOS上的表现如何呢,运行一下,发现报错了。。
貌似是ios找不到我们的这个图片相册选择库,这里可以用终端进入到ios目录执行一下这个命令,然后会下载相关的依赖包:
然后再运行: