Flutter开发之基础Widgets

Widgets概念

Flutter里有一个很是重要的核心理念:一切皆为组件,Flutter的全部元素都是由控件构成的。 与原生开发中控件所表明的含义不一样,Flutter中widget的概念更加普遍,它不只能够表示UI元素,也能够表示一些功能性的组件,如用于手势检测的 GestureDetector widget、用于应用主题数据传递的Theme等等。而原生开发中的控件一般只是指UI元素。因为Flutter主要就是用于构建用户界面的,因此,在大多数时候,咱们能够简单的认为widget就是一个控件,没必要纠结于概念。bash

Widget与Element

在正式介绍Flutter的Widget以前,咱们须要理清两个概念,即什么是Widget,什么是Element?框架

Widget的功能是“描述一个UI元素的配置数据,它就是说,Widget其实并非表示最终绘制在设备屏幕上的显示元素,而只是显示元素的一个配置数据。实际上,Flutter中真正表明屏幕上显示元素的类是Element,也就是说Widget只是描述Element的一个配置。而且一个Widget能够对应多个Element,这是由于同一个Widget对象能够被添加到UI树的不一样部分,而真正渲染时,UI树的每个Widget节点都会对应一个Element对象。因此,理解Flutter的Widget须要理清两个概念:less

  • Widget实际上就是Element的配置数据, Widget的功能是描述一个UI元素的一个配置数据, 而真正的UI渲染是由Element构成的。
  • 因为Element是经过Widget生成,因此它们之间有对应关系,因此在大多数场景,咱们能够简单地认为Widget就是指UI控件或UI渲染。

Widget声明

首先,咱们先来看一下Widget类的声明:ide

@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;

  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}
复制代码

从这个Widget类的申明中,咱们能够获得以下一些信息:函数

  • Widget类继承自DiagnosticableTree,主要做用是提供调试信息。
  • Key: 这个key属性相似于React/Vue中的key,主要的做用是决定是否在下一次build时复用旧的widget,决定的条件在canUpdate()方法中
  • createElement():正如前文所述一个Widget能够对应多个Element;Flutter Framework在构建UI时,会先调用此方法生成对应节点的Element对象。此方法是Flutter Framework隐式调用的,在咱们开发过程当中基本不会调用到。
  • debugFillProperties 复写父类的方法,主要是设置DiagnosticableTree的一些特性。
  • canUpdate()是一个静态方法,它主要用于在Widget树从新build时复用旧的widget。具体来讲,是否使用新的Widget对象去更新旧UI树上所对应的Element对象的配置;而且经过其源码咱们能够知道,只要newWidget与oldWidget的runtimeType和key同时相等时就会用newWidget去更新Element对象的配置,不然就会建立新的Element。

StatelessWidget

StatelessWidget是Flutter提供的一个不须要状态更改的widget ,它没有内部状态管理功能。StatelessWidget相对比较简单,它继承自Widget类,重写了createElement()方法。布局

@override
StatelessElement createElement() => new StatelessElement(this);
复制代码

StatelessElement 间接继承自Element类,与StatelessWidget相对应。StatelessWidget一般被用于不须要维护状态的场景,在build方法中经过嵌套其它Widget来构建UI,在构建过程当中会递归的构建其嵌套的Widget。例如:ui

class Echo extends StatelessWidget {
  const Echo({
    Key key,  
    @required this.text,
    this.backgroundColor:Colors.grey,
  }):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参数一般应被放在参数列表的最后。 而后,咱们能够经过以下方式来使用Echo widget。this

Widget build(BuildContext context) {
  return Echo(text: "hello world");
}
复制代码

运行后效果以下图所示: spa

在这里插入图片描述

StatefulWidget

StatefulWidget 是一个可变状态的widget。 使用setState方法管理StatefulWidget的状态的改变。调用setState告诉Flutter框架,某个状态发生了变化,Flutter会从新运行build方法,以便应用程序能够应用最新状态。debug

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

下面是StatefulWidget的类定义,以下所示:

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实例。

StatelessWidget和StatefulWidget的区别

经过上面的讲解,咱们能够得出以下结论:

  • StatelessWidget是状态不可变的widget, 初始状态设置之后就不可再变化, 若是须要变化须要从新建立StatefulWidget,由于StatefulWidget能够保存本身的状态。
  • 在Flutter中经过引入State来保存状态, 当State的状态改变时,能从新构建本节点以及孩子的Widget树来进行UI变化。
  • 若是须要主动改变State的状态,须要经过setState()方法进行触发,单纯改变数据是不会引起UI改变的

Widgets的State

说到组件,就不得不提到Widgets的State。一般,一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中的保存的状态信息有以下两个做用:

  1. 在widget build时能够被同步读取。
  2. 在widget生命周期中能够被改变,当State被改变时,能够手动调用其setState()方法通知Flutter framework状态发生改变,Flutter framework在收到消息后,会从新调用其build方法从新构建widget树,从而达到更新UI的目的。

State有两个经常使用属性:widget和context。

  • widget:它表示与该State实例关联的widget实例,由Flutter framework动态设置。注意,这种关联并不是永久的,由于在应用声明周期中,UI树上的某一个节点的widget实例在从新构建时可能会变化,但State实例只会在第一次插入到树中时被建立,当在从新构建时,若是widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。
  • context,它是BuildContext类的一个实例,表示构建widget的上下文,它是操做widget在树中位置的一个句柄,它包含了一些查找、遍历当前Widget树的一些方法。每个widget都有一个本身的context对象。

生命周期

和原平生台的控件同样,State也有本身的生命周期。为了加深读者对State生命周期的印象,本节咱们经过一个实例来演示一下State的生命周期。在接下来的示例中,咱们实现一个计数器widget,点击它可使计数器加1,因为要保存计数器的数值状态,因此咱们应继承StatefulWidget,代码以下:

class CounterWidget extends StatefulWidget {
  const CounterWidget({
    Key key,
    this.initValue: 0
  });

  final int initValue;

  @override
  _CounterWidgetState createState() => new _CounterWidgetState();
}
复制代码

CounterWidget接收一个initValue整型参数,它表示计数器的初始值。接下来,咱们看一下_CounterWidgetState的实现:

class _CounterWidgetState extends State<CounterWidget> {  
  int _counter;

  @override
  void initState() {
    super.initState();
    //初始化状态  
    _counter=widget.initValue;
    print("initState");
  }

  @override
  Widget build(BuildContext context) {
    print("build");
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text('$_counter'),
          //点击后计数器自增
          onPressed:()=>setState(()=> ++_counter,
          ),
        ),
      ),
    );
  }

  @override
  void didUpdateWidget(CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }

  @override
  void deactivate() {
    super.deactivate();
    print("deactive");
  }

  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
}
复制代码

接下来,咱们建立一个新路由,在新路由中,咱们只显示一个CounterWidget。

Widget build(BuildContext context) {
  return CounterWidget();
}
复制代码

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

I/flutter ( 5436): initState
I/flutter ( 5436): didChangeDependencies
I/flutter ( 5436): build
复制代码

能够看到,在StatefulWidget插入到Widget树时首先被调用的是initState方法。而后,咱们点击⚡️按钮热重载代码,控制台输出日志以下:

I/flutter ( 5436): reassemble
I/flutter ( 5436): didUpdateWidget
I/flutter ( 5436): build
复制代码

能够看到,热重载操做时initState 和didChangeDependencies都没有被调用,而是调用了didUpdateWidget。 接下来,咱们在widget树中移除CounterWidget,并将路由build方法改成:

Widget build(BuildContext context) {
  //移除计数器 
  //return CounterWidget();
  //随便返回一个Text()
  return Text("xxx");
}
复制代码

而后执行热重载操做,日志以下:

I/flutter ( 5436): reassemble
I/flutter ( 5436): deactive
I/flutter ( 5436): dispose
复制代码

能够看到,在CounterWidget从widget树中移除时,deactive和dispose会依次被调用。

经过上面的示例,咱们将StatefulWidget生命周期整理以下图:

在这里插入图片描述
StatefulWidget的生命周期大体可分为三个阶段:

  • 初始化:插入渲染树,这一阶段涉及的生命周期函数主要有createState、initState、didChangeDependencies和build。
  • 运行中:在渲染树中存在,这一阶段涉及的生命周期函数主要有didUpdateWidget和build。
  • 销毁:从渲染树中移除,此阶段涉及的生命周期函数主要有deactivate和dispose。

初始化阶段

createState:createState必须且仅执行一次,它用来建立state,当建立StatefulWidget时,该放方法就会被执行。

initState:在建立StatefulWidget后,initState是第一个被调用的方法,同createState同样只被调用一次,此时widget的被添加至渲染树,mount的值会变为true,但并无渲染。咱们能够在该方法内作一些初始化操做。

didChangeDependencies:当widget第一次被建立时,didChangeDependencies紧跟着initState函数以后调用,在widget刷新时,该方法不会被调用。它会在“依赖”发生变化时被Flutter Framework调用,这个依赖是指widget是否使用父widget中InheritedWidget的数据。也便是只有在widget依赖的InheritedWidget发生变化以后,didChangeDependencies才会调用。 这种机制可使子组件在所依赖的InheritedWidget变化时来更新自身!好比当主题、locale(语言)等发生变化时,依赖其的子widget的didChangeDependencies方法将会被调用。

build:build会在widget第一次建立时紧跟着didChangeDependencies方法以后和UI从新渲染时被调用。build只作widget的建立操做,若是在build里作其余操做,会影响UI的渲染效果。

运行中

StatefulWidget运行中只会调用两个函数,即didUpdateWidget和build。 didUpdateWidget:当组件的状态改变的时候就会调用didUpdateWidget,好比调用了setState。

销毁

deactivate:当State对象从树中被移除时,会调用此回调函数,这标志着 StatefulWidget将要执行销毁操做。页面切换时,也会调用它,由于此时State在视图树中的位置发生了变化可是State不会被销毁,而是从新插入到渲染树中。 重写的时候必需要调用 super.deactivate()

dispose:从渲染树中移除时调用,State会永久的从渲染树中移除,和initState正好相反mount值变味false。这时候就能够在dispose里作一些取消监听操做。

为了方便读者理解,咱们看一下StatefulWidget的生命周期函数调用状况。

生命周期 调用次数 调用时间
createState 1 组件建立时
initState 1 组件建立时
didChangeDependencies n 组件建立或状态发生变化
build n 组件建立或UI从新渲染
didUpdateWidget n 组件建立或UI从新渲染
deactivate n State对象将要移除时
dispose 1 state对象被销毁

内置组件库

Flutter SDK提供了一套丰富、强大的基础组件,在基础组件库之上Flutter又提供了一套Material风格(Android默认的视觉风格)和一套Cupertino风格(iOS视觉风格)的组件库。使用前只须要导入便可使用:

import 'package:flutter/widgets.dart';
复制代码

基础组件

Flutter SDK提供了不少功能丰富的基础组件,常见的有以下一些:

  • Text:该组件可以让您建立一个带格式的文本。
  • Row、 Column: 这些具备弹性空间的布局类Widget可以让您在水平(Row)和垂直(Column)方向上建立灵活的布局。其设计是基于Web开发中的Flexbox布局模型。
  • Stack: 取代线性布局 (译者语:和Android中的FrameLayout类似),Stack容许子 widget 堆叠, 你可使用 Positioned 来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝对定位(absolute positioning )布局模型设计的。
  • Container: Container 可以让您建立矩形视觉元素。container 能够装饰一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也能够具备边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可使用矩阵在三维空间中对其进行变换。

Material组件

众所周知,Material是Android应用默认的视觉风格,Cupertino则是iOS应用的默认视觉风格,为了实现两种不一样的视觉风格,Flutter 在基础组件库之上Flutter又提供了一套Material风格和一套Cupertino风格的组件库,以知足两种不一样设计风格的开发须要。

Material应用程序以MaterialApp 组件开始, 该组件在应用程序的根部建立了一些必要的组件,好比Theme组件,它用于配置应用的主题。 是否使用MaterialApp彻底是可选的,可是使用它是一个很好的作法。在以前的示例中,咱们已经使用过多个Material 组件了,如:Scaffold、AppBar、FlatButton等。

要使用Material 组件,须要先引入它:

import 'package:flutter/material.dart';
复制代码

Cupertino组件

Flutter也提供了一套丰富的Cupertino风格的组件,尽管目前尚未Material 组件那么丰富,可是它仍在不断的完善中。目前,Flutter提供的Cupertino组件主要有 CupertinoTabBar、 CupertinoActivityIndicator、CupertinoPageScaffold、 CupertinoTabScaffold、 CupertinoTabView 等 。 关于Cupertino组件,你们能够参考官方的介绍:Cupertino (iOS风格) Widgets

相关文章
相关标签/搜索