Flutter
Hello World
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}应用入口:
void main() => runApp(MyApp());runApp接受一个Widget参数
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
//platform: TargetPlatform.android,
),
home: const MyHomePage(),
);
}
}在Flutter中,大多数东西都是widget(叫“组件”或“部件”),包括对齐(Align)、填充(Padding)等都是以Widget形式提供的
Flutter在构建页面时,会调用build方法,widget的主要工作时提供一个build()方法来描述如何构建UI界面(通常是通过组合、拼装其他基础widget)
MaterialApp 是Material 库中提供的 Flutter APP 框架,通过它可以设置应用的名称、主题、语言、首页及路由列表等。MaterialApp也是一个 widget。
home为Flutter应用的首页,也是一个widget
初识Widget
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
...
}MyHomePage是应用的首页,继承自StatefulWidget类,表示一个有状态的组件。组件有有状态(Stateful)和无状态(Stateless)。Stateful widget可以拥有状态,这些状态在widget生命周期中是可变的,而Statless widget是不可变的。
Stateful widget至少由两个类组成:一个StatefulWidget类,一个State类;StatefulWidget类本身是不变的,但是State类中持有的状态在Widget生命周期中可能会发生变化。
MyHomePageState类是MyHomePage类对应的状态类,MyHomePage中并没有build方法,而是将build方法挪到了MyHomePageState方法中,这个后面解答。
State类
counter.dart文件中_MyHomePageState类解析:
import 'package:flutter/material.dart';
typedef CounterRoute = MyApp;
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}组件的状态,由于我们只需要维护一个点击次数计算机,所有定义了一个_counter状态
状态自增函数_incrementCounter,当调用该函数时,先自增_counter,然后再调用setState方法,告知Flutter框架,状态发生了改变,Flutter框架收到通知后,会执行build方法,来根据新的状态重新构建页面,Flutter对此方法进行了优化,执行很快,所以可以重新构建任何需要更新的东西,而无需分别去修改各个widget
构建UI的逻辑在build方法中,当MyHomePage第一次被创建时,_MyHomePageState类会被创建,当初始化完成后,Flutter框架会调用widget的build方法构建widget数,最终渲染到屏幕上。
Scaffold是Material库中提供的页面脚手架,提供了默认的导航栏,标题和包含主频木widget树的body属性,组件树可以很复杂,此后默认路由都是通过Scaffold创建的body的组件树中包含了一个Center组件,可以将其子组件树对其到屏幕中心
Column的作用是将其所有子组件沿屏幕垂直方向依次排列,第一个子组件Text,固定显示文本,第二个Text显示_counter状态的数值
floatingActionButton是页面右下角带+的悬浮按钮,他的onPressed属性接受一个回调函数,代表点击之后的处理器。
为什么将build方法放到State中,而不是StatefulWidget中?
这样做提高了开发的灵活性,如果将build方法放在StatefulWidget中,会出现下面两个问题:
状态访问不便
如果build方法在StatefulWidget中,由于构建界面的时候需要依赖State,所以build方法必须加一个State参数
Widget build(BuildContext context, State state){
//state.counter
...
}如果这样的话只能将State的所有状态声明为公开的状态,这样才能在State类外部访问,但是将状态设置为公开后,状态将不再具有私密性,会导致对状态的修改不可控。但是如果将build方法放在State中的话,构建过程不仅可以直接访问状态,而且不需要公开私有状态。
继承
StatefulWidget不变
例如,Flutter 中有一个动画 widget 的基类AnimatedWidget,它继承自StatefulWidget类。AnimatedWidget中引入了一个抽象方法build(BuildContext context),继承自AnimatedWidget的动画 widget 都要实现这个build方法。现在设想一下,如果StatefulWidget 类中已经有了一个build方法,正如上面所述,此时build方法需要接收一个 State 对象,这就意味着AnimatedWidget必须将自己的 State 对象(记为_animatedWidgetState)提供给其子类,因为子类需要在其build方法中调用父类的build方法,代码可能如下:
class MyAnimationWidget extends AnimatedWidget{
@override
Widget build(BuildContext context, State state){
//由于子类要用到AnimatedWidget的状态对象_animatedWidgetState,
//所以AnimatedWidget必须通过某种方式将其状态对象_animatedWidgetState
//暴露给其子类
super.build(context, _animatedWidgetState)
}
}
这样很显然是不合理的,因为
AnimatedWidget的状态对象是AnimatedWidget内部实现细节,不应该暴露给外部。如果要将父类状态暴露给子类,那么必须得有一种传递机制,而做这一套传递机制是无意义的,因为父子类之间状态的传递和子类本身逻辑是无关的。
综上所述,可以发现,对于StatefulWidget,将build方法放在 State 中,可以给开发带来很大的灵活性。
Widget
Widget比原生种的控件更广泛,Flutter中万物皆为Widget,通过Widget嵌套的方法构建UI和进行事件处理
在Flutter中,widget的功能是“描述一个UI元素的配置信息”,他就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,所谓的配置信息就是Widget接收的参数。
@immutable // 不可变的
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key? key;
@protected
@factory
Element createElement();
@override
String toStringShort() {
final String type = objectRuntimeType(this, 'Widget');
return key == null ? type : '$type-$key';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
}
@override
@nonVirtual
bool operator ==(Object other) => super == other;
@override
@nonVirtual
int get hashCode => super.hashCode;
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
...
}@immutable代表Widget是不可变的,这会限制Widget中定义的属性必须是不可变的,Flutter中如果属性发生变化则会重新构建Widget树,所以允许Widget属性变化是没有意义的,因为一旦Widget自己的属性发生了变化,自己就会被替换
Widget继承自DiagnosticableTree,即诊断树,主要作用是提供调试信息
Key类似于Vue中的Key,主要作用是决定是否在下一次build时复用就的Widget,决定的条件在canUpdate方法中
createElement,正如前文所述,一个widget对应多个Element,Flutter框架在构建UI树时,会先调用此方法生成对应节点的Element对象,此方法时隐式调用的,在开发过程中用不到
debugFillProperties复写父类的方法,主要是设置诊断树的一些特性
canUpdate是一个静态方法,主要用于在Widget树重新build时复用旧的widget,具体来说是,是否用新的widget对象去更新旧的UI树上对应的Element对象的配置,通过其源码可以看到,只要newWidget与oldWidget的runtimeType和key同时相等时就会用new widget去更新Element对象的配置,否则就会创建新的Element
Widget本身是一个抽象类,其最核心的就是定义了createElement接口,在开发中一般使用StatefulWidget和StatelessWidget间接继承Widget。
Flutter中的四棵树
Flutter框架的布局,绘制流程是这样的:
根据Widget树生成一个Element树,Element树中节点都继承自Element类
根据Element树生成Render树(渲染树),渲染树中的节点都继承自RenderObject类
根据渲染树生成Layer树,然后上屏显示,Layer树中的节点都继承自Layer类
真正的布局和渲染逻辑在Render树中,Element是Widget和RenderObject的粘合剂,三棵树中,Widget和Element时一一对应的,但并不和RenderObject一一对应
StatelessWidget
StatelessWidget继承自Widget类,重写了createElement()方法
@override
StatelessElement createElement() => StatelessElement(this);StatelessElement简介继承Element类,与StatelessWidget相对应。StatelessWidget用于不需要维护状态的场景,它通常在build方法中嵌套其他widget来构建UI,在构建过程中会递归的构建其嵌套的widget
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
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),
),
);
}
}上面的代码实现了一个回显字符串的Echo widget
按照惯例,widget的构造函数参数应使用命名参数,命名参数中的必须参数要添加required关键字,这样有利于静态代码分析器检查,在继承Widget时,第一个参数通常是Key,另外,如果widget需要接收子widget,child或者children参数通常被放在参数列表最后,同样是按照惯例,widget的属性应尽可能的被声明为final,防止被意外改变
Context
build方法有一个context参数,它是一个BuildContext类的一个实例,表示当前widget在widget树中的上下文,每一个widget都会对应一个context对象(因为每一个widget都是widget树上的一个节点)。实际上,context是当前widget在widget树中位置中执行相关操作的一个句柄(handle),比如,它提供了从当前widget开始向上遍历widget树以及按照widget类型查找父级widget的方法。
class ContextRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Context测试"),
),
body: Container(
child: Builder(builder: (context) {
// 在 widget 树中向上查找最近的父级`Scaffold` widget
Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
// 直接返回 AppBar的title, 此处实际上是Text("Context测试")
return (scaffold.appBar as AppBar).title;
}),
),
);
}
}StatefulWidget
和StatelessWidget一样,也是继承自Widget类,重写了createElement方法,不同的是,StatefulWidget中添加了一个createState接口
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
@override
StatefulElement createElement() => StatefulElement(this);
@protected
State createState();
}StatefulElement间接继承Element类,与StatefulWidget相对应,StatefulElement中可能多次调用createElement来创建State对象
createState方法用于创建和Stateful相关的状态,在StatefulWidget的生命周期中可能会被多次调用
State
一个StatefulWidget类会对应一个State类,State是要维护的状态,State中保存的状态信息可以在widget构建时可以被同步读取,在widget生命周期中可以被改变,当State被改变时,可以手动调用其setState方法通知Flutter框架状态发生改变,Flutter框架在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新UI的目的
State中有两个常用属性:
widget,表示与该State实例关联的widget实例,由Flutter框架动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的widget实例在重新构建时可能会变化,但是State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter框架会动态设置State,widget为新的widget实例。
context,StatefulWidget对应的BuildContext,作用同StatelessWidget的BuildContext
State生命周期
理解State的生命周期对flutter开发非常重要。
仍然以计数器为例,要保存计数状态,所以继承StatefulWidget
class CounterWidget extends StatefulWidget {
const CounterWidget({Key? key, this.initValue = 0});
final int initValue;
@override
_CounterWidgetState createState() => _CounterWidgetState();
}ConterWidget接收一个initValue整形参数,表示计算器初始值,State代码:
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
@override
void initState() {
super.initState();
//初始化状态
_counter = widget.initValue;
print("initState");
}
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
body: Center(
child: TextButton(
child: Text('$_counter'),
//点击后计数器自增
onPressed: () => setState(
() => ++_counter,
),
),
),
);
}
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget ");
}
@override
void deactivate() {
super.deactivate();
print("deactivate");
}
@override
void dispose() {
super.dispose();
print("dispose");
}
@override
void reassemble() {
super.reassemble();
print("reassemble");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
}接下来创建一个新的路由,在路由中,只显示一个CounterWidget
class StateLifecycleTest extends StatelessWidget {
const StateLifecycleTest({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const CounterWidget();
}
}打开路由界面,屏幕中会出现一个数字0,控制台输出
I/flutter (23216): initState
I/flutter (23216): didChangeDependencies
I/flutter (23216): build可以看到,在StatefulWidget插入到widget树时,首先会调用initState方法
然后点击热加载,控制台输出
I/flutter (23216): reassemble
I/flutter (23216): didUpdateWidget
I/flutter (23216): build可以看到,initState和didChangeDependencies都没有被调用,而didUpdateWidget被调用
我们在路由中移除CounterWidget,随便添加一个widget
@override
Widget build(BuildContext context) {
// return const CounterWidget();
return Text("text");
}控制台输出:
I/flutter (23216): reassemble
I/flutter (23216): deactivate
I/flutter (23216): dispose可以看到,deactivate和dispose被依次调用
由上可以得出widget各个回调函数的作用:
initState:当widget第一次插入到widget树时会被调用,对于每一个State对象,Flutter只会调用一次该回调,通常在该回调中做一些一次性操作,如状态初始化,订阅子树的事件通知等。【不能在该回调中调用
BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后, widget 树中的InheritFrom widget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。】didChangeDependencies:当State对象的依赖发生变化时会被调用,典型的场景是当系统语言Locale或应用主题改变时,Flutter框架会通知widget调用此回调。注意,组件第一次被创建后挂载的时候(包括重创建)对应的didChangeDependencies也会被调用
build:用于构建widget,会在initState、didUpdateWidget、setState、didChangeDependencies、State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其他位置之后被调用
reassemble:专门为开发调试而使用的,在热重载的时候会被调用
didUpdateWidget:在widget重新构建时,Flutter框架会调用canUpdate来检测widget树中同一位置的新旧节点,然后决定是否更新,如果canUpdate返回true则调用此回调,canUpdate会比较新旧widget的key和runtimeType,相同时返回true
deactivate:当State对象从树中移除时,会调用此回调。在一些场景下,Flutter框架会将State重新插入树中,例如包含此State对象的子树在树的一个位置移动到另一个位置时,如果移除后没有重新插入,则紧接着调用dispose方法
dispose:当State对象从树种被永久移除时调用,通常在此回调中释放资源

注意:在继承StatefulWidget重写方法时,对于包含@mustCallSuper标注的父类方法,都要在子类方法中调用父类方法。