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'),
        ),
      ),
    );
  }
}

效果如下:

Widget基础系列 - StatelessWidget_第1张图片
tigger_name

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

body: Center(
  child: DecoratedBox(
    decoration: BoxDecoration(color: Colors.lightBlueAccent),
    child: Text('Tigger'),
  ),
),
Widget基础系列 - StatelessWidget_第2张图片
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'),
    ),
  ),
),
Widget基础系列 - StatelessWidget_第3张图片
tigger_colored_name_padding

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

StatelessWidget

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

Widget基础系列 - StatelessWidget_第4张图片
three_cat_names

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

Widget基础系列 - StatelessWidget_第5张图片
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),
      ),
    );
  }
}

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

Widget基础系列 - StatelessWidget_第6张图片
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基础系列 - StatelessWidget_第7张图片
widget_element

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

Widget基础系列 - StatelessWidget_第8张图片
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对象,直到一切都被构建并装载到屏幕上,准备好被布局和渲染 -- 然后我们就看到了屏幕上的内容。

Widget基础系列 - StatelessWidget_第9张图片
CatApp

参考资料

  • Flutter Widget 101

你可能感兴趣的:(Widget基础系列 - StatelessWidget)