Flutter里有一个很是重要的核心理念:一切皆为组件,Flutter的全部元素都是由控件构成的。 与原生开发中控件所表明的含义不一样,Flutter中widget的概念更加普遍,它不只能够表示UI元素,也能够表示一些功能性的组件,如用于手势检测的 GestureDetector widget、用于应用主题数据传递的Theme等等。而原生开发中的控件一般只是指UI元素。因为Flutter主要就是用于构建用户界面的,因此,在大多数时候,咱们能够简单的认为widget就是一个控件,没必要纠结于概念。bash
在正式介绍Flutter的Widget以前,咱们须要理清两个概念,即什么是Widget,什么是Element?框架
Widget的功能是“描述一个UI元素的配置数据,它就是说,Widget其实并非表示最终绘制在设备屏幕上的显示元素,而只是显示元素的一个配置数据。实际上,Flutter中真正表明屏幕上显示元素的类是Element,也就是说Widget只是描述Element的一个配置。而且一个Widget能够对应多个Element,这是由于同一个Widget对象能够被添加到UI树的不一样部分,而真正渲染时,UI树的每个Widget节点都会对应一个Element对象。因此,理解Flutter的Widget须要理清两个概念:less
首先,咱们先来看一下Widget类的声明:ide
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
@override
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
复制代码
从这个Widget类的申明中,咱们能够获得以下一些信息:函数
StatelessWidget是Flutter提供的一个不须要状态更改的widget ,它没有内部状态管理功能。StatelessWidget相对比较简单,它继承自Widget类,重写了createElement()方法。布局
@override
StatelessElement createElement() => new StatelessElement(this);
复制代码
StatelessElement 间接继承自Element类,与StatelessWidget相对应。StatelessWidget一般被用于不须要维护状态的场景,在build方法中经过嵌套其它Widget来构建UI,在构建过程当中会递归的构建其嵌套的Widget。例如:ui
class Echo extends StatelessWidget {
const Echo({
Key key,
@required this.text,
this.backgroundColor:Colors.grey,
}):super(key:key);
final String text;
final Color backgroundColor;
@override
Widget build(BuildContext context) {
return Center(
child: Container(
color: backgroundColor,
child: Text(text),
),
);
}
}
复制代码
按照惯例,widget的构造函数参数应使用命名参数,命名参数中的必要参数要添加@required标注,这样有利于静态代码分析器进行检查。另外,在继承widget时,第一个参数一般应该是Key,另外,若是Widget须要接收子Widget,那么child或children参数一般应被放在参数列表的最后。 而后,咱们能够经过以下方式来使用Echo widget。this
Widget build(BuildContext context) {
return Echo(text: "hello world");
}
复制代码
运行后效果以下图所示: spa
StatefulWidget 是一个可变状态的widget。 使用setState方法管理StatefulWidget的状态的改变。调用setState告诉Flutter框架,某个状态发生了变化,Flutter会从新运行build方法,以便应用程序能够应用最新状态。debug
和StatelessWidget同样,StatefulWidget也是继承自Widget类,并重写了createElement()方法,不一样的是返回的Element 对象并不相同;另外StatefulWidget类中添加了一个新的接口createState()。
下面是StatefulWidget的类定义,以下所示:
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => new StatefulElement(this);
@protected
State createState();
}
复制代码
StatefulElement 间接继承自Element类,它与StatefulWidget相对应(做为其配置数据)。同时,StatefulElement中可能会屡次调用createState()来建立状态(State)对象。
createState() 用于建立和Stateful widget相关的状态,它在Stateful widget的生命周期中可能会被屡次调用。例如,当一个Stateful widget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。
经过上面的讲解,咱们能够得出以下结论:
说到组件,就不得不提到Widgets的State。一般,一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中的保存的状态信息有以下两个做用:
State有两个经常使用属性:widget和context。
和原平生台的控件同样,State也有本身的生命周期。为了加深读者对State生命周期的印象,本节咱们经过一个实例来演示一下State的生命周期。在接下来的示例中,咱们实现一个计数器widget,点击它可使计数器加1,因为要保存计数器的数值状态,因此咱们应继承StatefulWidget,代码以下:
class CounterWidget extends StatefulWidget {
const CounterWidget({
Key key,
this.initValue: 0
});
final int initValue;
@override
_CounterWidgetState createState() => new _CounterWidgetState();
}
复制代码
CounterWidget接收一个initValue整型参数,它表示计数器的初始值。接下来,咱们看一下_CounterWidgetState的实现:
class _CounterWidgetState extends State<CounterWidget> {
int _counter;
@override
void initState() {
super.initState();
//初始化状态
_counter=widget.initValue;
print("initState");
}
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
body: Center(
child: FlatButton(
child: Text('$_counter'),
//点击后计数器自增
onPressed:()=>setState(()=> ++_counter,
),
),
),
);
}
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
}
@override
void deactivate() {
super.deactivate();
print("deactive");
}
@override
void dispose() {
super.dispose();
print("dispose");
}
@override
void reassemble() {
super.reassemble();
print("reassemble");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}
复制代码
接下来,咱们建立一个新路由,在新路由中,咱们只显示一个CounterWidget。
Widget build(BuildContext context) {
return CounterWidget();
}
复制代码
而后,运行应用并打开该路由页面,在新路由页打开后,屏幕中央就会出现一个数字0,而且控制台日志输出以下:
I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build
复制代码
能够看到,在StatefulWidget插入到Widget树时首先被调用的是initState方法。而后,咱们点击⚡️按钮热重载代码,控制台输出日志以下:
I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build
复制代码
能够看到,热重载操做时initState 和didChangeDependencies都没有被调用,而是调用了didUpdateWidget。 接下来,咱们在widget树中移除CounterWidget,并将路由build方法改成:
Widget build(BuildContext context) {
//移除计数器
//return CounterWidget();
//随便返回一个Text()
return Text("xxx");
}
复制代码
而后执行热重载操做,日志以下:
I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose
复制代码
能够看到,在CounterWidget从widget树中移除时,deactive和dispose会依次被调用。
经过上面的示例,咱们将StatefulWidget生命周期整理以下图:
createState:createState必须且仅执行一次,它用来建立state,当建立StatefulWidget时,该放方法就会被执行。
initState:在建立StatefulWidget后,initState是第一个被调用的方法,同createState同样只被调用一次,此时widget的被添加至渲染树,mount的值会变为true,但并无渲染。咱们能够在该方法内作一些初始化操做。
didChangeDependencies:当widget第一次被建立时,didChangeDependencies紧跟着initState函数以后调用,在widget刷新时,该方法不会被调用。它会在“依赖”发生变化时被Flutter Framework调用,这个依赖是指widget是否使用父widget中InheritedWidget的数据。也便是只有在widget依赖的InheritedWidget发生变化以后,didChangeDependencies才会调用。 这种机制可使子组件在所依赖的InheritedWidget变化时来更新自身!好比当主题、locale(语言)等发生变化时,依赖其的子widget的didChangeDependencies方法将会被调用。
build:build会在widget第一次建立时紧跟着didChangeDependencies方法以后和UI从新渲染时被调用。build只作widget的建立操做,若是在build里作其余操做,会影响UI的渲染效果。
StatefulWidget运行中只会调用两个函数,即didUpdateWidget和build。 didUpdateWidget:当组件的状态改变的时候就会调用didUpdateWidget,好比调用了setState。
deactivate:当State对象从树中被移除时,会调用此回调函数,这标志着 StatefulWidget将要执行销毁操做。页面切换时,也会调用它,由于此时State在视图树中的位置发生了变化可是State不会被销毁,而是从新插入到渲染树中。 重写的时候必需要调用 super.deactivate()。
dispose:从渲染树中移除时调用,State会永久的从渲染树中移除,和initState正好相反mount值变味false。这时候就能够在dispose里作一些取消监听操做。
为了方便读者理解,咱们看一下StatefulWidget的生命周期函数调用状况。
生命周期 | 调用次数 | 调用时间 |
---|---|---|
createState | 1 | 组件建立时 |
initState | 1 | 组件建立时 |
didChangeDependencies | n | 组件建立或状态发生变化 |
build | n | 组件建立或UI从新渲染 |
didUpdateWidget | n | 组件建立或UI从新渲染 |
deactivate | n | State对象将要移除时 |
dispose | 1 | state对象被销毁 |
Flutter SDK提供了一套丰富、强大的基础组件,在基础组件库之上Flutter又提供了一套Material风格(Android默认的视觉风格)和一套Cupertino风格(iOS视觉风格)的组件库。使用前只须要导入便可使用:
import 'package:flutter/widgets.dart';
复制代码
Flutter SDK提供了不少功能丰富的基础组件,常见的有以下一些:
众所周知,Material是Android应用默认的视觉风格,Cupertino则是iOS应用的默认视觉风格,为了实现两种不一样的视觉风格,Flutter 在基础组件库之上Flutter又提供了一套Material风格和一套Cupertino风格的组件库,以知足两种不一样设计风格的开发须要。
Material应用程序以MaterialApp 组件开始, 该组件在应用程序的根部建立了一些必要的组件,好比Theme组件,它用于配置应用的主题。 是否使用MaterialApp彻底是可选的,可是使用它是一个很好的作法。在以前的示例中,咱们已经使用过多个Material 组件了,如:Scaffold、AppBar、FlatButton等。
要使用Material 组件,须要先引入它:
import 'package:flutter/material.dart';
复制代码
Flutter也提供了一套丰富的Cupertino风格的组件,尽管目前尚未Material 组件那么丰富,可是它仍在不断的完善中。目前,Flutter提供的Cupertino组件主要有 CupertinoTabBar、 CupertinoActivityIndicator、CupertinoPageScaffold、 CupertinoTabScaffold、 CupertinoTabView 等 。 关于Cupertino组件,你们能够参考官方的介绍:Cupertino (iOS风格) Widgets