flutter3.0学习笔记

无处不在的Widget

Preview
  • Widget的分类
  • 1、有无状态分类
  • 无状态组件
  • 有状态组件
  • State生命周期
  • State方法概述
  • 2、RenderObject分类
  • 3、单元素与多元素分类
  • SingleChildRenderObjectWidget
  • MultiChildRenderObjectWidget

Widget的分类

1、有无状态分类

按照否是有状态的分类方式,widget可以分为无状态StatelessWidget和有状态StatefulWidgetStatelessWidgetStatefulWidgetElement都是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的内部逻辑与状态,由StatefulWidgetcreateState创建。

一个 StatefulWidget 类会对应一个 State 类,State表示与其对应的 StatefulWidget 要维护的状态,在State 中保存状态信息。

image.png

State方法概述
  1. initState

描述:当 widget 第一次插入到 widget 树时会被调用,对于每一个State对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。

注意:不能在该方法中调用BuildContext.dependOnInheritedWidgetOfExactType,该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget。但是此方法被调用后会立即调用didChangeDependencies,在didChangeDependencies可以使用BuildContext.dependOnInheritedWidgetOfExactType

  1. didChangeDependencies()

描述:当此State对象的依赖项(InheritedWidget)更改时调用。在之前build() 中包含了一个InheritedWidget,然后在之后的build()Inherited widget发生了变化,那么此时InheritedWidget的子 widgetdidChangeDependencies()回调都会被调用。

  1. build()

描述:StatefulElement通过build()返回的widget并通过调用updateChild来更新自己。

  • 在调用initState()
  • 在调用didUpdateWidget()后。
  • 在调用setState()
  • State对象的依赖(dependency )改变;比如:之前build时引用的InheritedWidget更改。
  • 调用了deactivate后,然后将State对象重新插入树中的另一个位置
  1. reassemble()

描述:此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,调用后build方法也将被调用,此回调在Release模式下永远不会被调用,无需在此方法中做任何操作。

  1. didUpdateWidget ()

描述:State对象存在,并且符合Widget.canUpdate的情况下对StatefulWidget进行更新。didUpdateWidget方法最终会调用build方法,因此在此方法中调用setState是多余的。如果重写此方法,请确保调用super.didUpdateWidget(oldWidget)

  1. deactivate()

描述:当State对象从树中被移除时,会调用此回调。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。

  1. 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分类

StatelessWidgetStatefulWidget 都是用于组合其他组件的,它们本身没有对应的 RenderObjectRenderObject的主要职责是布局和绘制,布局的原则是,Constraints向下,Sizes向上,父节点设置本节点的位置。

RenderObject Tree是底层的布局和绘制系统。大部分时候我们并不需要直接和RenderObject Tree交互,而是使用Widget,然后Flutter Framework会自动构建RenderObject Tree

image.png

如上图所示,RenderObject主要分为四类:

  1. RenderView

RenderView是整个RenderObject Tree的根节点,代表了整个输出界面。

  1. RenderSliver

是所有实现了滑动效果的RenderObject基类,其常用子类有RenderSliverSingleBoxAdapterRenderSliverToBoxAdapter等。关键参数是SliverConstraintsSliverGeometrySliverConstraintsBoxConstraints对比,BoxContraints只包括了,最大/最小的高度/宽度。但是SliverConstraints则更多的是滑动方向、滑动偏移、滑动容器大小、容器缓存大小和位置等相关参数。

SizeSliverGeometry进行对比,Size只包括了宽和高。但是SliverGeometry包括了滑动方位、绘制范围、偏移等相关参数。

  1. RenderAbstractViewport

RenderAbstractViewport是一类接口,此类接口为只展示其部分内容的RenderObject设计

  1. RenderBox

RenderBox是一个采用2D笛卡尔坐标系的RenderObject的基类,一般的RenderOBject都是继承自RenderBox,例如RenderStack等,它也是一般自定义RenderObject的基类。 RenderBox会根据parentconstraints大小判断自己的布局方法,然后将constraints传递给child得到child的大小,最后根据child返回的Size决定自己的Size,如果没有child,就使用自己的Size。用于那些不涉及的滚动的控件布局,他的两个关键参数就是BoxConstraintsSize

3、单元素与多元素分类

根据的child是否支持单个/多个child又可以分为SingleChildRenderObjectWidgetMultiChildRenderObjectWidget

SingleChildRenderObjectWidget

单元素顾名思义就是只有一个WidgetSingleChildRenderObjectWidget单元素有经常使用的:ClipOpacityPaddingAlignSizededBoxConstrainedBoxDecoratedBox等。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数组。如RowColumnStackRichText等。

MultiChildRenderObjectWidget实现起来要复杂许多,主要复杂的部分在于RenderBox,我们需要自定义一个类继承于RenderBox,同时还得混入ContainerRenderObjectMixinRenderBoxContainerDefaultsMixin,然后去重写他的两个方法:setupParentDataperformLayout,然后在重写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{

}