Flutter之Widget概念

在Flutter框架中,Widget代表最基础的部分,相当于是应用程序的基石.引用一句话“万物皆是Widget”,就能看出Widget的重要性不言而喻,所以本篇着重讲一下,我在学习Flutter的过程中,对Widget的一些理解和认知.有些基本知识直接摘自Flutter中文网


1.Widget的划分

import 'package:flutter/material.dart'; //安卓类型的风格库
void main() {
  runApp(
    new Center(
      child: new Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

眼熟吧? HelloWorld,程序员标准入门,上面的例子中,将一个widget传递给runApp函数就能构成一个最简单的Flutter程序.

上面的例子中,该runApp函数接收给定的widget并使其成为widget树的根,这就类似于iOS中,指定window的rootViewController.(我是这样理解的).

常用的Widget:

1.Text:文本格式的widget
2.Row,Column:类似web开发中的盒模型FlexBox,让你可以在水平或者垂直方向上灵活布局.
3.Stack:取代线性布局,Stack允许子widget堆叠,你可以使用Positioned来定位他们相对于Stack的上左下右四条边的位置.
4.Container:它可以让你创建一个矩形元素.它可以被装饰为一个BoxDecoration,如background、一个边框或者一个阴影.它同样具有margins、padding和应用于其大小的约束constraints.

1.1.StatelessWidget和StatefulWidget

statelessWidget:表示无状态的widget,内部没有保存状态,UI界面一经创建不会发生改变.

statefulWidget:有状态的widget,内部有保存状态,当状态发生改变,调用setState()方法,会触发更新UI界面的操作.另外对于自定义继承自StatefulWidget的子类,必须要重写createState()方法.

StatelessWidget示例
import 'package:flutter/material.dart';

void main() => runApp(MyStatelessWidget(text: "Welcome"));

class MyStatelessWidget extends StatelessWidget {
  final String text;
  MyStatelessWidget({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        text,
        textDirection: TextDirection.ltr,
      ),
    );
  }
}

在上面的示例中,使用了MyStatelessWidget类的构造函数传递标记为final的text。这个类继承了StatelessWidget,它包含不可变数据。

statelessWidget的build方法通常只会在以下三种情况调用:

  • 将widget插入树中时
  • 当widget的父级更改其配置时
  • 当它依赖的InheritedWidget发生变化时
StatefulWidget示例
class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);

  _HomePageState createState() => _HomePageState();
}
------------------------------------------------------------------------
class _HomePageState extends State<HomePage> {
  int count=0;
  @override
  Widget build(BuildContext context) {
    return Container(
          
      child:Column(
        children: <Widget>[
          Chip(
            label: Text("${this.count}")           
          ),
          RaisedButton(
            child: Text('增加'),
            onPressed: (){             
              setState(() {
                 this.count++;
              });
            },
          )          
        ],
      )
    );
  }
}

上面的示例中,虚线上面的部分,声明了一个StatefulWidget,它需要一个createState()方法。此方法创建管理widget状态的状态对象_HomePageState 。


2.如何判断使用statelessWidget还是StatefulWidget的条件?

在Flutter中,widget是有状态的还是无状态的 , 取决于是否他们依赖于状态的变化。
1.如果用户交互或数据改变导致widget改变,那么它就是有状态的。
2.如果一个widget是最终的或不可变的,那么它就是无状态。

首先需要判断widget的状态,简单点,没有数据驱动,信息不可变的,都是statelessWidget,Flutter本身也告诉你,优先使用StatelessWidget。

还有一个重要的条件,Flutter并没有实现数据双向绑定,当你使用StatefulWidget时,你在 State.setState((){}) 中写什么代码都不重要,它仅用来标记这个State对象需要重新Build,重新build后根据已变更的数据来创建新的Widget,但是这个build带来的消耗是巨大的,它会触发父类底下每个子widget的build方法。

当某个父widget下,只有部分数据发生改变时,它还是会全局重新刷新,所以这对于个别场景是不适用的。对于网上的一些学习资料中提到的方法,我认为是行之有效的。

1.尽量将需要刷新的statefulWidget放在最小的子节点
2.根布局视图不要使用statefulWidget
3.将会调用到setState((){}) 的代码尽可能的和要更新的视图封装在一个尽可能小的模块里。
4.如果一个Widget需要reBuild,那么它的子节点、兄弟节点、兄弟节点的子节点应该尽可能少


3.Widget的生命周期

在iOS里的ViewController中,每个视图控制器都有自己的生命周期,包含loadView,viewDidLoad等方法一样,Flutter中的widget也有属于自己生命周期。

方法 状态
initState 插入渲染树时调用,只调用一次
didChangeDependencies state依赖的对象发生变化时调用
build 构建widget时调用
setState 触发视图重建
didUpdateWidget 某个组件状态发生改变时调用,可能会调用多次
deactivate 移除渲染树时调用
dispose 组件即将销毁时调用

上面的表格中,罗列了widget生命周期的各个阶段。

initState:类似于iOS中ViewController的ViewDidLoad方法,
可以在此做一些初始化的设置,比如为某些状态变量设置默认值。
didChangeDependencies: 用来专门处理 State 对象依赖关系变化,会在 initState() 调用结束后被调用。
build:构建视图,创建并返回一个widget。
setState:当状态数据发生变化时,通过调用这个方法通知 Flutter 更新重构 Widget。
didUpdateWidget:当 Widget 的配置发生变化时,比如,父 Widget 触发重建(即父 Widget 的状态发生变化时),热重载时,系统会调用这个函数。
deactivate:当组件的可见状态发生变化时,deactivate 函数会被调用,这时 State 会被暂时从视图树中移除。当页面切换时,由于 State 对象在视图树中的位置发生了变化,需要先暂时移除后再重新添加,重新触发组件构建,因为这个函数也会被调用。
dispose:从视图树中销毁时调用。


4.Flutter中视图的层级关系

关系图

如上图所示,Flutter的视图层级主要包含三个层级:widget,Element和RenderObject。下面我们就按照这三个层级,依次讲述其中的知识点。

4.1Widget

首先是widget,按我的理解,它在这三者里应该属于组织者的角色,通过下面widget的源码,我们做简单分析。

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

  /// 创建Widget对应的Element对象,Element对象存储了Widget的配置信息
  @protected
  Element createElement();

  /// 判断是否可以更新Widget
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

widget这个class中,主要有以下两个方法:
createElement:该方法主要是通过widget创建一个对应的Element对象。
canUpdate:该方法主要是判断widget是否可更新,通过比较新旧widget的runtimeType和key。说到这里就不不提新旧widget之间的重要判断依据key。

Widget的标识符:Key

Key是所有Widget都拥有的重要属性,但它并不是默认的,构建某些复杂界面时,我们需要设置widget的key来提升性能。通过Key来比较新旧widget Tree。

4.2 Element

在这三者里,属于协调者的角色。Element对象会同时持有widget对象和RenderObject对象,相当于是widget和RenderObject之间的桥梁。

它承载了视图构建的上下文数据,Element与Widget是一对多的关系。由于Element是可变的,所以通过Element将Widget树的变化做了抽象,可以将真正需要修改的部分同步到RenderObject树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。

我们在代码中最常看见的BuildContext,其实就是抽象的Element对象。

4.3 RenderObject

RenderObject中包含了所有用来渲染实例Widget的逻辑。它负责layout、painting和hit-testing。它的生成十分耗费性能,所以我们应该尽可能的缓存它。我们与它打交道最多的时候就是在调试布局的时候。

RenderObject 抽象的Widget
layout Column和Row
painting Text和Image
hit-testing GestureDetector

通过上面的叙述,可以总结出三者的基本关系:

视图由一个个独立的Element节点构成。组件最终的Layout、渲染都是通过RenderObejct来完成的,从创建到绘制渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObejct并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。


Flutter这块总是写写停停的,层级关系这部分还有许多更深的知识点,以后还是要继续总结学习,回顾旧知识,学习新知识。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342