概念

在Flutter中几乎所有的对象都是一个Widget。与原生开发中“控件”不同的是,Flutter中的Widget的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于APP主题数据传递的Theme等等,而原生开发中的控件通常只是指UI元素。

Widget与Element

在Flutter中,Widget的功能是“描述一个UI元素的配置数据”,它就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,而它只是描述显示元素的一个配置数据。

实际上,Flutter中真正代表屏幕上显示元素的类是Element,也就是说Widget只是描述Element的配置数据!Widget只是UI元素的一个配置数据,并且一个Widget可以对应多个Element。这是因为同一个Widget对象可以被添加到UI树的不同部分,而真正渲染时,UI树的每一个Element节点都会对应一个Widget对象。

总结:

  1. Widget实际上就是Element的配置数据,Widget树实际上是一个配置树,而真正的UI渲染树是由Element构成;不过,由于Element是通过Widget生成的,所以它们之间有对应关系,在大多数场景,我们可以宽泛地认为Widget树就是指UI控件树或UI渲染树。
  2. 一个Widget对象可以对应多个Element对象。这很好理解,根据同一份配置(Widget),可以创建多个实例(Element)。

StatelessWidget

StatelessWidget用于不需要维护状态的场景,它通常在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget。

import 'package:flutter/material.dart';

class Echo extends StatelessWidget {
const Echo({
Key key,
@required this.text, this.backgroundColor:Colors.red,
}):super(key:key); final String text;
final Color backgroundColor; @override
Widget build(BuildContext context) {
return Center(
child: Container(
color: backgroundColor,
child: Text(text),
),
);
} } // 按照惯例,widget的构造函数参数应使用命名参数,命名参数中的必要参数要添加@required标注,这样有利于静态代码分析器进行检查。
// 另外,在继承widget时,第一个参数通常应该是Key,另外,如果Widget需要接收子Widget,那么child或children参数通常应被放在参数列表的最后。
// 同样是按照惯例,Widget的属性应尽可能的被声明为final,防止被意外改变。

Context

build()方法有一个context参数,它是BuildContext类的一个实例,表示当前widget在widget树中的上下文,每一个widget都会对应一个context对象(因为每一个widget都是widget树上的一个节点)。实际上,context是当前widget在widget树中位置中执行”相关操作“的一个句柄,比如它提供了从当前widget开始向上遍历widget树以及按照widget类型查找父级widget的方法。

import 'package:flutter/material.dart';

class TestContextRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Test Context'),
),
body: Container(
child: Builder(builder: (context){
// 在Widget树中向上查找父级‘Scaffold’ widget
Scaffold scaffold = context.ancestorWidgetOfExactType(Scaffold);
// 直接返回 AppBar的title
return (scaffold.appBar as AppBar).title;
}),
),
);
}
}

StatefulWidget

和StatelessWidget一样,StatefulWidget也是继承自Widget类,并重写了createElement()方法,不同的是返回的Element 对象并不相同;另外StatefulWidget类中添加了一个新的接口createState()

// StatefulWidget的类定义:
import 'package:flutter/material.dart'; abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key); @override
StatefulElement createElement() => new StatefulElement(this); @protected
State createState();
} // StatefulElement 间接继承自Element类,与StatefulWidget相对应(作为其配置数据)。
// StatefulElement中可能会多次调用createState()来创建状态(State)对象。 // createState() 用于创建和Stateful widget相关的状态,它在Stateful widget的生命周期中可能会被多次调用。
// 例如,当一个Stateful widget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例,
// 其实,本质上就是一个StatefulElement对应一个State实例。

State

一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中的保存的状态信息可以:

  1. 在widget 构建时可以被同步读取。
  2. 在widget生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter framework状态发生改变,

    Flutter framework在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新UI的目的。

State中有两个常用属性:

  1. widget,它表示与该State实例关联的widget实例,由Flutter framework动态设置。

    注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的widget实例在重新构建时可能会变化,

    但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。
  2. context。StatefulWidget对应的BuildContext,作用同StatelessWidget的BuildContext。
// State的生命周期
// 实现一个计数器widget,点击它可以使计数器加1,由于要保存计数器的数值状态,所以我们应继承StatefulWidget
import 'package:flutter/material.dart'; // CounterWidget接收一个initValue整型参数,它表示计数器的初始值
class CounterWidget extends StatefulWidget {
const CounterWidget({Key key, this.initValue: 0}); final int initValue; @override
_CounterWidgetState createState() => _CounterWidgetState();
} //
class _CounterWidgetState extends State<CounterWidget> {
int _counter; // 当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,
// 所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。
// 不能在该回调中调用BuildContext.inheritFromWidgetOfExactType(该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget,关于InheritedWidget我们将在后面章节介绍),
// 原因是在初始化完成后,Widget树中的InheritFromWidget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。
@override
void initState(){
super.initState();
// 初始化状态
_counter = widget.initValue;
print('initState');
} // 主要是用于构建Widget子树的,会在如下场景被调用:
// 1. 在调用initState()之后。
// 2. 在调用didUpdateWidget()之后。
// 3. 在调用setState()之后。
// 4. 在调用didChangeDependencies()之后。
// 5. 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。
@override
Widget build(BuildContext context) {
print('build');
return Scaffold(
appBar: AppBar(
title: Text('Test State'),
),
body: Center(
child: FlatButton(
child: Text('$_counter'),
// 点击后计数器自增
onPressed: ()=>setState(()=>++_counter),
),
),
);
} // 在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。
// 正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。
@override
void didUpdateWidget(CounterWidget oldWidget) {
super.didUpdateWidget(oldWidget);
print('didUpdateWidget');
} // 当State对象从树中被移除时,会调用此回调。
// 在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。
// 如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
@override
void deactivate() {
super.deactivate();
print('deactivate');
} // 当State对象从树中被永久移除时调用。通常在此回调中释放资源。
@override
void dispose() {
super.dispose();
print('dispose');
} // 此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。
@override
void reassemble() {
super.reassemble();
print("reassemble");
} // 当State对象的依赖发生变化时会被调用;
// 例如:在之前build()中包含了一个InheritedWidget,然后在之后的build()中InheritedWidget发生了变化,那么此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。
// 典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
} }

十分重要的分析:

  1. 运行应用并打开该路由页面,在新路由页打开后,屏幕中央就会出现一个数字0,控制台日志输出:

    I/flutter (22202): initState

    I/flutter (22202): didChangeDependencies

    I/flutter (22202): build

    由此可知,在StatefulWidget插入到Widget树时首先initState方法会被调用

  2. 点击热重载按钮,控制台日志输出:

    I/flutter (22202): reassemble

    I/flutter (22202): didUpdateWidget

    I/flutter (22202): build

    由此可知,此时initState和didChangeDependencies都没有被调用,而此时didUpdateWidget被调用

  3. 在widget树移除CountWidget,将路由改为

    RaisedButton(

    child: Text('Test State'),

    textColor: Colors.blue,

    onPressed: () {

    print('test state');

    // Navigator.pushNamed(context, 'test state');

    },

    然后热重载,控制台日志输出:

    I/flutter (22202): reassemble

    I/flutter (22202): deactivate

    I/flutter (22202): dispose

    由此可知,在CounterWidget从widget树中移除时,deactivate和dispose会依次被调用

StatefulWidget生命周期

如下图:

Flutter 基础组件:Widget简介的更多相关文章

  1. Flutter 基础组件:进度指示器

    前言 Material 组件库中提供了两种进度指示器:LinearProgressIndicator和CircularProgressIndicator,它们都可以同时用于精确的进度指示和模糊的进度指 ...

  2. Flutter 基础组件:单选框和复选框

    前言 Material组件库中提供了Material风格的单选开关Switch和复选框Checkbox,虽然它们都是继承自StatefulWidget,但它们本身不会保存当前选中状态,选中状态都是由父 ...

  3. Flutter 基础组件:图片和Icon

    前言 Flutter中,可以通过Image组件来加载并显示图片,Image的数据源可以是asset.文件.内存以及网络. ImageProvider 是一个抽象类,主要定义了图片数据获取的接口load ...

  4. Flutter 基础组件:按钮

    前言 Material组件库中提供了多种按钮组件如RaisedButton.FlatButton.OutlineButton等,它们都是直接或间接对RawMaterialButton组件的包装定制,所 ...

  5. Flutter 基础组件:文本、字体样式

    // 文本.字体样式 import 'package:flutter/material.dart'; class TextFontStyle extends StatelessWidget { // ...

  6. Flutter 基础组件:状态管理

    前言 一个永恒的主题,"状态(State)管理",无论是在React/Vue(两者都是支持响应式编程的Web开发框架)还是Flutter中,他们讨论的问题和解决的思想都是一致的. ...

  7. flutter 基础组件

    TextWidget class TextWidget extends StatelessWidget { final TextStyle _textStyle = TextStyle( fontSi ...

  8. Flutter 基础组件:输入框和表单

    前言 Material组件库中提供了输入框组件TextField和表单组件Form. 输入框TextField 接口描述 const TextField({ Key key, // 编辑框的控制器,通 ...

  9. JMeter各个基础组件简介

    刚从LoadRunner转到JMeter,对JMeter的各种概念比较懵.在这里记录下.欢迎大家关注我的个人微信号:测试杂货铺. JMeter的各个功能都是它的组件来完成或实现的,下面来对JMeter ...

随机推荐

  1. 通过游戏学javascript系列第一节Canvas游戏开发基础

    本节教程通过一个简单的游戏小例子,讲解Canvas的基础知识. 最终效果: 点击移动的方块,方块上的分数会增加,方块的行进方向会改变,并且方块的速度会增加. 在线演示 源码 HTML5引入了canva ...

  2. 【opencv】学习笔记

    安装 此笔记仅对python36实用 OpenCV装3.4.1.15 指令:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv ...

  3. Linux下设置定时任务删除归档日志

    1.编辑删除归档日志的脚本----/home/oracle/clearlog.sh #! /bin/bash source ~/profile_orcl#记录归档删除的日志exec >> ...

  4. java中什么是对象,什么是对象引用

    在Java语句中,我们都会用到"=",但是这个"="的意义大部分人都没有一个清楚明确的认知 例如:Student a = new Student(); Stud ...

  5. ASP.NET Core 中间件的使用(二):依赖注入的使用

    写在前面 上一篇大家已经粗略接触了解到.NET Core中间件的使用:ASP .Net Core 中间件的使用(一):搭建静态文件服务器/访问指定文件, .NET Core框架中很多核心对象都是通过依 ...

  6. CTF练习②

    参考的文章链接 :https://www.cnblogs.com/chrysanthemum/p/11657008.html 这个题是强网杯的一道SQL注入的题,网上有不少的在线靶场和writeup, ...

  7. 第七章 Rocketmq--消息驱动

    今天咱们接着 上一篇第六章 Sleuth–链路追踪 继续写 SpringCloud Alibaba全家桶 , 第七章 Rocketmq--消息驱动,废话不多说,开始了 7.1 MQ简介 7.1.1 什 ...

  8. 爱普生 L4160 Serveies 网络打印机配置(问题解决)

    一.爱普生网络打印机固定IP地址 用网络打印机过程中,偶尔会出现打印机脱机的状况,大多数原因是打印机的IP地址在路由器重启过后重新分配了IP地址导致的.此时,为了减少不必要的麻烦就需要固定打印机的IP ...

  9. 去掉关闭按钮同时禁用alt+f4进行关闭

    public int ctype = 0; private const int GWL_STYLE = -16; private const int WS_SYSMENU = 0x80000; [Sy ...

  10. 记一次jedis并发使用问题JedisException: Could not return the resource to the pool

    今天线上突然发现个奇怪的问题项目第一次启动的时候redis报错JedisException: Could not return the resource to the pool 直接访问接口的时候不报 ...