上一篇,学习了 Dart 语法,对 Dart 的语法和特性有了更深一步的了解。今天,来学习 Flutter 的基础控件,身为 Android 开发者都知道,一开始入坑 Android 就要熟悉学习其控件,如:TextView,ImageView,Button,ListView,RecycleView 等。为什么要学习呢?因为平时的开发都离不开这些控件,UI 的呈现都是有这些控件组成的,因此,其重要性就不用说了。对于 Flutter 来讲,基础控件(widget)就更加重要了。Flutter 和 Android 有所不一样,Android 布局包含布局(RelativeLayout,LinearLayout,ConstrainLayou)和组件。Flutter的一切都是 Widget ,包括最顶层布局也是 Widget,一个页面有很多很多的 Widget 组合而成, Widget 也称为装饰品,窗口小部件。
在 Flutter 里,UI 控件就是 Widget ,Widget 根据不同的功能可以分为结构元素(如按钮或菜单),文本样式(字体或者颜色方案),布局属性(如填充,对齐,居中),可以这么理解,一个 flutter 的页面是有一棵树型的 Widget 组成,包括根节点,树枝和树叶,全都是 Widget ,只是 Widget 嵌套 Widget ,那就可以用下面这张图来表示:
在 Flutter 中,Widget
是一切的基础,作为响应式渲染,属于 MVVM
的实现机制,通过修改数据,再用 setState
设置数据,Flutter 会自动通过绑定的数据更新 Widget
,所以在平时开发中,开发者需要的就是实现 Widget
界面,和数据绑定起来。在平时,用的最多就是 StatelessWidget
和 StatefulWidget
这两种 Widget
,StatelessWidget
表示无状态的,StatefulWidget
表示有状态的。
这里怎么理解呢?
在 Flutter 中每个页面都是一帧,无状态就是保持在那一帧,总而言之就是不能跟用户交互,当有状态的 Widget
当数据更新时,其实是绘制了新的 Widget
,也就是 UI 发生了变化,只是 State
实现了跨帧数据同步保存。这里给大家说下,在 Android Studio 看源码的两个工具:
左边一栏 Structure
结构(看当前文件,win下的快捷键是(Alt+7))和右边 Hierarchy
继承关系(看当前类,win下快捷键是F4)都可以帮助你阅读源码。因为 StatelessWidget
和 StatefulWidget
用的最多,现在只需要用到这两个,就先学习这两个 Widget
。
const StatelessWidget({Key key}):super(key:key)
:初始化子类的 [key]。这个 key 类是Widget、Element、SemanticsNode
的唯一标识符,是用来控制 Widget 数中替换 Widget 的时候使用的。StatelessElement createElement()
:创建一个[StatelessElement]
来管理这个小部件在树中的位置,源码解释:子类重写此方法是不常见的,那这个方法也不用管,只需要知道这个方法用来管理自身在Widget
树中的位置。Widget build(BuildContext context)
:描述这部件呈现用户界面的部分。对于StatelessWidget
,当 Widget
第一次插入到树中,或者父节点更改了配置和所依赖的[InheritedWidget]改变,都会被重新调用。这里说下如何启动一个 Flutter 应用,并使用 Flutter 框架:
import 'package:flutter/material.dart';
void main() {
return runApp(Widget app);
}
其实就是在main()
函数中调用 runApp
函数。下面直接直接上例子,继承StatelessWidget
,通过 build
方法返回一个控件:
import 'package:flutter/material.dart';
//使用`flutter/material.dart` 目的是使用Matrial风格的小控件
void main(){
//运行程序
runApp(MyApp(null));
}
//继承无状态的StatelessWidget 使程序自身变为Wiget
class MyApp extends StatelessWidget{
//要显示的内容
final String text;
//数据内容可以通过构造方法传递进来
MyApp(this.text);
//重写build方法 返回你需要的控件
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
//红色背景
color: Colors.red,
//高度 现在没用 会撑满整个屏幕
height: 200,
//宽度 运行效果会撑满整个屏幕
width: 200,
//内容居中
alignment: Alignment.center,
//Text控件
child: new Text(
//Dart语法中 ?? 表示如果text为空,就会返回??号的内容
text ?? "my name is Knight",
textDirection: TextDirection.ltr,//需要加上这句不然报 RichText widgets require a Directionality widget ancestor.
),
);
}
}
Widget
和 Widget
之间通过 child
进行嵌套,有些 Widget
只能有一个 child
。就像上面的 Container
,有些 Widget
可以有多个child
,像 Colum
布局。上面例子根布局是 Container
,Container
嵌套了 Text
。
源码也是只有三个方法:
前两个方法和 StatelessWidget
一样的,而 createState()
这个方法源码注释是:在 Widget
树中给定的位置创建此可变状态的小部件,子类应该重写此方法返回新建的,关联子类的实例。当调用一个 StatefulWidget
,框架就会调用createState
这个方法,当一个 StatefulWidget
从 Widget
树中移除,再次插入树中,那么会再次调用 createState
来创建一个新的 State
对象,这样做简化了State
对象的生命周期。
需要创建管理的是主要是 State
,StatefulWidget
用起来麻烦一些,他需要一个 State
,例子如下:
//继承StatefulWidget
class StateWidget extends StatefulWidget{
@override
State createState(){
return _StateWidget();
}
}
class _StateWidget extends State<StateWidget>{
//重写build方法
@override
Widget build(BuildContext context){
}
}
简单观察上面代码,大致流程还是和 StatelessWidget
一样的,build
方法照样返回Widget
,不过在StatefulWidget
将这个方法放在createState
里面。这里细想一下,也知道为什么要这样做,因为当状态改变,就会回调createState
方法,重新调用build方法重新创建 UI,下面通过每两秒改变 UI 这个例子来加深理解:
import 'package:flutter/material.dart';
//使用`flutter/material.dart` 目的是使用Matrial风格的小控件
import 'dart:async';//记得导库
void main(){
//运行程序
runApp(StateWidget());
}
//控件继承State
class _StateWidget extends State<StateWidget>{
int Number = 0;
String text;
//构造函数
_StateWidget(this.text);
@override
void initState(){
//初始化,这个函数在控件的生命周期内调用一次
super.initState();
print("进入initState");
//3秒后改变text的内容
new Future.delayed(const Duration(seconds: 3),(){
setState(() {
Number++;
text = "已经改变数值,数值现在是$Number";
});
});
}
@override
void dispose(){
//销毁
super.dispose();
print('销毁');
}
@override
void didChangeDependencies(){
//在initState之后调
super.didChangeDependencies();
print('进入didChange');
}
//重写build方法
@override
Widget build(BuildContext context){
return Container(
//红色背景
color: Colors.red,
//内容居中
alignment: Alignment.center,
//Text控件
child: new Text(
//Dart语法中 ?? 表示如果text为空,就会返回??号的内容
text ?? "没改变数值",
textDirection: TextDirection.ltr,//需要加上这句不然报 RichText widgets require a Directionality widget ancestor.
),
);
}
}
上面例子可以知道知道:在State
可以动态更改数据,在调用setState
后,改变的数据会除法Widget
重新构建,上面代码还写了三个生命周期方法,这里简单说一下:
initState
:初始化操作didChangeDependencies
:在initState
之后调用,可以获取其他State
dispose
:销毁平时开发中在build
实现布局的摆放,把数据添加Widget
,通过setState
改变数据。那如果很高频率取改变数据,性能肯定受影响,以下三点可以减少重新构建有状态控件的影响:
树根上尽量不用状态控件,因为如果数据有变化树根每次都更新,那就是整棵树都要重建,把状态用在树叶上,这样更新的时候只会更新自己。
减少build
方法所创建的节点数量和控件数量。
利用缓存,如果子树中不更改,将子树中缓存起来,每次使用其子树时重新使用它,学会重用思想。
尽可能使用const
修饰控件。怎么去选择有状态和无状态,最简单就是可以跟用户进行交互应该使用StatefulWidget
,例如:点击,滑动屏幕信息流数据更新,如果只是仅仅显示数据,那就可以选择使用StatelessWidget
创建一个无状态控件。