小白的flutter之路(二)

本文深入探讨Flutter中的基本组件StatefulWidget、StatelessWidget和RenderObjectWidget,以及与其对应的Element对象。首先,介绍了StatefulWidget和StatelessWidget的构建原理,它们通过createElement()创建Element对象。接着,详细阐述了RenderObjectWidget,它用于渲染组件,创建RenderObject对象,并涉及布局和绘制的过程。文章进一步解释了RenderObject对象的角色,包括布局、绘制、节点管理和事件处理。最后,讨论了Element系列,包括RenderObjectElement和ComponentElement,以及它们在构建和更新过程中的作用。
摘要由CSDN通过智能技术生成

       上一节介绍了dart的安装和使用,这一节让我们一起正式进入flutter的世界里。好的,话不多说,让我们一起开启flutter的探索之旅吧!

第一节:Flutter基本组件StatefulWidget和StatelessWidget


        第一节,我们先一起来熟悉一下flutter中遍地的widget,了解一下它的结构和特点。

        我们在开始学习flutter的时候,最先接触到的widget是StatefulWidget和StatelessWidget这两个基本组件,既然如此,那我们先来单独解析一下这两个组件的基本结构。

abstract class StatefulWidget extends Widget {
  
   const StatefulWidget({ Key key }) : super(key: key);
  
   @override
   StatefulElement createElement() => StatefulElement(this);

   @protected
   State createState();
}
abstract class StatelessWidget extends Widget {
 
   const StatelessWidget({ Key key }) : super(key: key);

   @override
   StatelessElement createElement() => StatelessElement(this);

   @protected
   Widget build(BuildContext context);
}

我们从上面它们的实现中可以了解到,代码量仅仅数行,其都继承自Widget组件类,并且都实现了来自基类Widget中的createElement()函数,创建出一个element对象。下面是基类Widget的代码结构:

abstract class Widget extends DiagnosticableTree {
  
   const Widget({ this.key });
 
   final Key key;
  
   @protected
   Element createElement();

   static bool canUpdate(Widget oldWidget, Widget newWidget) {
     return oldWidget.runtimeType == newWidget.runtimeType
         && oldWidget.key == newWidget.key;
   }
}

基类Widget的代码量也是极其少的,它继承自DiagnosticableTree类,而DiagnosticableTree根据它名称含义,就是可诊断的树,无非就是产生一些有关widget配置数据的诊断信息(这个就不具体介绍了)。我们重点关注成员属性key和抽象函数createElement()还有一个静态函数canUpdate()。这也表明了只要实现了Widget类的组件都具备可配置key创建element元素的特性,而静态函数canUpdate()主要用来比较两个widget组件的类型和配置的key是否都一致。

好,我们言归正传,继承了Widget组件的StatelessWidget和StatefulWidget的不同点是什么呢?就他们命名的含义来说:StatelessWidget意为无状态的组件;而StatefulWidget意为有状态的组件。无状态的组件StatelessWidget抽象出一个build()函数,该build函数需要传入一个BuildContext对象,并且返回一个Widget类型的组件。接触过flutter的童鞋们都知道build()是写flutter程序必需的函数,可以构建出我们想要的用户界面。那么,StatefulWidget的build()函数又在哪里?我们从StatefulWidget的代码结构可以了解到,虽然StatefulWidget没有build()函数,但是他有一个StatelessWidget不具备的函数createState(),该函数返回一个State对象,这就是StatelessWidget叫做有状态对象的实现之一,我们具体来看一下State类的代码实现:

//继承State对象的子类可以指明泛型T的具体类型,该T类型必须是StatefuleWidget组件类的子类型。
//通过对泛型T和内部成员属性的理解,可以得知以下两点信息:
//1.任何继承State的子类都拥有一个具备StatefuleWidget特性的组件。
//2.任何继承State的子类都拥有一个具备StatefuleElement特性的元素。
abstract class State<T extends StatefulWidget> extends Diagnosticable {
  
  //可以保存一个widget组件对象
  T get widget => _widget;
  T _widget;
  
  //可以保存一个BuildContext类型的上下文,
  //然而这里的上下文实质上就是一个StatefulElement元素对象。
  //这说明了任何StatefulElement都具有BuildContext特性。
  BuildContext get context => _element;
  StatefulElement _element;

  //mounted的值跟元素是否为空相关
  bool get mounted => _element != null;
  
  //初始化状态
  //@protected注释表明该函数可以被子类重写
  //@mustCallSuper注释似乎在告诉子类,如果你想重写我,
  //必须使用super.initState()调用到我的功能。
  @protected
  @mustCallSuper
  void initState() {}

  //当组件已经更新的时候会触发该函数的调用,
  //这里可以拿着新组件widget和旧组件oldWidget
  //处理一些你认为需要的逻辑(一般可以用来做数据优化)
  @mustCallSuper
  @protected
  void didUpdateWidget(covariant T oldWidget) { }

  //意为重新装配,
  //可以实现你认为比较重要的处理过程,
  //该函数的触发点在后面小节中可能会介绍
  @protected
  @mustCallSuper
  void reassemble() { }

  //设置状态
  //1.首先会调用到传进来的函数fn()
  //2.继而会调用到元素的markNeedsBuild()函数,该函数在后面章节中分析说明
  @protected
  void setState(VoidCallback fn) {
    final dynamic result = fn() as dynamic;
    _element.markNeedsBuild();
  }

  //停用功能(比如移除元素的时候)
  //为什么不直接销毁?增加此功能的目的可能是
  //为了保留被移除的元素,也就是说元素可能都
  //具有被复用的特性。
  @protected
  @mustCallSuper
  void deactivate() { }

  //丢弃功能
  //该功能一旦调用,元素对象也就不能被再次复用了
  @protected
  @mustCallSuper
  void dispose() { }
  
  //当依赖发生改变的时候会调用到此函数,
  //什么依赖会发生什么样的改变?猜测可能跟元素节点之间的依赖关系相关,
  //在稍后的章节中一起来分析下。
  @protected
  @mustCallSuper
  void didChangeDependencies() { }
  
  //状态类中具备构建能力的函数。
  //此函数为抽象函数,子类必须实现。
  @protected
  Widget build(BuildContext context);

}

从上面的代码中我们知道任何状态对象中都可以关联一个组件对象StatefulWidget和一个元素对象StatefulElement,而创建状态的责任由StatefulWidget对象负责,虽然StatefulWidget中没有和StatelessWidget类似的构建函数build(),但是在它的状态对象中存在,这样就相当于StatefulWidget也具备了和StatelessWidget相同的构建能力了。我们知道StatefuleWidget的状态和构建工作都保存在一个叫做State的对象中,而开发者只需要创建出一个State的子类即可轻松实现有状态的组件了,我们一般将状态数据和构造工作都放在同一个State状态对象内部,这很大的方便了我们在构建过程中对状态的管理。那么让状态对象State关联上StatefulWidget和StatefulElement这两个重要的类由谁负责呢?这个我们在介绍element一节中再继续讨论。

那么除了上面介绍的两种类型的widget组件,就没有其他widget组件了吗?答案是当然不止这两种。我们从上面的介绍中就可以了解到,StatelessWidget和StatefulWidget这两种组件的特性主要就两种:

1. 创建元素element。

2. 利用build()函数构建其他类型的Widget组件。

What?其他类型的Widget组件?是的,你没听错,还有其他不同于build特性的Widget组件。当我们想要创建自己定义的Widget组件的时候,实现的也就是这些其他类型的Widget组件了。

 

第二节:Flutter基本组件RenderObjectWidget和渲染对象RenderObject


第二节我们来了解一下flutter中的其他类型的组件之RenderObjectWidget组件(这里只介绍重要的组件)。前一小节我们简单介绍了一下flutter两种基本构建组件:无状态的组件StatelessWidget和有状态的组件StatefuleWidget,他们的主要作用是build()出我们可以在界面上显示的组件,当然build()中也可以存在无状态的组件StatelessWidget和有状态的组件StatefuleWidget,但是build()内部大部分都是其他类型的组件RenderObjectWidget,而RenderObjectWidget从字面上理解为渲染对象的组件,因为我们构建的目的最终只有一个,就是能在手机界面上显示出这些组件所描述的样子。那么RenderObjectWidget的作用就显而易见了,主要就是用来显示组件的,然而想要显示出界面,必然需要布局和绘制这两个基本步骤。我们一起来看看ReaderObjectWidget类的代码结构:

abstract class RenderObjectWidget extends Widget {
 
   const RenderObjectWidget({ Key key }) : super(key: key);

   @override
   RenderObjectElement createElement();

    @protected
  RenderObject createRenderObject(BuildContext context);

   @protected
   void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

   @protected
   void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

我们从组件RenderObjectWidget类中的代码结构可以了解到,RenderObjectWidget组件和StatefuleWidget、StatelessWidget的共同点在于,都具备创建元素的能力createElement(),并且都拥有属性key。不同点在于RenderObjectWidget组件具备创建RenderObject对象的能力createRenderObject(),并且需要传入一个BuildContext类型的参数。其余两个函数从字面意义上理解无非就是还拥有更新和卸载RenderObject对象的能力。但是RenderObjectWidget类显示组件的能力怎么没看出来?这里就要我们深入到RenderObject类里面一探究竟,下面是RenderObject类的继承关系:

从上图可以看出RenderObject主要继承了AbstractNode和HitTestTarget两个类,而AbstractNode是抽象节点的含义,也就是说RenderObject具备抽象节点的相关特性;而HitTestTarget顾名思义为敲击测试目标,这就跟我们的点击事件相关了,也就是说任何RenderObject对象都具备可点击的特性。下面是AbstractNode类的代码结构:

//该类仅仅存在父节点内容,
//意味着子节点的实现逻辑交由其子类负责。
//covariant关键词代表子类可以缩小形参的类型。
class AbstractNode {
  
  //意味着每个节点都具有深度值
  int get depth => _depth;
  int _depth = 0;
  
  //提供一个计算子节点的函数,此处表明
  //子节点的深度值永远大于父节点的深度值。
  //但是暂不清楚子节点的来源。
  @protected
  void redepthChild(AbstractNode child) {
    if (child._depth <= _depth) {
      child._depth = _depth + 1;
      child.redepthChildren();
    }
  }

  //计算所有子节点的深度。
  //该函数为空实现,想必是交由其子类具体实现,
  //猜测:子类实现应该会利用redepthChild()进行深度计算。
  void redepthChildren() { }
  
  //owner对象的实际意义暂不清楚,应该是由子类确定
  Object get owner => _owner;
  Object _owner;
  
  bool get attached => _owner != null;

  //节点被添加到节点树中的时候被调用
  //一般在父节点关联子节点的时候调用
  @mustCallSuper
  void attach(covariant Object owner) {
    _owner = owner;
  }
  
  //节点从节点树中移除的时候会被调用
  //一般在父节点移除子节点的时候调用
  @mustCallSuper
  void detach() {
    _owner = null;
  }
  
  //声明了一个父节点属性
  AbstractNode get parent => _parent;
  AbstractNode _parent;

  //在关联子节点时,该函数会触发子节点的attach()函数
  //1.确定子节点的父引用
  //2.将owner传递给子节点
  //3.计算子节点的深度
  @protected
  @mustCallSuper
  void adoptChild(covariant AbstractNode child) {
    child._parent = this;
    if (attached)
      child.attach(_owner);
    redepthChild(child);
  }
  
  //在移除子节点时,该函数会触发子节点的detach()函数
  //1.删除子节点的父引用
  //2.删除子节点的owner引用
  @protected
  @mustCallSuper
  void dropChild(covariant AbstractNode child) {
    child._parent = null;
    if (attached)
      child.detach();
  }
}

从上面的代码中可以看出,AbstractNode对象可以通过属性"parent"形成一棵链式集合的节点树,并且可以按照深度进行排列,而且一棵节点树集合只管理同一个owner对象,也就是说同一棵节点树只会产生唯一的一个owner实例,并且每个节点对象对owner的引用都会在attach()被调用时指定,并且在detch()被调用时删除,由此可证明owner对象是整棵节点树的纽带所在。而RenderObject就具备了AbstractNode的所有特性。同时RenderObject也具备可点击的特性,HitTestTarget类代码结构如下所示: 

abstract class HitTestTarget {
 
  factory HitTestTarget._() => null;

  void handleEvent(PointerEvent event, HitTestEntry entry);
}

 

抽象类HitTestTarget只存在一个可用函数handleEvent(),该函数需要传递两个不同类型的对象:PointerEvent和HitTestEntry,这里就简单的阐述一下这两个对象的作用,不详细进行代码分析了:

1. PointEvent为指针事件,其实就是点击事件的一个模型对象,里面存储了点击相关的数据,并且提供了跟矩阵转换Matrix4相关的函数,但是矩阵转换不影响事件本身。

2. HitTestEntry内部可以存储一个HitTestTarget和一个Matrix4对象。

总结,RenderObject具备了AbstractNode和HitTestTarget的特性,因此,任何实现了RenderObject的子类都可以组成一棵节点树并且都可以进行点击事件的传递。下面我们来具体分析一下其代码结构(这里我们单独提取其重要的函数来讲解):

class ParentData {
   //当RenderObject从节点树中移除时调用
   @protected
   @mustCallSuper
   void detach() { }
}

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
	
    ......

    //重写了AbstractNode的owner()函数,使owner对象具备实际意义
    PipelineOwner get owner => super.owner;
    
    //每一个RenderObject对象都可以保存一个ParentData的对象
    //该类型就名称而言意为父节点的数据,该类只存在一个detach()函数,
    //而ParentData对象的detach函数是空实现,因此不同RenderObject的子类
    //可能会实现不同ParentData的子类逻辑。
    ParentData parentData;
    
    //默认值为true,说明markNeedsLayout()函数执行无效
    bool _needsLayout = true;
    
    //根据markNeedsPaint()函数中的逻辑,我们知道:
    //1.当_relayoutBoundary不等于本节点,代表需要重新布局父节点
    //2.当_relayoutBoundary等于本节点&#x
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值