Flutter - StateWidget与生命周期

欢迎关注微信公众号:FSA全栈行动 👋

一、程序入口

一般情况下,Flutter 的主入口是 main.dart

1、界面的本质(Widget)

在 main 函数中通过 runApp 函数启动一个 Flutter 界面,而 runApp(Widget app) 函数接收的参数 Widget app 就是界面了,即界面是一个 Widget

main() {
  runApp(Center(
    child: Text("Hello world",
        textDirection: TextDirection.ltr,
        style: TextStyle(fontSize: 30, color: Colors.orange)),
  ));
}

在 Flutter 中,所有的界面、控件都是 Widget

2、MaterialApp

为了快速开发一个标准 material 的界面,Flutter 提供了 MaterialApp 这个 Widget:

import 'package:flutter/material.dart';

main() {
  // 1. runApp 函数
  runApp(MaterialApp(
    // debugShowCheckedModeBanner: false, // 控制界面右上角是否显示`debug`提示
    home: Scaffold(
      appBar: AppBar(
        title: Text("第一个Flutter程序"),
      ),
      body: Center(
        child: Text(
          "Hello World",
          textDirection: TextDirection.ltr,
          style: TextStyle(fontSize: 30, color: Colors.orange),
        ),
      ),
    ),
  ));
}

Scaffold 也是 Widget,翻译为脚手架,可以帮助开发者搭建出带 AppBar 的界面

二、State Widget

万物基于 Widget 的 Flutter 把 Widget 分为两类,分别是:

  • StatelessWidget: 无状态的 Widget,内容是确定没有状态(data)的改变
  • StatefulWidget: 有状态的 Widget,在运行过程中有一些状态(data)需要改变

1、StatelessWidget

上面的 demo 中,runApp(Widget app)MaterialApp({this.home})Scaffold({this.body}) 这 3 个方法接收的主要参数都是 Widget 类型,并且不涉及状态的保存,因此可以把它们抽成对应的 3 个 StatelessWidget 来使用,这样就可以有效的避免 嵌套地狱 的发生:

这种抽取出一个个 Widget 的方式,类似于大前端中的组件化开发

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第一个Flutter程序"),
      ),
      body: ContentBody(),
    );
  }
}

class ContentBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        "Hello World",
        textDirection: TextDirection.ltr,
        style: TextStyle(fontSize: 30, color: Colors.orange),
      ),
    );
  }
}

AndroidStudio 中的 State Widget 快速创建模板:

  • stless:输入 stless 直接生成 StatelessWidget
  • stful:输入 stful 直接生成 StatefulWidget

2、StatefulWidget

Flutter 是声明式开发,即一旦状态发生变化,界面会自动改变,这里说的状态就是数据。StatefulWidget 就是可以有状态的 Widget,一个简单的例子:写一个同意协议控件:

/// flag:状态
/// Stateful不能定义状态 -> 创建一个单独的State类,这个State类负责维护状态
class ContentBody extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return ContentBodyState();
  }
}

/// 使用State类来保存状态
class ContentBodyState extends State<ContentBody> {
  var flag = true;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Checkbox(
            value: flag,
            onChanged: (value) {
              /// flag = value; // 无效代码,不会刷新界面
              /// 必须使用 setState() 方法强制界面刷新
              setState(() {
                flag = value;
              });
            },
          ),
          Text("同意协议", style: TextStyle(fontSize: 20)),
        ],
      ),
    );
  }
}

注意:

  • Flutter 中的状态(State)和 React 中的状态概念一致。
  • 单单修改数据是不行的,还必须调用 setState() 通知界面在下一帧重新绘制才行。

3、为什么 State Widget 本身不能定义状态?

无论 StatelessWidget 还是 StatefulWidget,其父类都是 Widget,来看看 Widget 的定义:

@immutable
abstract class Widget extends DiagnosticableTree {
    ...
}

Widget 有 @immutable 注解,就意味着 Widget 的所有子类即使有成员属性,也一定是使用 final 修饰的,试问 final 变量会有变化吗?

/// 所有的Widget类中都不能定义状态,类成员属性必须是final
class ContentBody extends StatelessWidget {
  // IDE报错:This class (or a class that this class inherits from) is marked as '@immutable', but one of more of its instance fields aren't final: ContentBody.flag

  // 错误代码
  // @immutable 注释过的类,成员属性必须是final的
  var flag = true;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Checkbox(value: flag, onChanged: (value) => flag = value),
          Text("同意协议", style: TextStyle(fontSize: 20)),
        ],
      ),
    );
  }
}

三、综合案例

1、StatelessWidget 综合案例(商品列表)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("商品列表"),
      ),
      body: HomeContent(),
    );
  }
}

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Column 会越界relayoutBoundary,改成ListView就好了
    return ListView(
      children: [
        HomeProductItem("Apple1", "Macbook1",
            "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg"),
        SizedBox(height: 6),
        HomeProductItem("Apple2", "Macbook2",
            "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imm9u5zj30u00k0adf.jpg"),
        SizedBox(height: 6),
        HomeProductItem("Apple3", "Macbook3",
            "https://tva1.sinaimg.cn/large/006y8mN6gy1g72imqlouhj30u00k00v0.jpg"),
      ],
    );
  }
}

class HomeProductItem extends StatelessWidget {
  final String title;
  final String desc;
  final String imageURL;
  final style1 = TextStyle(fontSize: 25, color: Colors.orange);
  final style2 = TextStyle(fontSize: 20, color: Colors.green);

  HomeProductItem(this.title, this.desc, this.imageURL);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(8),
      decoration: BoxDecoration(
        border: Border.all(
          width: 5, // 设置边框的宽度
          color: Colors.pink, // 设置边框的颜色
        ),
      ),
      child: Column(
        // crossAxisAlignment: CrossAxisAlignment.start,
        // crossAxisAlignment: CrossAxisAlignment.center,
        // crossAxisAlignment: CrossAxisAlignment.end,
        // crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Text(title, style: style1, textAlign: TextAlign.center),
          Text(title, style: style1),
          SizedBox(height: 8),
          Text(desc, style: style2),
          SizedBox(height: 8),
          Image.network(imageURL),
        ],
      ),
    );
  }
}

2、StatefulWidget 综合案例(计数器)

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("title")),
      body: HomeContent("hello world"),
    );
  }
}

/// 为什么Flutter在设计的时候StatefulWidget的build方法放在State中
/// 1. build出来的Widget是需要依赖State中的变量(状态/数据)
/// 2. 在Flutter的运行过程中:
///   Widget是不断的销毁和创建的
///   当我们自己的状态发生改变时,并不希望重新创建一个新的State
class HomeContent extends StatefulWidget {
  final String message;

  HomeContent(this.message);

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

class _HomeContentState extends State<HomeContent> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          _getButtons(),
          Text("当前计数:$_counter", style: TextStyle(fontSize: 25)),
          Text("传递的信息:${widget.message}")
        ],
      ),
    );
  }

  Widget _getButtons() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
          child: Text("+", style: TextStyle(fontSize: 20)),
          style: TextButton.styleFrom(backgroundColor: Colors.pink),
          onPressed: () {
            setState(() {
              _counter++;
            });
          },
        ),
        ElevatedButton(
          child: Text("-", style: TextStyle(fontSize: 20)),
          style: TextButton.styleFrom(backgroundColor: Colors.purple),
          onPressed: () {
            setState(() {
              _counter--;
            });
          },
        ),
      ],
    );
  }
}

四、生命周期

1、StatelessWidget 的生命周期

因为 StatelessWidget 没有状态,所以它的生命周期很简单:先调用 构造函数,再调用 build(),没了:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("title")),
      body: HomeContent("hello world"),
    );
  }
}

// StatelessWidget 的生命周期
class HomeContent extends StatelessWidget {
  final String message;

  HomeContent(this.message) {
    print("构造函数被调用");
  }

  @override
  Widget build(BuildContext context) {
    print("调用build方法");
    return Text(message);
  }
}

日志输出:

I/flutter ( 3092): 构造函数被调用
I/flutter ( 3092): 调用build方法

2、StatefulWidget 的生命周期

方法说明
createStateFramework 会通过调用 StatefulWidget.createState 来创建一个 State。
initState新创建的 State 会和一个 BuildContext 产生关联,此时认为 State 已经被安装好了,initState 函数将会被调用。通常,我们可以重写这个函数,进行初始化操作。
didChangeDependencies在 initState 调用结束后,这个函数会被调用。事实上,当 State 对象的依赖关系发生变化时,这个函数总会被 Framework 调用。
build经过以上步骤,系统认为一个 State 已经准备好了,就会调用 build 来构建视图。我们需要在这个函数中返回一个 Widget。
deactivatedeactivate 当 State 被暂时从视图树中移除时,会调用这个函数。页面切换时,也会调用它,因为此时 State 在视图树中的位置发生了变化,需要先暂时移除后添加。
dispose当 State 被永久地从视图树中移除时,Framework 会调用该函数。在销毁前触发,我们可以在这里进行最终的资源释放。在调用这个函数之前,总会先调用 deactivate 函数。
didUpdateWidget当 Widget 的配置发生变化时,会调用这个函数。比如,热重载的时候就会调用这个函数。调用这个函数后,会调用 build 函数。
setState当需要更新 State 的视图时,需要手动调用这个函数,它会触发 build 函数。
// StatefulWidget 的生命周期
class HomeContent extends StatefulWidget {
  HomeContent(String message) {
    print("1. 调用 HomeContent 的 constructor 方法");
  }

  @override
  _HomeContentState createState() {
    print("2. 调用 HomeContent 的 createState 方法");
    return _HomeContentState();
  }
}

class _HomeContentState extends State<HomeContent> {
  _HomeContentState() {
    print("3. 调用 _HomeContentState 的 constructor 方法");
  }

  @override
  void initState() {
    // 注意:这里必须调用super(@mustCallSuper)
    super.initState();
    print("4. 调用 _HomeContentState 的 initState 方法");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("调用 _HomeContentState 的 didChangeDependencies 方法");
  }

  @override
  void didUpdateWidget(covariant HomeContent oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("调用 _HomeContentState 的 didUpdateWidget 方法");
  }

  @override
  Widget build(BuildContext context) {
    print("5. 调用 _HomeContentState 的 build 方法");
    return Container();
  }

  @override
  void dispose() {
    super.dispose();
    print("6. 调用 _HomeContentState 的 dispose 方法");
  }
}

日志结果:

// 界面初始化
I/flutter ( 3092): 1. 调用 HomeContent 的 constructor 方法
I/flutter ( 3092): 2. 调用 HomeContent 的 createState 方法
I/flutter ( 3092): 3. 调用 _HomeContentState 的 constructor 方法
I/flutter ( 3092): 4. 调用 _HomeContentState 的 initState 方法
I/flutter ( 3092): 调用 _HomeContentState 的 didChangeDependencies 方法
I/flutter ( 3092): 5. 调用 _HomeContentState 的 build 方法

// 点击计数按钮
I/flutter ( 3092): 5. 调用 _HomeContentState 的 build 方法

没有打印 dispose()中的日志,是因为这里 Demo 中没有将 Widget 移除

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值