Widget基础系列 - StatelessWidget

在Flutter中,Widget可以说是第一基础概念。Widget是对用户界面的不可变描述,可被膨化为管理底层渲染树的Element。

理解Widget原理是掌握Flutter编程至关重要的一步,本系列主要介绍Widget的基础知识,本文是第一篇:

  • StatelessWidget
  • StatefulWidget
  • InheritedWidget
  • Key

什么是Widget?

我们知道,Flutter是Google推出的一个跨平台的移动App开发方案,让我们可以用一套代码库同时开发iOS和Android应用。

Widget是Flutter应用的基本构造单元,每个Widget表示对一块用户界面的不可变描述。Widget可以做很多事情,即有按钮、菜单这样的结构化Widget,也有传递字体或者主题颜色的样式Widget,还有padding这样的布局Widget...我们还可以通过组合已有的Widget来构建新的Widget,组合无穷无尽。

看一个简单的例子,在屏幕上显示出我家猫咪“Tigger"的名字:

import 'package:flutter/material.dart';

void main() => runApp(CatApp());

class CatApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My Cat',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('My Cat'),
        ),
        body: Center(
          child: Text('Tigger'),
        ),
      ),
    );
  }
}

效果如下:

tigger_name

如果我们想给猫的名字加一个背景颜色,可以将TextDecoratedBox包裹起来:

body: Center(
  child: DecoratedBox(
    decoration: BoxDecoration(color: Colors.lightBlueAccent),
    child: Text('Tigger'),
  ),
),
tigger_colored_name

如果我们希望在背景颜色和文字之间加一个边距,可以在两个Widget中间加一个Padding

body: Center(
  child: DecoratedBox(
    decoration: BoxDecoration(color: Colors.lightBlueAccent),
    child: Padding(
      padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
      child: Text('Tigger'),
    ),
  ),
),
tigger_colored_name_padding

将widget装配起来的过程就是我们上面所说的“组合”。用户界面由一系列的widget组合而成,每个widget处理一个特定的任务。Padding负责添加内边距,DecoratedBox装饰一个box...

StatelessWidget

假设我又收养了一对猫,需要将它们的名字也显示出来。可以用一个Column将它们纵向排列起来:

three_cat_names

名字挤在了一起,在名字中间加一些空白:

three_cat_names_margin

看起来好多了,不过重复的代码有点儿多,如果创建一个自定义Widget将细节封装起来,只需要传入一个名字,就像上面使用的Text那样是不是更好?

class CatName extends StatelessWidget {
  final String name;

  const CatName(this.name);

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(color: Colors.lightBlueAccent),
      child: Padding(
        padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
        child: Text(name),
      ),
    );
  }
}

如此一来,上图的代码可以简化为:

three_cat_names_encapsulated

界面显示效果是一样的,通过使用一个StatelessWidget和Flutter的组合功能,代码看起来紧凑多了。

我们创建了一个名为“CatName”的StatelessWidgetStatelessWidget是一个由子widget组合而成的widget,因此它有一个build方法,且没有需要跟踪的可变状态。

“可变状态”是什么意思呢?随时间变化的任何属性。例如,一个文本输入框,它有一个可变的字符串属性来跟踪用户的输入。或者一个带动画的widget,某些值随动画而改变。CatName没有这些,它只需要一个字符串类型的名字,不会改变,因此StatelessWidget非常适合它。我们可以将name声明为final类型,通过构造函数接受此参数。由于所有的参数都是final的,因此我们可以将构造函数也声明为final的。

我们已经看到build方法是如何工作的,那么它是什么时候被调用的呢?

我们倾向于将用Flutter构建的App想象成一棵widget组成的树,这并不是坏事。不过,正如前面提到的,widget仅仅是一块用户界面的配置信息或蓝本。那么,它们配置的又是什么呢?Element。Element是widget实例化并挂载到屏幕上的对象,element树表示给定时刻屏幕上实际显示的内容,一个element表示用对应的widget配置树中特定位置的元素。

每个widget类都有对应的element类,以及用于创建element实例的方法。例如,StatelessWidget对应StatelessElement。当widget被挂载到树上时, createElement方法会被调用。

abstract class StatelessWidget extends Widget {
  @override
  StatelessElement createElement() => StatelessElement(this);
}

Flutter向widget请求一个element对象,然后将其放到element树上,element会引用创建它的widget对象。

widget_element

在上面的CatApp例子中,每个widget创建自己的element,并挂载到element树上。

CatApp_widgets_elements

因此,CatApp有两棵树:element树,表示屏幕上的实际内容;widget树,创建element树的蓝本。

Element并不负责布局和渲染,因此还有一棵树:RenderObject树。说element表示屏幕上的实际内容也说得过去,只不过抽象层次不同而已。

Element的构建过程是如何开始的呢?或者说,是什么开启了这一切?我们看一下最开始的代码:

void main() => runApp(CatApp());

CatApp表示整个应用程序,它是一个StatelessWidget。在Flutter中,Widget几乎可以做任何事情。我们看到main函数,它是应用的入口,main调用了runApp函数,这就是应用的起点。runApp接受一个widget对象,将其装载为应用的根element,大小与屏幕(准确地说是FlutterView)相同。Flutter依次调用所有的build方法创建widget对象,并通过它们创建element对象,直到一切都被构建并装载到屏幕上,准备好被布局和渲染 -- 然后我们就看到了屏幕上的内容。

CatApp

参考资料

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

推荐阅读更多精彩内容

  • 在Flutter中,Widget可以说是第一基础概念。Widget是对用户界面的不可变描述,可被膨化为管理底层渲染...
    光明自在阅读 1,488评论 0 7
  • 在Flutter中,Widget可以说是第一基础概念。Widget是对用户界面的不可变描述,可被膨化为管理底层渲染...
    光明自在阅读 732评论 0 1
  • 原文在此,此处只为学习 Widget与ElementWidget主要接口Stateless WidgetState...
    lltree阅读 4,510评论 0 1
  • 前言 上一篇我们简单地了解了 Dart 语言,接着我们就开始学习 Flutter 的基础 Widget 吧。 1....
    南小夕阅读 2,568评论 0 8
  • 首先,在Flutter中几乎所有的对象都是一个Widget。跟原生开发中的“控件”不同,Flutter中的Widg...
    沉江小鱼阅读 1,617评论 0 2