简述flutter的widget的基本结构

flutter项目中必不可少一部分,就是widget,今天就来简单的聊一下。
通俗的说,widget框架是flutter的UI方案,widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改,类似于React/Vue中虚拟DOM的diff算法。
如果接触过Android开发的同学一定对widget不会陌生,在flutter中,widget与Android的定义是一致的,学名可以叫:窗口小部件。
widget类的官方说明文档: https://api.flutter.dev/flutter/widgets/Widget-class.html
widget库的官方文档地址:https://api.flutter.dev/flutter/widgets/widgets-library.html

widget类是widget库中的所有组件的基类,所以如果我们制作自定义UI组件的话,一定也会继承widget,当然多数时候你不是直接继承widget,而是继承自widget的子类,比如StatelessWidget StatefulWidget
widget的主要工作是实现一个build函数,用以构建自身。一个widget通常由一些较低级别widget组成。Flutter框架将依次构建这些widget,直到构建到最底层的子widget时,这些最低层的widget通常为RenderObject,它会计算并描述widget的几何形状。

我想大家肯定对这样的树形结构都不陌生,几乎所有框架的UI结构都是类似的结构

而刚刚提到的StatelessWidget与StatefulWidget也是一组重要的基类

  • StatefulWidget 具有可变状态( state)的Widget(窗口小部件).
  • StatelessWidget 不需要可变状态的小部件

状态( state) 是可以在构建Widget时同步读取时 和 在Widget的生命周期期间可能改变的信息
Widget实现者的责任就是 在状态改变时通过 State.setState. 立即通知状态
我想接触过react 小程序 或者vue的同学,应该可以很好的理解他们。动态内容制作时,需要使用StatefulWidget,通过 State.setState改变数据来修改展示。

widget的布局模型

Flutter有一套丰富、强大的基础widget,其中以下是很常用的:

  • Text:该 widget 可让创建一个带格式的文本。

  • RowColumn: 这些具有弹性空间的布局类Widget可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于web开发中的Flexbox布局模型。

  • Stack: 取代线性布局 (与Android中的LinearLayout相似,而html中默认是线性布局),Stack允许子 widget 堆叠, 你可以使用 Positioned 来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝度定位(absolute positioning )布局模型设计的。

  • ContainerContainer 可让您创建矩形视觉元素。container 可以装饰为一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可以使用矩阵在三维空间中对其进行变换。

以下是一些简单的Widget,它们可以组合出其它的Widget:

import 'package:flutter/material.dart';

class MyAppBar extends StatelessWidget {
  MyAppBar({this.title});

  // Widget子类中的字段往往都会定义为"final"

  final Widget title;

  @override
  Widget build(BuildContext context) {
    return new Container(
      height: 56.0, // 单位是逻辑上的像素(并非真实的像素,类似于浏览器中的像素)
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: new BoxDecoration(color: Colors.blue[500]),
      // Row 是水平方向的线性布局(linear layout)
      child: new Row(
        //列表项的类型是 <Widget>
        children: <Widget>[
          new IconButton(
            icon: new Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null 会禁用 button
          ),
          // Expanded expands its child to fill the available space.
          new Expanded(
            child: title,
          ),
          new IconButton(
            icon: new Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Material 是UI呈现的“一张纸”
    return new Material(
      // Column is 垂直方向的线性布局.
      child: new Column(
        children: <Widget>[
          new MyAppBar(
            title: new Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.title,
            ),
          ),
          new Expanded(
            child: new Center(
              child: new Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(new MaterialApp(
    title: 'My app', // used by the OS task switcher
    home: new MyScaffold(),
  ));
}

看到这里,大家会发现,build方法中传入了一个BuildContext,那么这个buildcontext是什么呢?
下面就是重点内容,flutter的widget与element。先看一张图,表明他们的继承关系

20190221095002401.png

在上面我们已经说了,Widget是flutter的UI方案。但实际上,Widget并不是真正要显示在屏幕上的东西,只是一个配置信息,它永远是immutable的,并且可以在多处重复使用。那真正显示在屏幕上的视图树是什么呢?Element Tree!

abstract class Element extends DiagnosticableTree implements BuildContext

可以看到Element 实现了BuildContext。我们再来看官方对于BuildContext的解释:

  • BuildContextobjects are actually Element objects. The BuildContextinterface is used to discourage direct manipulation of Element objects.

  • BuildContext对象实际上就是Element对象,BuildContext 接口用于阻止对 Element 对象的直接操作。

也就是说,build中传入的BuildContext其实是一个element.

视图树装载过程

StatelessWidget

  • 首先它会调用StatelessWidget的 createElement 方法,并根据这个widget生成StatelesseElement对象。
  • 将这个StatelesseElement对象挂载到element树上。
  • StatelesseElement对象调用widget的build方法,并将element自身作为BuildContext传入。

StatefulWidget

  • 首先同样也是调用StatefulWidget的 createElement方法,并根据这个widget生成StatefulElement对象,并保留widget引用。
  • 将这个StatefulElement挂载到Element树上。
  • 根据widget的 createState 方法创建State。
  • StatefulElement对象调用state的build方法,并将element自身作为BuildContext传入。

所以我们在build函数中所使用的context,正是当前widget所创建的Element对象。

好了,有了这个基础之后,我们可以理解build方法与widget框架的结构了。总结起来

*树形结构,节点嵌套
*build方法传入widget创建的Element,返回这个widget所代表的用户界面的描述

最后我们再看看widget的生命周期

1768723716-5b188ca549250_articlex.png


 class MyStatefulWidget extends StatefulWidget {
    MyStatefulWidget({
        Key key,
        this.parameter,
    }): super(key: key);
    
    final parameter;
    
    @override
    _MyStatefulWidgetState createState() => new _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {

    @override
    void initState(){
        super.initState();
        
        // Additional initialization of the State
    }
    
    @override
    void didChangeDependencies(){
        super.didChangeDependencies();
        
        // Additional code
    }
    
    @override
    void dispose(){
        // Additional disposal code
        
        super.dispose();
    }
    
    @override
    Widget build(BuildContext context){
        return new ...
    }
}

留一个小问题,我们可以确定父节点的构造方法比子节点的构造方法先执行,那么后续的生命周期方法,先后顺序是怎样的呢?

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

推荐阅读更多精彩内容

  • 国庆后面两天在家学习整理了一波flutter,基本把能撸过能看到的代码都过了一遍,此文篇幅较长,建议保存(star...
    Nealyang阅读 4,343评论 1 17
  • 万事皆 Widget Widget 是每个 Flutter 应用的基础。每个 Widget 是一部分用户界面上不可...
    三季人阅读 1,687评论 1 2
  • 前言 本文的目的是为了让读者掌握不同布局类Widget的布局特点,分享一些在实际使用过程遇到的一些问题,在《Flu...
    xqqlv阅读 5,256评论 0 18
  • 最喜欢一段静静的时光, 听着慢歌, 看着来往的人群。 有时忙碌让自己都不知道忙了些什么? 慢下来, 想想。 不要走...
    Ownery阅读 137评论 0 0
  • 返回目录 上一章 朱尔旦又想起自己的书僮谢伯贤,这可是谢管家唯一的一个儿子啊,不妨大方一点,于是说道:“我听说伯贤...
    清声雏凤阅读 316评论 0 7