Flutter 的基础-- Widget 介绍

前言

上一篇我们简单地了解了 Dart 语言,接着我们就开始学习 Flutter 的基础 Widget 吧。

本篇文章的目录结构:

1. 什么是 Widget

在 Flutter 中要用 Widget 构建 UI,Widget 相当于 Android 里的 View,iOS 里的 UIView。但与 View 不同的是,Widget 具有不同的生命周期:它是不可变的,每当 Widget 或者其状态发生变化时,Flutter 的框架都会创建一个新的 Widget 实例树,会先计算从上一个状态转换到下一个状态所需的最小更改,然后再去刷新界面。而 Aandroid 中的 View 会被绘制一次,并且在 invalidate 调用之前不会重绘。

2. Widget 的结构:Widget 树

Widget 组合的结构是树,所以叫做 Widget 树。

Widget 树结构如下图:

2.1 父 Widget 和 子 Widget

在 Widget 树里,Widget 有包含和被包含的关系:

  • 父 Widget:包含其他 Widget 的就叫父 Widget。
  • 子 Widget:被父 Widget 包含的 Widget 就叫子 Widget。

2.2 根 Widget

根 Widget 也叫 RootWidget。
我们之前创建的 Flutter demo 的入口文件 main.dart 里面有一个 main() 方法,是 Flutter 的入口方法:

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

runApp(MyApp()) 里的参数 MyApp() 就是一个 Widget,MyApp 的作用只是封装了一下,实际使用的 Widget 是 MaterialApp,这里的 MaterialApp 就是 RootWidget,Flutter 默认会把 根Widget 充满屏幕。

在 Flutter 中,根 Widget 只能是以下三个:

  • WidgetsApp:可以自定义风格的根 Widget。
  • MaterialApp:是在 WidgetsApp 上添加了很多 material-design 的功能,是 Material Design 风格的根 Widget。
  • CupertinoApp:也是基于 WidgetsApp 实现的 iOS 风格的根 Widget。
    这三个中最常用的是 MaterialApp,因为 MaterialApp 的功能最完善。MaterialApp 经常与 Scaffold 一起使用,下一篇文章我们再介绍。

3. Widget 的标识符:Key

因为 Flutter 采用的是 react-style 的框架,每次刷新 UI 的时候,都会重新构建新的 Widget 树,然后和之前的 Widget 树进行对比,计算出变化的部分,这个计算过程叫做 diff,在 diff 过程中,如果能提前知道哪些 Widget 没有变化,无疑会提高 diff 的性能,这时候就需要使用标识符。

在 diff 过程中,如何知道哪些 Widget 没有变化呢?
给 Widget 添加一个唯一的标识符,然后在 Widget 树的 diff 过程中,查看刷新前后的 Widget 树有没有相同标识符的 Widget,如果标识符相同,则说明 Widget 没有变化,否则说明 Widget 有变化。

注意:这个标识符在 Flutter 中就是 Key,所有 Widget 都有 Key 这一个属性。

那么 Flutter 中是如何在 diff 过程中判断哪些 Widget 没有变化呢?

  • 默认情况下(Widget 没有设置 Key)
    当没有给 Widget 设置 Key 时,Flutter 会根据 Widget 的 runtimeType 和显示顺序是否相同来判断 Widget 是否有变化。
    runtimeType 是 Widget 的 类型。
  • Widget 有 Key
    当给 Widget 设置了 Key 时,Flutter 是根据 Key 和 runtimeType 是否相同来判断 Widget 是否有变化。

注意:Key的使用:
一般情况下我们不需要使用 Key,但是当页面比较复杂时,就需要使用 Key 去提升渲染性能。

4. Widget 大全

Widget 有很多,Flutter官网(https://flutter.dev/docs/development/ui/widgets)上将 Widget 分为14类:

  1. Accessibility:辅助功能Widget。
  2. Animation and Motion:动画和动作Widget。
  3. Assets, Images, and Icons:资源和图片Widget。
  4. Async:Flutter应用程序的异步Widget。
  5. Basics:在构建第一个Flutter应用程序之前,需要知道的Basics Widget。
  6. Cupertino (iOS-style widgets):iOS风格的Widget。
  7. Input:除了在Material Components和Cupertino中的输入Widget外,还可以接受用户输入的Widget。
  8. Interaction Models:响应触摸事件并将用户路由到不同的视图中。
  9. Layout:用于布局的Widget。
  10. Material Components:Material Design风格的Widget。
  11. Painting and effects:不改变布局、大小、位置的情况下为子Widget应用视觉效果。
  12. Scrolling:滚动相关的Widget。
  13. Styling:主题、填充相关Widget。
  14. Text:显示文本和文本样式。

Widget 几乎实现了所有的功能,除了 UI、布局之外,还有交互、动画等,所以掌握 Widget 是很重要的。

5. Widget的分类

因为渲染是很耗性能的,为了提高 Flutter 的帧率,就要尽量减少不必要的 UI 渲染,所以 Flutter 根据 UI 是否有变化,将 Widget 分为:

  • StatefulWidget:是 UI 可以变化的 Widget,创建完后 UI 还可以再更改。
  • StatelessWidget:是 UI 不可以变化的 Widget,创建完后 UI 就不可以再更改。

5.1 StatefulWidget

StatefulWidget 是 UI 可以变化的Widget。

代码举例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp("Hello World"));

class MyApp extends StatefulWidget {
  // This widget is the root of your application.

  String content;

  MyApp(this.content);

  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return MyAppState();
  }
}

class MyAppState extends State<MyApp> {

  bool isShowText =true;

  void increment(){
    setState(() {
      widget.content += "d";
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          appBar: AppBar(title: Text("Widget -- StatefulWidget及State"),),
          body: Center(
              child: GestureDetector(
                child: isShowText? Text(widget.content) :null,
                onTap: increment,
              )
          ),
        )
    );
  }
}

当点击 Hello World 文本框的时候,内容会变。

实现自定义的 StatefulWidget,需要两部分:

  1. StatefulWidget:主要功能创建 State。
  2. State:状态。

其中 StatefulWidget 需要实现以下两个步骤:

  1. 首先继承 StatefulWidget
  2. 实现 createState() 的方法,返回一个 State

State:

实现步骤:

  1. 首先继承 State,State 的泛型类型是上面定义的 Widget 的类型
  2. 实现 build() 的方法,返回一个 Widget
  3. 需要刷新 UI 的话,调用 setState() 方法

State 的功能:

  • build()
  • setState()
  1. build():创建 Widget
    State 的 build() 函数创建 Widget,用于显示 UI。

  2. setState():刷新UI
    当我们想要刷新UI的时候,在 setState() 方法里更改数据,然后 setState() 会触发 State 的 build() 方法,引起强制重建 Widget,重建 Widget 的时候会重新绑定数据,而这时数据已经更新了,从而达到刷新 UI 的目的。

    接下来我们看一下 setState() 方法的源码:
    1117行,执行无参函数 fn(),并把结果转换成 dynamic 类型,然后赋值给 result。1133行,才是真正的 Widget 创建,感兴趣的可以去看 markNeedsBuild() 方法的源码,这里不过多介绍了。

注意:刷新 UI 的代码必须在调用 setState() 之前或者在 setState() 函数里面写,才能刷新UI。

为什么 StatefulWidget 被分成 StatefulWidget 和 State 两部分?
一方面是为了保存当前的 APP 状态,另一方面是为了性能。
当 UI 需要更新的时候,假设 Widget 和 State 都重建,由于 State 里保存了 UI 显示的数据,State 重建就会创建新的实例,UI 之前的状态就会丢失,导致 UI 显示异常,所以要分成两部分StatefulWidget 部分重建,而 State 部分不重建。
Widget 重建的成本比较低,但 State 的重建成本很高,分成两部分就可以让 State 不会被频繁重建,这样就提高了性能。

生命周期:

StatefulWidget 的生命周期:

  • createState

State的生命周期:
  • moundted is true
    mounted 是 boolean,只有当 mounted 为 true 时,才能调用 setState()
  • initState
    createState 创建 State 对象后调用的,只会调用一次。一旦这个方法完成,State 对象就初始化完成了。
  • didChangeDependencied
    state依赖的对象发生变化时调用,在initState() 方法运行完后调用
  • didUpdateWidget
    Widget状态改变时候调用,可能会调用多次
  • build
    在 didChangeDependencies()(或者 didUpdateWidget() )之后调用。 这是构建Widget的地方。
  • deactivate
    当 State 从树中移除时,就会触发 deactivate。但是如果在这帧结束前,如果其他地方使用到了这个 Widget,就会重新把 Widget 插入到树里,这就涉及到了 Widget 的重用,Widget 的重用和 Key 有关。
  • dispose
    当 StaefulWidget 从树中移除时调用 dispose() 方法

5.2 StatelessWidget

StatelessWidget 是没有 State(状态)的 Widget,当 Widget 在运行时不需要改变时,就用 StatelessWidget。

代码举例:

import 'package:flutter/material.dart';

void main() => runApp(MyApp("Hello World"));

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  final String content;

  MyApp(this.content);
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: Center(
          child: Text(content),
        ),
      )
    );
  }
}

上面代码中的 MyApp 是 StatelessWidget,其实看一下 Text 的源码,发现它也是 StatelessWidget。

要实现自定义的 StatelessWidget,需要下面两步:

  1. 首先继承 StatelessWidget
  2. 必须要实现 build 函数,返回一个 Widget。

生命周期:
StatelessWidget 的生命周期只有一个,即 build 函数。

使用注意事项:
StatelessWidget 只能在它初始化的时候,通过构造函数传递一些额外的参数,这些参数在之后的阶段都不会变化。因为 StatelessWidget 是 immutable的,只能在加载/构建 Widget 时才绘制一次。

总结

本文我们主要介绍了什么是 Widget、Widget树结构、Widget标识符、Widget大全和Widget的状态分类,后面会详细介绍具体的Widget。

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

推荐阅读更多精彩内容