Widget的分类
1、有无状态分类
按照否是有状态的分类方式,widget可以分为无状态StatelessWidget
和有状态StatefulWidget
,StatelessWidget
和StatefulWidget
的Element
都是ComponentElement
,并且都不具备RenderObject
。两者都是调用build
方法,但是StatelessWidget
只是实现简单的组件,而StatefulWidget
则实现复杂的、有状态的组件,它的状态和数据都保存在State
里面。
无状态组件
一个无状态部件在Flutter应用程序的运行期间不能改变其状态。这意味着无状态小组件在应用程序运行时不能被重新绘制,外观和属性在小组件的整个生命周期内保持不变。我们创建一个不需要反复重绘部件的应用程序时,我们会使用一个无状态的部件。
class StatelessElement extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Test Stateless'),
backgroundColor: Colors.blue,
),
body: Container(
// child: Widget(),
),
),
);
}
}
无状态组件覆盖了build
方法,build
方法接受BuildContext
作为参数,并返回一个 widget。在表面上看 每次数据更新都会执行build
方法,但实际上,在组件树中只有StatelessElement
在初始化的时候才会调用build
方法。
有状态组件
在有状态类中,每次的数据发生变动,在组件树中只会调用state下的build方法进行重新渲染。这时候就能保存state中的状态。我们创建一个需要反复重绘部件的应用程序时,我们会使用一个有状态的部件。
//有状态组件的基本格式
class StatefulElement extends StatefulWidget{
@override
State<StatefulWidget> createState()=>_StatefulElementState();
}
class _StatefulElementState extends State<StatefulElement>{
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
//完整的有状态组件
class StatefulElement extends StatefulWidget{
const StatefulElement({super.key});
@override
State<StatefulWidget> createState()=>_StatefulElementState();
}
class _StatefulElementState extends State<StatefulElement>{
int _counter = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
Text('$_counter'),
ElevatedButton(
onPressed: (){
setState(() {
_counter++;
});
}, child: const Text('按钮'),
)
],
),
),
),
);
}
}
setState()
是一个在有状态部件类中调用的方法,当每次执行setState()
时,StatefulElement
组件都会调用build
方法进行将改变的数据进行重新渲染,以此来保证state
中的属性值的保存。
State生命周期
StatefulWidget
的内部逻辑与状态,由StatefulWidget
的createState
创建。
一个 StatefulWidget
类会对应一个 State
类,State
表示与其对应的 StatefulWidget
要维护的状态,在State
中保存状态信息。
State方法概述
initState
描述:当 widget
第一次插入到 widget
树时会被调用,对于每一个State
对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。
注意:不能在该方法中调用BuildContext.dependOnInheritedWidgetOfExactType
,该方法用于在 widget
树上获取离当前 widget
最近的一个父级InheritedWidget
。但是此方法被调用后会立即调用didChangeDependencies,在didChangeDependencies
可以使用BuildContext.dependOnInheritedWidgetOfExactType
。
didChangeDependencies()
描述:当此State
对象的依赖项(InheritedWidget
)更改时调用。在之前build()
中包含了一个InheritedWidget
,然后在之后的build()
中Inherited widget
发生了变化,那么此时InheritedWidget
的子 widget
的didChangeDependencies()
回调都会被调用。
build()
描述:StatefulElement
通过build()
返回的widget
并通过调用updateChild
来更新自己。
- 在调用
initState()
后 - 在调用
didUpdateWidget()
后。 - 在调用
setState()
后 State
对象的依赖(dependency
)改变;比如:之前build
时引用的InheritedWidget
更改。- 调用了
deactivate
后,然后将State
对象重新插入树中的另一个位置
reassemble()
描述:此回调是专门为了开发调试而提供的,在热重载(hot reload
)时会被调用,调用后build方法也将被调用,此回调在Release
模式下永远不会被调用,无需在此方法中做任何操作。
didUpdateWidget ()
描述:State
对象存在,并且符合Widget.canUpdate
的情况下对StatefulWidget
进行更新。didUpdateWidget
方法最终会调用build
方法,因此在此方法中调用setState
是多余的。如果重写此方法,请确保调用super.didUpdateWidget(oldWidget)
。
deactivate()
描述:当State
对象从树中被移除时,会调用此回调。如果移除后没有重新插入到树中则紧接着会调用dispose()
方法。
dispose()
描述:当 State
对象从树中被永久移除时调用;通常在此回调中释放资源。
//详细代码
class StatefulElement extends StatefulWidget{
const StatefulElement({super.key});
@override
State<StatefulWidget> createState()=>_StatefulElementState();
}
class _StatefulElementState extends State<StatefulElement>{
int _counter = 0;
@override
void initState(){
super.initState();
//初始化操作
//DO:
}
@override
void didChangeDependencies(){
super.didChangeDependencies();
//依赖发生更改时执行
//DO:
}
@override
void reassemble(){
super.reassemble();
//重新安装时执行,一般在调试的时候用,在发正式版本时 不会执行
}
@override
void didUpdateWidget(covariant StatefulElement oldWidget){
super.didUpdateWidget(oldWidget);
//组件发生变更时调用,当父组件有变动,子组件也会执行此方法
//DO:
}
@override
void deactivate(){
super.deactivate();
//停用
}
@override
void dispose(){
super.dispose();
//销毁
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
Text('$_counter'),
ElevatedButton(
onPressed: (){
setState(() {
_counter++;
});
}, child: const Text('按钮'),
)
],
),
),
),
);
}
}
2、RenderObject分类
StatelessWidget
和 StatefulWidget
都是用于组合其他组件的,它们本身没有对应的 RenderObject
,RenderObject
的主要职责是布局和绘制,布局的原则是,Constraints
向下,Sizes
向上,父节点设置本节点的位置。
RenderObject Tree
是底层的布局和绘制系统。大部分时候我们并不需要直接和RenderObject Tree
交互,而是使用Widget
,然后Flutter Framework
会自动构建RenderObject Tree
。
如上图所示,RenderObject
主要分为四类:
RenderView
RenderView
是整个RenderObject Tree
的根节点,代表了整个输出界面。
RenderSliver
是所有实现了滑动效果的RenderObject
基类,其常用子类有RenderSliverSingleBoxAdapter
、RenderSliverToBoxAdapter
等。关键参数是SliverConstraints
和SliverGeometry
,SliverConstraints
和BoxConstraints
对比,BoxContraints
只包括了,最大/最小的高度/宽度。但是SliverConstraints
则更多的是滑动方向、滑动偏移、滑动容器大小、容器缓存大小和位置等相关参数。
Size
和SliverGeometry
进行对比,Size
只包括了宽和高。但是SliverGeometry
包括了滑动方位、绘制范围、偏移等相关参数。
RenderAbstractViewport
RenderAbstractViewport
是一类接口,此类接口为只展示其部分内容的RenderObject设计
RenderBox
RenderBox
是一个采用2D笛卡尔坐标系的RenderObject
的基类,一般的RenderOBject
都是继承自RenderBox
,例如RenderStack
等,它也是一般自定义RenderObject
的基类。
RenderBox
会根据parent
的constraints
大小判断自己的布局方法,然后将constraints
传递给child
得到child
的大小,最后根据child
返回的Size
决定自己的Size
,如果没有child
,就使用自己的Size
。用于那些不涉及的滚动的控件布局,他的两个关键参数就是BoxConstraints
和Size
。
3、单元素与多元素分类
根据的child
是否支持单个/多个child
又可以分为SingleChildRenderObjectWidget
和MultiChildRenderObjectWidget
。
SingleChildRenderObjectWidget
单元素顾名思义就是只有一个Widget
,SingleChildRenderObjectWidget
单元素有经常使用的:Clip
、Opacity
、Padding
、Align
、SizededBox
、ConstrainedBox
、DecoratedBox
等。SingleChildRenderObjectWidget
继承RenderObjectWidget
,绘制流程是通过RenderObject
计算出自身的最大、最小宽高,并且通过performLayout
综合得到child
返回的Size
、最后在进行绘制。
//定义
class CustomCenter extends SingleChildRenderObjectWidget{
//const CustomCenter({super.key});
const CustomCenter({Key? key, Widget? child}):super(key: key,child: child);
@override
RenderObject createRenderObject(BuildContext context) {
// TODO: implement createRenderObject
return RenderCustomCenter();
}
}
class RenderCustomCenter extends RenderShiftedBox{
}
MultiChildRenderObjectWidget
包含多个子Widget
,一般都有一个children
参数,接受一个Widget
数组。如Row
、Column
、Stack
、RichText
等。
MultiChildRenderObjectWidget
实现起来要复杂许多,主要复杂的部分在于RenderBox
,我们需要自定义一个类继承于RenderBox
,同时还得混入ContainerRenderObjectMixin
和RenderBoxContainerDefaultsMixin
,然后去重写他的两个方法:setupParentData
和performLayout
,然后在重写paint
方法,调用系统绘制方法,完成绘制操作。这里就大概了解一下吧。
//定义
class UpDownBox extends MultiChildRenderObjectWidget{
UpDownBox({Key? key,required List<Widget> list}):assert(list.length==2,"只能传两个child"),super(key: key,children: list);
@override
RenderObject createRenderObject(BuildContext context) {
// TODO: implement createRenderObject
return RenderUpDownBox();
}
}
class UpDownParentData extends ContainerBoxParentData<RenderBox>{}
class RenderUpDownBox extends RenderBox{
}