Flutter(七)之有状态的StatefulWidget

前言一:接下来一段时间我会陆续更新一些列Flutter文字教程html

更新进度: 每周至少两篇;前端

更新地点: 首发于公众号,次日更新于掘金、思否、开发者头条等地方;vue

更多交流: 能够添加个人微信 372623326,关注个人微博:coderwhy算法

但愿你们能够 帮忙转发,点击在看,给我更多的创做动力。编程

一. StatefulWidget

在开发中,某些Widget状况下咱们展现的数据并非一层不变的:api

好比Flutter默认程序中的计数器案例,点击了+号按钮后,显示的数字须要+1;浏览器

好比在开发中,咱们会进行下拉刷新、上拉加载更多,这时数据也会发生变化;微信

而StatelessWidget一般用来展现哪些数据固定不变的,若是数据会发生改变,咱们使用StatefulWidget;网络

1.1. 认识StatefulWidget

1.1.1. StatefulWidget介绍

若是你有阅读过默认咱们建立Flutter的示例程序,那么你会发现它建立的是一个StatefulWidget。数据结构

为何选择StatefulWidget呢?

  • 由于在示例代码中,当咱们点击按钮时,界面上显示的数据会发生改变;
  • 这时,咱们须要一个变量来记录当前的状态,再把这个变量显示到某个Text Widget上;
  • 而且每次变量发生改变时,咱们对应的Text上显示的内容也要发生改变;

可是有一个问题,我以前说过定义到Widget中的数据都是不可变的,必须定义为final,为何呢?

  • 此次由于Flutter在设计的时候就决定了一旦Widget中展现的数据发生变化,就从新构建整个Widget;
  • 下一个章节我会讲解Flutter的渲染原理,Flutter经过一些机制来限定定义到Widget中的成员变量必须是final的;

Flutter如何作到咱们在开发中定义到Widget中的数据必定是final的呢?

咱们来看一下Widget的源码:

@immutable
abstract class Widget extends DiagnosticableTree {
	// ...省略代码
}
复制代码

这里有一个很关键的东西@immutable

  • 咱们彷佛在Dart中没有见过这种语法,这其实是一个 注解,这设计到Dart的元编程,咱们这里不展开讲;
  • 这里我就说明一下这个@immutable是干什么的;

实际上官方有对@immutable进行说明:

image-20190917202801994

结论: 定义到Widget中的数据必定是不可变的,须要使用final来修饰

1.1.2. 如何存储Widget状态?

既然Widget是不可变,那么StatefulWidget如何来存储可变的状态呢?

  • StatelessWidget无所谓,由于它里面的数据一般是直接定义玩后就不修改的。
  • 但StatefulWidget须要有状态(能够理解成变量)的改变,这如何作到呢?

Flutter将StatefulWidget设计成了两个类:

  • 也就是你建立StatefulWidget时必须建立两个类:
  • 一个类继承自StatefulWidget,做为Widget树的一部分;
  • 一个类继承自State,用于记录StatefulWidget会变化的状态,而且根据状态的变化,构建出新的Widget;

建立一个StatefulWidget,咱们一般会按照以下格式来作:

  • 当Flutter在构建Widget Tree时,会获取State的实例,而且它调用build方法去获取StatefulWidget但愿构建的Widget;
  • 那么,咱们就能够将须要保存的状态保存在MyState中,由于它是可变的;
class MyStatefulWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // 将建立的State返回
    return MyState();
  }
}

class MyState extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return <构建本身的Widget>;
  }
}
复制代码

思考:为何Flutter要这样设计呢?

这是由于在Flutter中,只要数据改变了Widget就须要从新构建(rebuild)

1.2. StatefulWidget案例

1.2.1. 案例效果和分析

咱们经过一个案例来练习一下StatefulWidget,仍是以前的计数器案例,可是咱们按照本身的方式进行一些改进。

案例效果以及布局以下:

  • 在这个案例中,有不少布局对于咱们来讲有些复杂,咱们后面会详细学习,建议你们根据个人代码一步步写出来来熟悉Flutter开发模式;
  • Column小部件:以前咱们已经用过,当有垂直方向布局时,咱们就使用它;
  • Row小部件:以前也用过,当时水平方向布局时,咱们就使用它;
  • RaiseButton小部件:能够建立一个按钮,而且其中有一个onPress属性是传入一个回调函数,当按钮点击时被回调;

image-20190917214653945

1.2.2. 建立StatefulWidget

下面咱们来看看代码实现:

  • 由于当点击按钮时,数字会发生变化,因此咱们须要使用一个StatefulWidget,因此咱们须要建立两个类;
  • MyCounterWidget继承自StatefulWidget,里面须要实现createState方法;
  • MyCounterState继承自State,里面实现build方法,而且能够定义一些成员变量;
class MyCounterWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // 将建立的State返回
    return MyCounterState();
  }
}

class MyCounterState extends State<MyCounterWidget> {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text("当前计数:$counter", style: TextStyle(fontSize: 30),),
    );
  }
}
复制代码

image-20190917215514053

1.2.3. 实现按钮的布局

class MyCounterState extends State<MyCounterWidget> {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              RaisedButton(
                color: Colors.redAccent,
                child: Text("+1", style: TextStyle(fontSize: 18, color: Colors.white),),
                onPressed: () {

                },
              ),
              RaisedButton(
                color: Colors.orangeAccent,
                child: Text("-1", style: TextStyle(fontSize: 18, color: Colors.white),),
                onPressed: () {

                },
              )
            ],
          ),
          Text("当前计数:$counter", style: TextStyle(fontSize: 30),)
        ],
      ),
    );
  }
}
复制代码

image-20190917215915106

1.2.4. 按钮点击状态改变

咱们如今要监听状态的改变,当状态改变时要修改counter变量

  • 可是,直接修改变量能够改变界面吗?不能够。
  • 这是由于Flutter并不知道咱们的数据发生了改变,须要来从新构建咱们界面中的Widget;

如何可让Flutter知道咱们的状态发生改变了,从新构建咱们的Widget呢?

  • 咱们须要调用一个State中默认给咱们提供的setState方法;
  • 能够在其中的回调函数中修改咱们的变量;
onPressed: () {
  setState(() {
    counter++;
  });
},
复制代码

这样就能够实现想要的效果了:

image-20190917220412775

1.3. StatefulWidget生命周期

1.3.1. 生命周期的理解

什么是生命周期呢?

  • 客户端开发:iOS开发中咱们须要知道UIViewController从建立到销毁的整个过程,Android开发中咱们须要知道Activity从建立到销毁的整个过程。以便在不一样的生命周期方法中完成不一样的操做;
  • 前端开发中:Vue、React开发中组件也都有本身的生命周期,在不一样的生命周期中咱们能够作不一样的操做;

Flutter小部件的生命周期:

  • StatelessWidget能够由父Widget直接传入值,调用build方法来构建,整个过程很是简单;
  • 而StatefulWidget须要经过State来管理其数据,而且还要监控状态的改变决定是否从新build整个Widget;
  • 因此,咱们主要讨论StatefulWidget的生命周期,也就是它从建立到销毁的整个过程;

1.3.2. 生命周期的简单版

在这个版本中,我讲解那些经常使用的方法和回调,下一个版本中我解释一些比较复杂的方法和回调

那么StatefulWidget有哪些生命周期的回调呢?它们分别在什么状况下执行呢?

  • 在下图中,灰色部分的内容是Flutter内部操做的,咱们并不须要手动去设置它们;
  • 白色部分表示咱们能够去监听到或者能够手动调用的方法;

咱们知道StatefulWidget自己由两个类组成的:StatefulWidgetState,咱们分开进行分析

首先,执行StatefulWidget中相关的方法:

  • 一、执行StatefulWidget的构造函数(Constructor)来建立出StatefulWidget;
  • 二、执行StatefulWidget的createState方法,来建立一个维护StatefulWidget的State对象;

其次,调用createState建立State对象时,执行State类的相关方法:

  • 一、执行State类的构造方法(Constructor)来建立State对象;

  • 二、执行initState,咱们一般会在这个方法中执行一些数据初始化的操做,或者也可能会发送网络请求;

    • 注意:这个方法是重写父类的方法,必须调用super,由于父类中会进行一些其余操做;
    • 而且若是你阅读源码,你会发现这里有一个注解(annotation):@mustCallSuper

    image-20190918212956907

  • 三、执行didChangeDependencies方法,这个方法在两种状况下会调用

    • 状况一:调用initState会调用;
    • 状况二:从其余对象中依赖一些数据发生改变时,好比前面咱们提到的InheritedWidget(这个后面会讲到);
  • 四、Flutter执行build方法,来看一下咱们当前的Widget须要渲染哪些Widget;

  • 五、当前的Widget再也不使用时,会调用dispose进行销毁;

  • 六、手动调用setState方法,会根据最新的状态(数据)来从新调用build方法,构建对应的Widgets;

  • 七、执行didUpdateWidget方法是在当父Widget触发重建(rebuild)时,系统会调用didUpdateWidget方法;

咱们来经过代码进行演示:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("HelloWorld"),
        ),
        body: HomeBody(),
      ),
    );
  }
}


class HomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("HomeBody build");
    return MyCounterWidget();
  }
}


class MyCounterWidget extends StatefulWidget {
  
  MyCounterWidget() {
    print("执行了MyCounterWidget的构造方法");
  }
  
  @override
  State<StatefulWidget> createState() {
    print("执行了MyCounterWidget的createState方法");
    // 将建立的State返回
    return MyCounterState();
  }
}

class MyCounterState extends State<MyCounterWidget> {
  int counter = 0;
  
  MyCounterState() {
    print("执行MyCounterState的构造方法");
  }

  @override
  void initState() {
    super.initState();
    print("执行MyCounterState的init方法");
  }
  
  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    print("执行MyCounterState的didChangeDependencies方法");
  }

  @override
  Widget build(BuildContext context) {
    print("执行执行MyCounterState的build方法");
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              RaisedButton(
                color: Colors.redAccent,
                child: Text("+1", style: TextStyle(fontSize: 18, color: Colors.white),),
                onPressed: () {
                  setState(() {
                    counter++;
                  });
                },
              ),
              RaisedButton(
                color: Colors.orangeAccent,
                child: Text("-1", style: TextStyle(fontSize: 18, color: Colors.white),),
                onPressed: () {
                  setState(() {
                    counter--;
                  });
                },
              )
            ],
          ),
          Text("当前计数:$counter", style: TextStyle(fontSize: 30),)
        ],
      ),
    );
  }

  @override
  void didUpdateWidget(MyCounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("执行MyCounterState的didUpdateWidget方法");
  }

  @override
  void dispose() {
    super.dispose();
    print("执行MyCounterState的dispose方法");
  }
}
复制代码

打印结果以下:

flutter: HomeBody build
flutter: 执行了MyCounterWidget的构造方法
flutter: 执行了MyCounterWidget的createState方法
flutter: 执行MyCounterState的构造方法
flutter: 执行MyCounterState的init方法
flutter: 执行MyCounterState的didChangeDependencies方法
flutter: 执行执行MyCounterState的build方法

// 注意:Flutter会build全部的组件两次(查了GitHub、Stack Overflow,目前没查到缘由)
flutter: HomeBody build
flutter: 执行了MyCounterWidget的构造方法
flutter: 执行MyCounterState的didUpdateWidget方法
flutter: 执行执行MyCounterState的build方法
复制代码

当咱们改变状态,手动执行setState方法后会打印以下结果:

flutter: 执行执行MyCounterState的build方法
复制代码

1.3.3. 生命周期的复杂版(选读)

咱们来学习几个前面生命周期图中提到的属性,可是没有详细讲解的

一、mounted是State内部设置的一个属性,事实上咱们不了解它也能够,可是若是你想深刻了解它,会对State的机制理解更加清晰;

  • 不少资料没有提到这个属性,可是我这里把它列出来,是内部设置的,不须要咱们手动进行修改;

image-20190918212620587

二、dirty state的含义是脏的State

  • 它实际是经过一个Element的东西(咱们尚未讲到Flutter绘制原理)的属性来标记的;
  • 将它标记为dirty会等待下一次的重绘检查,强制调用build方法来构建咱们的Widget;
  • (有机会我专门写一篇关于StatelessWidget和StatefulWidget的区别,讲解一些它们开发中的选择问题);

三、clean state的含义是干净的State

  • 它表示当前build出来的Widget,下一次重绘检查时不须要从新build;

二. Flutter的编程范式

这个章节又讲解一些理论的东西,可能并不会直接讲授Flutter的知识,可是会对你之后写任何的代码,都具有一些简单的知道思想;

2.1. 编程范式的理解

编程范式对于初学编程的人来讲是一个虚无缥缈的东西,可是倒是咱们平常开发中都在默认遵循的一些模式和方法

好比咱们最为熟悉的 面向对象编程就是一种编程范式,与之对应或者结合开发的包括:面向过程编程、函数式编程、面向协议编程;

另外还有两个对应的编程范式:命令式编程声明式编程

  • 命令式编程: 命令式编程很是好理解,就是一步步给计算机命令,告诉它咱们想作什么事情;
  • 声明式编程: 声明式编程一般是描述目标的性质,你应该是什么样的,依赖哪些状态,而且当依赖的状态发生改变时,咱们经过某些方式通知目标做出相应;

上面的描述仍是太笼统了,咱们来看一些具体点的例子;

2.2. 前端的编程范式

下面的代码没有写过前端的能够简单看一下

下面的代码是在前端开发中我写的两个demo,做用都是点击按钮后修改h2标签的内容:

  • 左边代码: 命令式编程,一步步告诉浏览器我要作什么事情;
  • 右边代码: 声明式编程,我只是告诉h2标签中我须要显示title,当title发生改变的时候,经过一些机制自动来更新状态;

image-20190919120003281

2.3. Flutter的编程范式

从2009年开始(数据来自维基百科),声明式编程就开始流行起来,而且目前在Vue、React、包括iOS中的SwiftUI中以及Flutter目前都采用了声明式编程。

如今咱们来开发一个需求:显示一个Hello World,以后又修改为了Hello Flutter

若是是传统的命令式编程,咱们开发Flutter的模式极可能是这样的:(注意是想象中的伪代码)

  • 整个过程,咱们须要一步步告诉Flutter它须要作什么;
final text = new Text();
var title = "Hello World";
text.setContent(title);

// 修改数据
title = "Hello Flutter";
text.setContent(title);

复制代码

若是是声明式编程,咱们一般会维护一套数据集:

  • 这个数据集可能来本身父类、来自自身State管理、来自InheritedWidget、来自统一的状态管理的地方;
  • 总之,咱们知道有这么一个数据集,而且告诉Flutter这些数据集在哪里使用;
var title = "Hello World";

Text(title); // 告诉Text内部显示的是title

// 数据改变
title = "Hello Flutter";
setState(() => null); // 通知从新build Widget便可

复制代码

上面的代码过于简单,可能不能体现出Flutter声明式编程的优点所在,可是在之后的开发中,咱们都是按照这种模式在进行开始,咱们一块儿来慢慢体会;

备注:全部内容首发于公众号,以后除了Flutter也会更新其余技术文章,TypeScript、React、Node、uniapp、mpvue、数据结构与算法等等,也会更新一些本身的学习心得等,欢迎你们关注

公众号
相关文章
相关标签/搜索