flutter Widget基础

首先,我们先附上官网教程中关于flutter框架的介绍,作为补充。

Flutter官方提供的Flutter框架图
flutter Widget基础_第1张图片
Flutter Framework
这是一个纯 Dart实现的 SDK:

Foundation和Animation、Painting、Gestures 在Google的一些视频中被合并为一个dart UI层,对应的是Flutter中的dart:ui包,它是Flutter引擎暴露的底层UI库,提供动画、手势及绘制能力。

Rendering层是一个抽象的布局层,它依赖于dart UI层,Rendering层会构建一个UI树,当UI树有变化时,会计算出有变化的部分,然后更新UI树,最终将UI树绘制到屏幕上,这个过程类似于React中的虚拟DOM。

Widgets层是Flutter提供的的一套基础组件库,在基础组件库之上,Flutter还提供了 Material 和Cupertino两种视觉风格的组件库。

Flutter Engine
这是一个纯 C++实现的 SDK,其中包括了 Skia引擎、Dart运行时、文字排版引擎等。在代码调用 dart:ui库时,调用最终会走到Engine层,然后实现真正的绘制逻辑。

在我们学习flutter的过程中,很多老师大咖们最喜欢说的一句话就是flutter一切皆widget。
Flutter中Widget不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于APP主题数据传递的Theme等等,而原生开发中的控件通常只是指UI元素。

我们先来看一下Widget类的声明:

/// Describes the configuration for an [Element].
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;
  }
}

其中有一句注释我特别给大家列出来了:Describes the configuration for an [Element].

在Flutter中,Widget的功能是“描述一个UI元素的配置数据”,也就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,而它只是描述显示元素的一个配置数据。实际上在flutter中真正代表屏幕上显示元素的类是Element。对于我们这些希望能够达到使用目的的初学者来说,我觉得没有必要过于深究。

我们只需要知道:Widget只是UI元素的一个配置数据,并且一个Widget可以对应多个Element。这是因为同一个Widget对象可以被添加到UI树的不同部分,而真正渲染时,UI树的每一个Element节点都会对应一个Widget对象。

再来看看类接口:

widget类继承自DiagnosticableTree,DiagnosticableTree即“诊断树”,主要作用是提供调试信息。

Key: 这个key属性类似于React/Vue中的key,主要的作用是决定是否在下一次build时复用旧的widget,决定的条件在canUpdate()方法中。

createElement**()**:Flutter Framework在构建UI树时,会先调用此方法生成对应节点的Element对象。此方法是Flutter Framework隐式调用的,在我们开发过程中基本不会调用到。

debugFillProperties(…) 复写父类的方法,主要是设置诊断树的一些特性。

**canUpdate(…)**是一个静态方法,可以理解为是否用新的Widget对象去更新旧UI树上所对应的Element对象的配置。

Widget 类本身是一个抽象类,其中最核心的就是定义了createElement()接口,在Flutter开发中,我们一般都不用直接继承Widget类来实现一个新组件,相反,我们通常会通过继承StatelessWidget或StatefulWidget来间接继承Widget类来实现。

StatelessWidget和StatefulWidget都是直接继承自Widget类,而这两个类也正是Flutter中非常重要的两个抽象类,它们引入了两种Widget模型:有状态的组件(Stateful widget) 和无状态的组件(Stateless widget)。

无状态的组件(Stateless widget)

StatelessWidget用于不需要维护状态的场景,也就是说它初始化后就不会更改,比如标题等等。一般情况下,如果widget对应的ui不需要进行更新时,那么我们就应该使用StatelessWidget。

它通常在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget。

StatelessWidget继承自Widget类,重写了createElement()方法:

@override
StatelessElement createElement() => new StatelessElement(this);

StatelessElement 间接继承自Element类,与StatelessWidget相对应(作为其配置数据)。

接下来我们看一个简单的示例:

import  "package:flutter/material.dart";

class QStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("hello flutter");
  }
}

在main.dart中使用:
1.导入自定义的widget
2.在需要使用的地方创建对应的实例

import "package:flutter/material.dart";
import 'basic/QStatelessWidget.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text("Flutter 基础"),
          ),
          body: Center(
            child:QStatelessWidget(),
          ),
        ));
  }
}

运行结果:

flutter Widget基础_第2张图片

显然这样简单的使用并不能满足我们的胃口,接下来我们在看看怎么使用widget的构造函数进行传参。

按照惯例,widget的构造函数参数应使用命名参数,命名参数中的必要参数要添加@required标注,这样有利于静态代码分析器进行检查。另外,在继承widget时,第一个参数通常应该是Key,另外,如果Widget需要接收子Widget,那么child或children参数通常应被放在参数列表的最后。同样是按照惯例,Widget的属性应尽可能的被声明为final,防止被意外改变。

import "package:flutter/material.dart";

class QStatelessWidget extends StatelessWidget {
  QStatelessWidget({Key key, @required this.text, this.fontColor: Colors.green})
      : super(key: key);

  final String text;
  final Color fontColor;
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Text(
          text,
          style: TextStyle(
              color: fontColor,
              fontSize: 24,
              fontFamily: "Courier",
              decoration: TextDecoration.underline,
              decorationStyle: TextDecorationStyle.dashed),
        ),
      ),
    );
  }
}

其中构造函数:

 QStatelessWidget({Key key, @required this.text, this.fontColor: Colors.green})
      : super(key: key);

这里使用的是dart的语法糖,(详细请参考dart的相关文档)等价于下面这种形式:

QStatelessWidget(String text, Color fontColor=Colors.green){
	this.text = text;
	this.fontColor = fontColor;
}

this.fontColor: Colors.green相当于其他语言中的默认参数值,这点大家想必都知道。

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

具体的代码没有什么看的,这里就不在赘述了。在使用的地方我们使用命名参数传递对应的参数即可。

flutter Widget基础_第3张图片
如果我们没有使用命名参数,那么会得到下面的错误提示,:
flutter Widget基础_第4张图片

运行效果:
flutter Widget基础_第5张图片

有状态的组件(Stateful widget)

StatefulWidget又被称为有状态组件,开发者可以根据用户的操作来选择性的更新界面上的组件。

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

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);

  @override
  StatefulElement createElement() => new StatefulElement(this);

  @protected
  State createState();
}

接下来我们创建一个基于statefulwidget的例子。刚创建出来的文件可能向下面的例子一样简单,我们一步步开始。

flutter Widget基础_第6张图片

一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态

createState() 用于创建和Stateful widget相关的状态,它在Stateful widget的生命周期中可能会被多次调用。例如,当一个Stateful widget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。

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

接下来我们来个简单的例子:

import 'package:flutter/material.dart';

class QStatefulWidget extends StatefulWidget {
  const QStatefulWidget({Key key, this.current: 0}) : super(key: key);

  final int current;

  @override
  _QStatefulWidgetState createState() => _QStatefulWidgetState();
}

class _QStatefulWidgetState extends State {
  int current;

  final welcomeArray = const [
    "Welcome,first.",
    "Welcome,second.",
    "Welcome,third.",
    "Welcome,fourth."
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FlatButton(
          child: Text(welcomeArray[current]),
          onPressed: () => setState(this.handlePressed),
        ),
      ),
    );
  }

  void handlePressed() {
    if (current == welcomeArray.length - 1) {
      current = 0;
    } else {
      ++current;
    }
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    current = widget.current;
  }
}

代码十分简单,在屏幕中央显示一个扁平按钮,当点击的时候我们会切换其上面显示的文字。

在main.dart中指定其初始显示的文字索引

flutter Widget基础_第7张图片

运行效果:
flutter Widget基础_第8张图片

点击后:
flutter Widget基础_第9张图片

作为我们日后使用最多的widget,我们有必要了解一下它的生命周期,先贴一张StatefulWidget生命周期图
flutter Widget基础_第10张图片

ok,我们将其中用到的方法重载,添加log语句,直观的看下其各个状态的变化。

 @override
  void initState() {
    // TODO: implement initState
    super.initState();
    print("initState function run");
    current = widget.current;
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    print("didChangeDependencies function run");
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    print("dispose function run");
  }

  @override
  void reassemble() {
    // TODO: implement reassemble
    super.reassemble();
    print("reassemble function run");
  }

  @override
  void deactivate() {
    // TODO: implement deactivate
    super.deactivate();
    print("deactivate function run");
  }

  @override
  void didUpdateWidget(QStatefulWidget oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget function run");
  }

重新运行程序,打印内容如下:
flutter Widget基础_第11张图片

点击按钮:

在这里插入图片描述

在main.dart中移除该widget
flutter Widget基础_第12张图片
热加载后输出:
flutter Widget基础_第13张图片

initState:当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。

didChangeDependencies:当State对象的依赖发生变化时会被调用。

build:用于构建Widget子树的,会在如下场景被调用:

1.调用initState()之后
2.调用didUpdateWidget()之后。
3.调用setState()之后。
4.调用didChangeDependencies()之后。
5.State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。

reassemble:在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。

didUpdateWidget:在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。

deactivate:当State对象从树中被移除时,会调用此回调。

dispose:当State对象从树中被永久移除时调用。

更多的内容我们暂时不深入探究,我们只要了解了其各个状态即可。

另外,Flutter Widget build方法可能会执行多次,所以在build方法内做的事情应该尽可能的少。

在使用StatefulWidget的过程中将会调用到setState((){}) 的代码尽可能的和要更新的视图封装在一个尽可能小的模块里。

你可能感兴趣的:(flutter)