Flutter 基础组件:Widget简介

概念

在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生命周期

如下图:

原文地址:https://www.cnblogs.com/parzulpan/p/12056387.html

时间: 2024-11-13 08:52:15

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

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

// 文本.字体样式 import 'package:flutter/material.dart'; class TextFontStyle extends StatelessWidget { // 声明文本样式 TextStyle textStyle = const TextStyle(fontFamily: 'MyFont', fontSize: 30, ); @override Widget build(BuildContext context) { return Scaffold( ap

flutter 基础组件

TextWidget 1 class TextWidget extends StatelessWidget { 2 final TextStyle _textStyle = TextStyle( 3 fontSize: 16.0, 4 ); 5 final String _auther = '李白'; 6 final String _title = '将进酒'; 7 8 9 @override 10 Widget build(BuildContext context) { 11 return T

Akka(17): Stream:数据流基础组件-Source,Flow,Sink简介

在大数据程序流行的今天,许多程序都面临着共同的难题:程序输入数据趋于无限大,抵达时间又不确定.一般的解决方法是采用回调函数(callback-function)来实现的,但这样的解决方案很容易造成“回调地狱(callback hell)”,即所谓的“goto-hell”:程序控制跳来跳去很难跟踪,特别是一些变量如果在回调函数中更改后产生不可预料的结果.数据流(stream)是一种解决问题的有效编程方式.Stream是一个抽象概念,能把程序数据输入过程和其它细节隐蔽起来,通过申明方式把数据处理过程

Flutter基础系列之入门(一)

1.Flutter是什么? 官方介绍:Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费.开源的. 从官方介绍可以看到,Flutter有如下特点: 跨平台:现在Flutter至少可以跨4种平台,甚至支持嵌入式开发.我们常用的有Linux.Android.IOS,甚至可以在谷歌最新的操作系统上Fuchsia进行运行,经过第三方

谷歌移动UI框架Flutter教程之Widget

引言 在之间我已经介绍了关于Flutter的下载安装以及配置,还有开发工具Android Studio的配置,还不知道的同学可以看看我这篇博客--谷歌移动UI框架Flutter入门.这里为什么非要用Android Studio,我可以解释一下.Android Studio是Google的亲儿子,由谷歌一手开发,而Flutter也是谷歌推出的技术,所以在支持和兼容问题上,Android Studio是非常有优势的.老话说得好,肥水不流外人田,谷歌内部肯定是将Android Studio对Flutt

flutter常用组件

TextWidget  文本组件 TextWidget  的常用属性 TextAlign: 下面的五种对齐方式 左对齐, 右对齐, 居中对齐, 从开始位置对齐 MaxLines: 显示几行 overflow: 超出范围截取后的样式 clip 直接阶段, ellipsis 后面省略号... fade 超出后渐变, decoration : underline 下划线 linethrough 中间穿过 none 不显示 overline 上划线 decorationStye: 下划线样式 dashe

3. playbook基础组件

Playbook playbook是由一个或多个“play”组成的列表.play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色. 从根本上来讲,所谓task无非是调用ansible的一个module.将多个play组织在一个playbook中,即可以让它们联同起来按事先编排的机制同唱一台大戏. playbook基础组件 Hosts和Users playbook中的每一个play的目的都是为了让某个或某些主机以某个指定的用户身份执行任务. hosts用于指

Android 基础组件

基础组件 所有的控件都可以在java代码中创建出来,并且大部分的属性都对应set和get方法,比如 View view = new View(Context context)  context是上下文,是Activity父类,一般传入当前Activity 1.TextView text 文本 setText() getText() textColor文本颜色 #FFFFFF setTextColor(Color.Blue) getTextColor() textSize文本大小   sp set

数据库写库基础组件设计思想与实现

码农一定会遇到写库的繁琐操作,字段少的话数据访问层的SQL语句封装还好实现,可是字段一旦多起来,比如十多个二十多个字段的话,SQL的封装将会是一个巨大的难题,并不是说难度有多大,而是这样的操作很繁琐,况且极容易出错,SQL语句一旦出错很难排查.我也是在开发中遇到了相同的问题,这样的问题总会浪费很多不必要的时间,所以我就想能不能提供一个公共的基础组件来实现繁琐的底层SQL语句操作,我们只需要调用一些简单的借口就能实现数据库的快捷的写库.首先,写库时必要的信息包含:要写入的列名,还有就是数据实体.(