flutter的四棵树
widgetTree、elementTree、renderTree 三棵渲染树的关系
渲染树在上屏前会生成一棵 Layer 树
StatelessWidget
StatefulWidget
- 继承于
widget,重写createElement(),不同的是返回的Element对象并不相同;另外StatefulWidget类中添加了一个新的接口createState(); - 对于
StatefulWidget,将build方法放在setState()可以提高开发灵活性,具体指的是build里面可以直接拿到属性;
State
一个 StatefulWidget 类会对应一个 State 类,State表示与其对应的 StatefulWidget 要维护的状态,State 中的保存的状态信息可以:
- 在 widget 构建时可以被同步读取。
- 在 widget 生命周期中可以被改变,当State被改变时,可以手动调用其
setState()方法通知Flutter 框架状态发生改变,Flutter 框架在收到消息后,会重新调用其build方法重新构建 widget 树,从而达到更新UI的目的。 下面我们来看看各个回调函数:
state的生命周期
-
initState:当 widget 第一次插入到 widget 树时会被调用,对于每一个State对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后, widget 树中的InheritFrom widget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。 -
didChangeDependencies():当State对象的依赖发生变化时会被调用;例如:在之前build()中包含了一个InheritedWidget(第七章介绍),然后在之后的build()中Inherited widget发生了变化,那么此时InheritedWidget的子 widget 的didChangeDependencies()回调都会被调用。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter 框架会通知 widget 调用此回调。需要注意,组件第一次被创建后挂载的时候(包括重创建)对应的didChangeDependencies也会被调用。 -
build():此回调读者现在应该已经相当熟悉了,它主要是用于构建 widget 子树的,会在如下场景被调用:- 在调用
initState()之后。 - 在调用
didUpdateWidget()之后。 - 在调用
setState()之后。 - 在调用
didChangeDependencies()之后。 - 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其他位置之后。
- 在调用
-
reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。 -
didUpdateWidget ():在 widget 重新构建时,Flutter 框架会调用widget.canUpdate来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新,如果widget.canUpdate返回true则会调用此回调。正如之前所述,widget.canUpdate会在新旧 widget 的key和runtimeType同时相等时会返回true,也就是说在在新旧 widget 的key和runtimeType同时相等时didUpdateWidget()就会被调用。 -
deactivate():当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter 框架会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。 -
dispose():当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。
StatefulWidget 生命周期如图2-5所示:
注意:在继承
StatefulWidget重写其方法时,对于包含@mustCallSuper标注的父类方法,都要在子类方法中调用父类方法。
在 widget 树中获取State对象
context中获取state
context对象有一个findAncestorStateOfType()方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的StatefulWidget对应的State对象.通过context.findAncestorStateOfType获取 StatefulWidget 的状态的方法是通用的,我们并不能在语法层面指定 StatefulWidget 的状态是否私有,所以在 Flutter 开发中便有了一个默认的约定:如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State不希望暴露,则不提供of方法.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("我是SnackBar")),
);
通过GlobalKey
- GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了
GlobalKey,那么我们便可以通过globalKey.currentWidget获得该 widget 对象、globalKey.currentElement来获得 widget 对应的element对象,如果当前 widget 是StatefulWidget,则可以通过globalKey.currentState来获得该 widget 对应的state对象。
注意:使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复。
状态管理
如何决定使用哪种管理方法?下面是官方给出的一些原则可以帮助你做决定:
- 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
- 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
- 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。
在 Widget 内部管理状态封装性会好一些,而在父 Widget 中管理会比较灵活。有些时候,如果不确定到底该怎么管理状态,那么推荐的首选是在父 Widget 中管理(灵活会显得更重要一些)
路由管理
MaterialPageRoute
MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRoute是 Material组件库提供的组件,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画
Navigator
Navigator是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。- 最常用的两个方法:
Future push(BuildContext context, Route route)将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。bool pop(BuildContext context, [ result ])将栈顶路由出栈,result为页面关闭时返回给上一个页面的数据。
Navigator.push(BuildContext context, Route route)==Navigator.of(context).push(Route route)
// var result = await Navigator.pushNamed(context, "new_page");
// print("路由返回值: $result");
var result = await Navigator.of(context).pushNamed('new_page');
命名路由
路由表
- 首先需要注册路由,路由表定义
Map<String, WidgetBuilder> routes;,注册代码如下:
MaterialApp(
initialRoute:"home", //initialRoute主要功能主要是控制app启动的时候进入的是哪个页面。
theme: ThemeData(
routes:{ //注册路由表
"new_page":(context) => NewRoute(),
"home":(context) => MyHomePage(title: 'Flutter Demo Home Page'), //注册首页路由
}
),
);
...
//路由跳转以及传值
var result = await Navigator.of(context).pushNamed("new_page",arguments: 'parameterFromLastRoute');
print("路由返回值: $result");
.....
///新路由接收传过来的值
var args=ModalRoute.of(context)?.settings.arguments.toString();
通过路由名称来打开新路由
// var result = await Navigator.pushNamed(context, "new_page");
// print("路由返回值: $result");
var result = await Navigator.of(context).pushNamed('new_page');
路由生成钩子
这个博客的用法值得参考flutter onGenerateRoute原生路由简单封装及页面传参
包管理
- Flutter 使用配置文件
pubspec.yaml(位于项目根目录)来管理第三方依赖包(类似cocoapods),注意下图的添加位置
布局类组件
tip: 向下👇传递约束。向上👆传递尺寸。父节点设置位置
遇到一些小问题
- 设置边框颜色,参考 链接
decoration: BoxDecoration(
color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
border: _highlight
? Border.all(
// color: Colors.lightGreen[700], //报错,改成下面的方式ok
color: (Colors.blue[300])!,
width: 10.0,
)
: null,
),
=========
主要学习来源 - flutter中文网