Flutter Widget框架概述

Flutter第一部分(UI)第一篇:初识Widget

Flutter跨平台移动端开发丨Widget、Element、State、状态管理

 

Flutter Widget采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用widget构建你的UI。

Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改(译者语:类似于React/Vue中虚拟DOM的diff算法)。

runApp函数接受给定的Widget并使其成为widget树的根

在编写应用程序时,通常会创建新的widget,这些widget是无状态的StatelessWidget或者是有状态的StatefulWidget, 具体的选择取决于您的widget是否需要管理一些状态。widget的主要工作是实现一个build函数,用以构建自身。一个widget通常由一些较低级别widget组成。Flutter框架将依次构建这些widget,直到构建到最底层的子widget时,这些最低层的widget通常为RenderObject,它会计算并描述widget的几何形状

一、Widget、Element、State

Flutter跨平台移动端开发丨Widget、Element、State、状态管理
1、widget
(1)概念
widget 的主要工作是通过实现 build 函数 来构建自身。一个 widget 通常由一些低级别的 widget 组成,flutter 框架依次的构建这些低级别的 widget,直到构建到最底层的子 widget 时,它会计算并描述 widget 的几何形状
(2)分类

StatelessWidget:无状态,比如标题栏中的标题
StatefulWidget:有状态,创建时需要指定一个 State ,在需要更新 UI时调用 setState(VoidCallbackfn),并在 VoidCallback 中改变一些些变量数值等,组件会重新 build 以达到数显状态/UI的效果。

管理状态的常见方法:

widget 管理自己的 state
父 widget 管理子 widget 状态
混合管理

决定状态管理的原则:

有关用户数据由父 widget 管理
有关界面效果由 widget 本身管理
状态被不同 widget 共享,由他们共同的父 widget 管理

(3)Widget支持库
flutter 提供了一套丰富、强大的基础 widget ,在此基础上还提供了Android 默认风格库: Material 与 IOS 风格库:Cupertino。
(4)基础Widget

Text:文本
Row:水平布局,基于 web Flexbox 布局模型。
Column:垂直布局,基于 web Flexbox 布局模型。
Stack:取代线性布局,与 Android 中 FrameLayout相似,允许子 widget 堆叠,使用 positioned 定位它们相对于上下左右四条边的位置。基于 web absolute positioning(绝对定位) 布局模型
Container:矩形元素,可以装饰 BoxDecoration,如 background、边框、阴影,它可以具有边距(margins)、填充(padding)和应用与其大小的约束(constraints)。Container 可以使用矩阵在三维空间中对其进行变换

2、element
Element定义

An instantiation of a Widget at a particular location in the tree.

Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An Element represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location.

Elements form a tree. Most elements have a unique child, but some widgets (e.g., subclasses of RenderObjectElement) can have multiple children.

widget 中主要包含了组件的配置数据,但它并不代表最终绘制在屏幕上的显示元素,真正代表屏幕上显示元素的是 element,widget 是 element 的配置数据,一个 widget 可同时对应多个 element
3、state

每一个 StatefulWidget 类都会对应一个 State 类,State 表示与其对应的 StatefulWidget 要维护的状态,保存的状态信息可以在 build 时被获取,同时,在 widget 生命周期中可以被改变,改变发生时,可以调用其 setState() 方法通知 framework 发生改变,framework 会重新调用 build 方法重构 widget 树,最终完成更新 UI 的目的。
 

state 中包含两个常用属性:widget 和 contextwidget 属性表示当前正在关联的 widget 实例,但关联关系可能会在 widget 重构时发生变化(framework 会动态设置 widget 属性为最新的widget 对象)。context 属性是 buildContext 类的实例,表示构建 widget 的上下文,每个 widget 都有一个自己的 context 对象,它包含了查找、遍历当前 widget 树的方法。
 

State 的生命周期:

主要文章: State

在StatefulWidget调用createState之后,框架将新的状态对象插入树中,然后调用状态对象的initState。 子类化State可以重写initState,以完成仅需要执行一次的工作。 例如,您可以重写initState以配置动画或订阅platform services。initState的实现中需要调用super.initState

当一个状态对象不再需要时,框架调用状态对象的dispose。 您可以覆盖该dispose方法来执行清理工作。例如,您可以覆盖dispose取消定时器或取消订阅platform services。 dispose典型的实现是直接调用super.dispose

initState:当前 widget 对象插入 widget树中时调用
didChangeDependencies:当前 State 对象的依赖项发生变化时调用
build:绘制当前界面布局时调用
reassemble:使用热重载时调用
didUpdateWidget:widget 配置内容有变动重构时调用
deactivate:当前 widget 对象从 widget 树中移出时调用
dispose:当前 widget 对象从 widget 树中永久删除时调用

二、基础 Widget

Flutter跨平台移动端开发丨Widget、Element、State、状态管理

主要文章: widget概述-布局模型

Flutter有一套丰富、强大的基础widget,其中以下是很常用的:

Text:该 widget 可让创建一个带格式的文本。

Row、 Column: 这些具有弹性空间的布局类Widget可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于web开发中的Flexbox布局模型。

Stack: 取代线性布局 (译者语:和Android中的LinearLayout相似),Stack允许子 widget 堆叠, 你可以使用 Positioned 来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝度定位(absolute positioning )布局模型设计的。

Container: Container 可让您创建矩形视觉元素。container 可以装饰为一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可以使用矩阵在三维空间中对其进行变换。

三、Material组件

主要文章: Widgets 总览 – Material 组件

Flutter提供了许多widgets,可帮助您构建遵循Material Design的应用程序。Material应用程序以MaterialApp widget开始, 该widget在应用程序的根部创建了一些有用的widget,其中包括一个Navigator, 它管理由字符串标识的Widget栈(即页面路由栈)。Navigator可以让您的应用程序在页面之间的平滑的过渡。 是否使用MaterialApp完全是可选的,但是使用它是一个很好的做法。

四、处理手势

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      child: new Container(
        height: 36.0,
        padding: const EdgeInsets.all(8.0),
        margin: const EdgeInsets.symmetric(horizontal: 8.0),
        decoration: new BoxDecoration(
          borderRadius: new BorderRadius.circular(5.0),
          color: Colors.lightGreen[500],
        ),
        child: new Center(
          child: new Text('Engage'),
        ),
      ),
    );
  }
}

GestureDetector widget并不具有显示效果,而是检测由用户做出的手势。 当用户点击Container时, GestureDetector会调用它的onTap回调, 在回调中,将消息打印到控制台。您可以使用GestureDetector来检测各种输入手势,包括点击、拖动和缩放。

许多widget都会使用一个GestureDetector为其他widget提供可选的回调。 例如,IconButton、 RaisedButton、 和FloatingActionButton ,它们都有一个onPressed回调,它会在用户点击该widget时被触发。

五、StatefulWidget StatefulWidget State 

Flutter跨平台移动端开发丨Widget、Element、State、状态管理

1、简述

在Flutter中,这两种类型的对象具有不同的生命周期: Widget是临时对象,用于构建当前状态下的应用程序,而State对象在多次调用build()之间保持不变,允许它们记住信息(状态)。

2、关于状态的demo,仔细体会

(1)两个无状态的Widget 和 一个有状态的Widget的清晰的分离了,显示和更改的逻辑,将复杂性逻辑封装在各个widget中,同时保持父项的简单性。

(2)事件流是“向上”传递的,而状态流是“向下”传递的

  ps:函数的生命和调用分离,实现了回调;体会无状态,和有状态部件的区别。

class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});

  final int count;

  @override
  Widget build(BuildContext context) {
    return new Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  CounterIncrementor({this.onPressed});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return new RaisedButton(
      onPressed: onPressed,
      child: new Text('Increment'),
    );
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => new _CounterState();
}

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Row(children: <Widget>[
      new CounterIncrementor(onPressed: _increment),
      new CounterDisplay(count: _counter),
    ]);
  }
}

3、Key

深入浅出 Key

Flutter中key的作用    demo 

(1)什么是key

我们知道 Widget 可以有 Stateful 和 Stateless 两种。Key 能够帮助开发者在 Widget tree 中保存状态

Key is an identifier for Widgets, Elements and SemanticsNodes.

A new widget will only be used to update an existing element if its key is the same as the key of the current widget associated with the element.

Keys must be unique amongst the Elements with the same parent.

Subclasses of Key should either subclass LocalKey or GlobalKey

See also the discussion at Widget.key.

Widget的源码:

@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;
  ···
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

我们知道 Widget 只是一个配置且无法修改,而 Element 才是真正被使用的对象,并可以修改。当新的 Widget 到来时将会调用 canUpdate 方法,来确定这个 Element是否需要更新。

canUpdate 对两个(新老) Widget 的 runtimeType 和 key 进行比较,从而判断出当前的 Element 是否需要更新

StatelessContainer 比较过程:

在 StatelessContainer 中,我们并没有传入 key ,所以只比较它们的 runtimeType。我们将 color 属性定义在了 Widget 中,这将导致他们具有不同的 runtimeType。所以在 StatelessContainer 这个例子中,Flutter能够正确的交换它们的位置。

StatefulContainer 比较过程:

而在 StatefulContainer 的例子中,我们将 color 的定义放在了 State 中,Widget 并不保存 State,真正 hold State 的引用的是 Stateful Element。当我们没有给 Widget 任何 key 的时候,将会只比较这两个 Widget 的 runtimeType 。由于两个 Widget 的属性和方法都相同,canUpdate 方法将会返回 false,在 Flutter 看来,并没有发生变化。所以这两个 Element 将不会交换位置。而我们给 Widget 一个 key 之后,canUpdate 方法将会比较两个 Widget 的 runtimeType 以及 key。并返回 true,现在 Flutter 就可以正确的感知到两个 Widget 交换了顺序了。 (这里 runtimeType 相同,key 不同)

比较范围:

···
class _ScreenState extends State<Screen> {
  List<Widget> widgets = [
    Padding(
      padding: const EdgeInsets.all(8.0),
      child: StatefulContainer(key: UniqueKey(),),
    ),
    Padding(
      padding: const EdgeInsets.all(8.0),
      child: StatefulContainer(key: UniqueKey(),),
    ),
  ];
···

分析;

我们分析一下这次的Widget Tree 和 Element Tree,当我们交换元素后,Flutter element-to-widget matching algorithm,(元素-组件匹配算法),开始进行对比,算法每次只对比一层,即Padding这一层。显然,Padding并没有发生本质的变化。

于是开始进行第二层对比,在对比时Flutter发现元素与组件的Key并不匹配,于是,把它设置成不可用状态,但是这里所使用的Key只是本地Key(Local Key),Flutter并不能找到另一层里面的Key(即另外一个Padding Widget中的Key)所以,Flutter就创建了一个新的Widget,而这个Widget的颜色就成了我们看到的『随机色』。

(2)什么时候使用key?

当使用Stateless Widget时,我们并不需要使用key,当使用Stateful Widget时,集合内有数据移动和改变并且需要展示到界面时才需要key。

(3)Key应该用到哪?

我们的Key要设置到组件树的顶层,而这一层在改变时,才能复用或者更新状态。

(4)用哪一种Key?

Flutter中有很多Key,但是总体分为两种 Local Key和Global Key两种。

Flutter入门(一)Flutter Widget框架概述-风君雪科技博客

对于Key的研究还不是特别多,有时间再补一篇,上面的例子,因为没有数据,所以使用了UniqueKey,在真实的开发中,我们可以用Model中的id作为ObjectKey。

GlobalKey其实是对应于LocalKey,上面我们说Padding中的就是LocalKey,Global即可以在多个页面或者层级复用,比如两个页面也可也同时保持一个状态。