flutter的基础特性

选择MacOs

选择MacOs作为虚拟机演示

布局约束规则

让子元素竟可能的大,撑满父元素

void main() {
  // 启动Flutter应用,运行build()方法构建UI
  runApp(build());
}

Widget build() {
  // Container : 在这里创建一个宽200、高200、颜色为琥珀色的容器
  // 在Dart语法上,这里是创建了一个匿名对象
  return Container(width: 200, height: 200, color: Colors.amber);
}

这个时候,设置的widthheight并没有起作用

确认位置后,按子元素大小显示

void main() {
  // 启动Flutter应用,运行build()方法构建UI
  runApp(build());
}

Widget build() {
  // Container : 在这里创建一个宽200、高200、颜色为琥珀色的容器
  // 在Dart语法上,这里是创建了一个匿名对象
  // child: Container : 这里是将Container作为Center的子组件
  //  Center({super.key, super.widthFactor, super.heightFactor, super.child});
  // Center的构造函数是命名参数
  return Center(child: Container(width: 200, height: 200, color: Colors.amber));
}

确认好Container的位置之后,设置的widthheight才开始起作用

核心规则

核心规则:Constraints go down. Sizes go up. Positions are set by parents.

  • 上层 widget 向下层 widget 传递约束条件
  • 下层 widget 向上层 widget 传递大小信息。
  • 上层 widget 决定下层 widget 的位置。
Widget build() {
  // MaterialApp: 是一个应用程序的顶层容器,提供了许多应用程序所需的功能
  // MaterialApp 也是一个Widget
  return MaterialApp(
    // home:Scaffold: 这里是将Scaffold作为应用的首页
    home: Scaffold(
      // Scaffold : 在这里创建一个Scaffold组件
      body: Column(
        // Column : 在这里创建一个垂直排列的文本列表
        // Text("aaaaaa") : 这里是第一个文本组件
        // const <Widget> : 这里是一个常量列表
        children: const <Widget>[Text("aaaaaa"), Text("bbbbb"), Text("cccc")],
      ),
    ),
  );
}

下面是组件的大小和位置信息:

宽度 0.0 <= w <= 280.0 , 高度 0.0 <= h <= 173.0 ,就是上层传下来的约束。Scaffold组件的大小就是height=173.0width=280.0

Text组件的宽度 w=47.8 , 高度 h=20.0 就是组件向上层传递的大小信息

元素左边 w=1.8,右边 w=1.8,就是上层决定下层的组件位置

Column 宽度等于子元素最大宽度

Widget build() {
  return MaterialApp(
    home: Scaffold(
      body: Column(
        children: const <Widget>[
          // 只修改了Text的宽度
          Text("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
          Text("bbbbb"),
          Text("cccc"),
        ],
      ),
    ),
  

Container 紧包裹子元素

Scaffold 填充了整个屏幕,Container 包裹了 Column

Widget build() {
  return MaterialApp(
    home: Scaffold(
      body: Container( // container 容器
        color: Colors.yellow, // 黄色
        child: Column(
          children: const <Widget>[
            Text("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
            Text("bbbbb"),
            Text("cccc"),
          ],
        ),
      ),
    ),
  );
}

这里container的width=446.6,等于column的width。而container的height=173.0,等于column的height

Scaffold的width=603.0,因此会有部分区域不是黄色的。在正常情况下,Flutter 里的 Scaffold 会自动撑满整个可用窗口区域。也就是Scaffold的大小等于窗口的大小。

松约束

当一个 widget 告诉其子级可以比自身更小的话, 我们通常称这个 widget 对其子级使用 宽松约束(loose)。在下面这个例子中,Text的width的取值范围是0.0 <= w <= 280.0 ,是一个区间而不是固定的值。这就是松约束。

紧约束

ConstrainedBox 约束组件

constraints 通过 maxWidth maxHeight 来设置子组件最大约束;通过 minWidth minHeight来设置子组件最小约束

Widget build() {
  return MaterialApp(
    home: Center(
      child: Scaffold(
        body: Center(
          // ConstrainedBox: 用于限制子组件的大小
          child: ConstrainedBox(
            // constraints: 用于设置子组件的大小限制
            constraints: const BoxConstraints(
              minWidth: 100, // 最小宽度
              minHeight: 100, // 最小高度
              maxWidth: 200, // 最大宽度
              maxHeight: 200, // 最大高度
            ),
            child: Container(color: Colors.amber, width: 10, height: 10),
          ),
        ),
      ),
    ),
  );
}

由于最小限制是100,即便我们设置了Container大小,但是我们设置的小于最小值,所以采取了最小值。

紧约束定义

它的最大/最小宽度是一致的,高度也一样。

通过 BoxConstraints.tight 可以设置紧约束

Widget build() {
  return MaterialApp(
    home: Scaffold(
      body: Center(
        child: ConstrainedBox(
          // constraints: 用于设置子组件的大小限制
          // BoxConstraints.tight: 创建一个紧约束,要求子组件必须占据指定的大小
          constraints: BoxConstraints.tight(const Size(200, 200)),
          child: Container(color: Colors.amber, width: 10, height: 10),
        ),
      ),
    ),
  );
}

最后,遵循了BoxConstraints.tight的设置:

不受约束

UnconstrainedBox 不受约束

Widget build() {
  return MaterialApp(
    home: Center(
      child: Scaffold(
        body: ConstrainedBox(
          constraints: const BoxConstraints(
            minWidth: 100,
            minHeight: 100,
            maxWidth: 300,
            maxHeight: 300,
          ),
          // UnconstrainedBox: 用于去除父组件的约束,包括宽度和高度的约束
          child: UnconstrainedBox(
            child: Container(width: 50, height: 50, color: Colors.blue),
          ),
        ),
      ),
    ),
    // 关闭调试模式下的右上角的DEBUG标识
    debugShowCheckedModeBanner: false,
  );
}

UnconstrainedBox 包裹内部的 Container 50*50 可以不受约束自己控制大小

unbounded 组件

bounded(有界约束) → 父组件明确告诉子组件最大/最小宽高,例如:maxHeight = 500

unbounded(无界约束) → 某个维度上没有最大限制(maxHeight = ∞maxWidth = ∞

Row Column ListView 这种组件 属于 unbounded

组件 主轴方向 主轴约束 副轴约束
Column 垂直方向 高度 unbounded(子组件想多高就多高) 宽度 bounded(由父约束决定)
Row 水平方向 宽度 unbounded(子组件想多宽就多宽) 高度 bounded(由父约束决定)
ListView 滚动方向 滚动方向 unbounded(无限延伸) 另一方向 bounded
Widget build() {
  return MaterialApp(
    home: Scaffold(
      // column: 用于创建一个垂直排列的组件,在垂直方向可以无限扩展
      body: Column(
        children: [
          // FlutterLogo : 用于显示Flutter的Logo
          const FlutterLogo(size: 50),
          const FlutterLogo(size: 20),
          Container(
            height: 2000, // 设置了 2000 高
            color: Colors.amber,
          ),
        ],
      ),
    ),
    debugShowCheckedModeBanner: false,
  );
}

底部有一行小字,提示溢出:

在inspector里面也清楚的显示了你溢出的情况。container最高是731.4,但是现在在2070。

初识生命周期

生命周期就是一个对象从出生到死亡所经历的完整过程,以及在这些过程中的关键阶段。

下面是一个简化版的通用生命周期过程(不特指 Flutter,任何有生命周期的对象都类似):

1. 创建阶段(Create)
   - 对象被创建
   - 构造函数 / 初始化逻辑执行一次

2. 初始化阶段(Initialize)
   - 设置初始数据
   - 建立需要的资源(比如网络连接、监听器等)

3. 活跃阶段(Active / Run)
   - 执行主要功能
   - 根据外部变化更新状态(刷新界面、数据变化等)

4. 更新阶段(Update)
   - 外部条件变化时,重新调整自己
   - 比如父对象传入的新数据

5. 销毁阶段(Destroy / Dispose)
   - 释放资源(关闭文件、断开连接、移除监听)
   - 对象彻底销毁,无法再使用

有状态无状态组件

概念理解

首先:Widget 不是“组件”,而是“配置”

  • 很多人刚开始会以为:“StatelessWidget 和 StatefulWidget 是组件的两种类型,一个能变,一个不能变。”
  • 其实更准确的理解是:
    • Widget 在 Flutter 中是UI的配置描述(immutable,不可变对象)
    • 真正渲染的是 ElementRenderObject
    • 所谓“有状态”和“无状态”,是说这个 UI 描述在生命周期内能否保存数据并触发重绘

所谓“有状态”和“无状态”,是说这个 UI 描述在生命周期内能否保存数据并触发重绘

  • 生命周期内能否保存数据
    • 生命周期内 = 从组件创建到销毁的这段时间。
    • 保存数据 = 组件里是否有变量能记住一些值,不会因为一次 UI 重建就丢失。
      • StatelessWidget → 没有自己的状态存储空间,它的所有数据都来自外部传入,不能自己记住变化
      • StatefulWidget → 有一个 State 对象,可以在里面存变量,这些变量在组件生命周期内是持久的(直到组件被销毁)。
  • 并触发重绘
    • 触发重绘 = 当保存的数据发生变化时,是否可以告诉 Flutter “我变了,请重新画我”。在 Flutter 里,这个动作是调用 setState() 完成的。
      • StatelessWidget → 没有 setState(),自己没法触发 UI 更新。要更新只能靠外部重新创建它。
      • StatefulWidget → 有 setState(),可以在内部修改数据并要求 Flutter 重建 UI。

所以这句话的意思就是:

  • 有状态组件(StatefulWidget):在它的存在期间,可以记住一些数据(状态),并在这些数据变化时主动让自己重绘。
  • 无状态组件(StatelessWidget):生命周期内不保存自己的状态,要变只能靠外部重新创建它。

无状态组件 StatelessWidget

特点:

  • 没有可变状态(没有 State 对象)
  • 数据一旦传进来,就不会自己变
  • 需要刷新时只能靠外部传入新的 Widget 实例
  • 每次数据变化时,你必须重新 new 一个新的 MyText,Flutter 会用新的 Widget 替换旧的
  • 没有 setState() 方法

适用场景

  • 静态内容(标题、标签、图标等)
  • 内容只会随外部数据变化而变化
void main(List<String> args) {
  // 1-runApp: Flutter 框架提供的全局函数。参数必须是一个 Widget。
  //它会把这个 Widget 挂载到 Flutter 的根部 UI 树,并启动整个应用的渲染流程。
  // 2-const MyApp(): 创建一个MyApp的实例
  runApp(const MyApp());
}

const img1 = "https://linresource.uk/img/13399635147767705.jpg";
const img2 = "https://linresource.uk/img/13399635169004637.jpg";

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        // AppBar: 用于显示应用程序的标题栏
        appBar: AppBar(title: Center(child: const Text('有无状态组件'))),
        // body: 用于显示应用程序的主要内容
        body: Center(child: const BannerWidget()),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}

class BannerWidget extends StatelessWidget {
  const BannerWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Image.network(img1);
  }
}

有状态 StatefulWidget

特点:

  • 拥有一个单独的 State 对象
  • State 对象可以持有可变数据(变量)
  • 当状态改变时可以调用 setState() 触发 UI 重建
  • State 生命周期内变量可以多次改变
  • 每次调用 setState(),Flutter 会:
    1. 标记当前 Element 需要重建
    2. 重新执行 build() 方法
    3. 刷新界面

适用场景

  • 需要动态变化的 UI(计数器、表单、动画等)
  • 需要在组件内部保存数据的情况
void main(List<String> args) {
  // 1-runApp: Flutter 框架提供的全局函数。参数必须是一个 Widget。
  //它会把这个 Widget 挂载到 Flutter 的根部 UI 树,并启动整个应用的渲染流程。
  // 2-const MyApp(): 创建一个MyApp的实例
  runApp(const MyApp());
}

const img1 = "https://linresource.uk/img/13399635147767705.jpg";
const img2 = "https://linresource.uk/img/13399635169004637.jpg";

// MyApp: 这是一个无状态组件,继承自 StatelessWidget
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        // AppBar: 用于显示应用程序的标题栏
        appBar: AppBar(title: Center(child: const Text('有无状态组件'))),
        // body: 用于显示应用程序的主要内容
        body: Center(child: const BannerWidget()),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}

// BannerWidget: 有状态组件,继承自StatefulWidget
class BannerWidget extends StatefulWidget {
  const BannerWidget({super.key});

  @override
  // createState: 创建状态对象
  State<BannerWidget> createState() => _BannerWidgetState();
}

// _BannerWidgetState: BannerWidget的状态类
class _BannerWidgetState extends State<BannerWidget> {
  // imgUrl: 用于存储当前显示的图片URL
  String? imgUrl;

  @override
  Widget build(BuildContext context) {
    // Column: 用于垂直排列子组件
    return Column(
      children: [
        // ElevatedButton: 用于创建一个可点击的按钮
        ElevatedButton(
          // onPressed: 当按钮被点击时触发的回调函数
          onPressed: () {
            // setState: 通知框架状态已改变,从而重新构建 Widget
            setState(() {
              // imgUrl == img1 ? img2 : img1 : 表示根据当前图片切换到另一张图片
              // 如果当前图片是 img1,则切换到 img2;否则切换到 img1
              // 如果 imgUrl 为 null,则默认显示 img1,因为null==img1为false
              imgUrl = imgUrl == img1 ? img2 : img1;
            });
          },
          // child: 用于定义按钮的子组件
          child: const Text("切换图片"),
        ),
        Image.network(imgUrl ?? img1, height: 600),
      ],
    );
  }
}
  • BannerWidget extends StatefulWidget:继承StatefulWidget必须实现createState()方法,返回一个 State 类型对象,这个 State 对象就是实际维护可变数据的地方。

    State<BannerWidget> createState()
    
  • class _BannerWidgetState extends State<BannerWidget>

    • 这个 State<T> 里面的泛型 T,其实是非常关键的——它必须对应你这个 State 类所绑定的 StatefulWidget 类型。这里的 BannerWidget 是一个 StatefulWidget,而 _MyAppState 是它对应的状态类。

    • 为什么要指定这个泛型?

      • State 知道自己属于哪个 Widget。Flutter 会把 Widget 实例传给 State,以便在 State 中通过 widget 属性访问它。比如

        @override
        Widget build(BuildContext context) {
          return Text(widget.title); // widget 就是 BannerWidget 的实例
        }
        

        如果你把泛型错写成别的类型,比如 State<OtherWidget>widget 的类型就会变成 OtherWidget,而不是 BannerWidget,导致属性和方法对不上。

      • 类型安全

        • 泛型确保了 State 与它的宿主 StatefulWidget 类型一致。
        • 如果泛型和实际 Widget 类型不一致,编译器会直接报错,避免运行时才发现问题。
      • 框架自动绑定

        • Flutter 在创建 State 时会调用 createState(),并自动把对应的 Widget 传给它,这个过程依赖泛型来做类型匹配。
  • _BannerWidgetState 这个类前面有一个下划线 _,在 Dart 里表示库级私有(library private)

    • State 对象是 Flutter 框架内部用的,外部其实不需要直接操作它。
    • 你只希望外界通过 BannerWidget 这个 Widget 来使用,而不是直接去 new 一个 State。
    • State 类私有化,可以防止外部直接访问或依赖它的实现细节,方便以后修改内部逻辑而不影响外部 API。
  • build() 方法:每次 setState() 调用后,Flutter 都会重新调用这个 build() 方法来重建 UI

  • ElevatedButton → 点击时执行 onPressed

  • setState() 的作用是:

    • 更新状态变量(这里是 imgUrl
    • 通知 Flutter:这个 State 对应的 Widget 需要重建
  • Image.network(imgUrl ?? img1)

    • ?? 运算符:如果 imgUrlnull,就用 img1 作为默认值。

生命周期

StatefulWidget 生命周期

概览

创建阶段
┌──────────────────────────────┐
│ StatefulWidget 被创建        │
└──────────────┬───────────────┘
               │
               ▼
      createState()  ← 框架调用,生成 State 实例
               │
               ▼
         initState() ← 只执行一次,初始化数据
               │
               ▼
 didChangeDependencies() ← 首次依赖注入或依赖变更
               │
               ▼
            build() ← 构建 UI(可能多次调用)
               │
               ├──────────► addPostFrameCallback()
               │              (当前帧构建完成后执行)
               ▼
────────── 以下是组件更新流程 ──────────
父 Widget 更新(配置变化):
               │
               ▼
     didUpdateWidget(oldWidget)
               │
               ▼
            build() ← 重建 UI
               │
               ├──────────► addPostFrameCallback()
               ▼
────────── 以下是移除/销毁流程 ──────────
Widget 从树中临时移除:
               │
               ▼
         deactivate()
               │
Widget 永久移除:
               │
               ▼
         dispose() ← 释放资源(如动画控制器、监听器等)
                 
方法 触发时机 特点 / 用途
createState() Widget 创建时 返回一个 State 实例,Flutter 框架调用。
initState() State 创建后、build 只调用一次,适合初始化数据、启动动画、监听事件等。
didChangeDependencies() initState 后、依赖变化时 会在依赖的 InheritedWidget 更新时再次调用。
build() 首次渲染 & setState 构建 Widget 树。可能会频繁调用,要高效。
didUpdateWidget() 父组件重建并传入新配置时 适合对比 oldWidgetwidget 的差异来更新状态。
addPostFrameCallback() 当前帧 build 完成后 一般用来在布局完成后获取 context 尺寸、滚动到特定位置等。
setState() 手动调用 通知 Flutter 数据变化,触发 build
deactivate() 从 widget 树移除时 可能会重新插入(比如导航切换 tab 页)。
dispose() 永久移除时 释放资源,比如关闭 Stream、动画控制器等。

代码

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void main() {
  runApp(const MyApp());
}

const img1 = "https://linresource.uk/img/13399635147767705.jpg";
const img2 = "https://linresource.uk/img/13399635169004637.jpg";

// 在这里定义一个图片组件
Widget imageWidget({required String imgUrl}) {
  return Container(
    // 设置内边距和背景色
    // padding: 设置内边距, 内边距为 10
    padding: const EdgeInsets.all(10),
    color: Colors.amber,
    child: Center(child: Image.network(imgUrl, height: 600)),
  );
}

// BannerWidget: 有状态组件,继承自StatefulWidget
class BannerWidget extends StatefulWidget {
  const BannerWidget({super.key});

  // 创建 State 只执行1次
  @override
  State<BannerWidget> createState() {
    print("createState:创建状态对象");
    return _BannerWidgetState();
  }
}

// _BannerWidgetState: BannerWidget的状态类
class _BannerWidgetState extends State<BannerWidget> {
  String? imgUrl;

  // 初始 State, mounted 等于 true, 只执行1次
  @override
  void initState() {
    super.initState();
    print("initState:初始化状态");

    // 渲染结束调用,只执行1次
    // SchedulerBinding是Flutter框架中的一个类,用于调度帧的绘制和事件的处理
    // .instance: 获取SchedulerBinding的实例
    // .addPostFrameCallback: 在下一帧绘制结束后执行的回调
    //
    SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
      print("addPostFrameCallback:在build完成后");
      print(timeStamp);
    });
  }

  // 父或祖先widget中的InheritedWidget改变时会被调用
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies:依赖发生变化");
  }

  // 父类 setState 后,子类就会触发
  @override
  void didUpdateWidget(oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget:更新组件");
  }

  // 从组件树中移除 State 时调用
  @override
  void deactivate() {
    super.deactivate();
    print("deactivate:从组件树中移除状态");
  }

  // 组件被释放时调用
  @override
  void dispose() {
    print("dispose:释放资源");
    // 调用父类的dispose方法,目的是释放资源
    super.dispose();
  }

  // UI 被重新渲染的时候多次执行
  @override
  Widget build(BuildContext context) {
    print("build:构建BannerWidget");
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            print("onPressed:按钮被点击");
            // mounted 这个值是用来判断当前状态对象是否仍然在树中
            // flutter 分配完你的组件树位置,会设置 mounted 为 true。
            // 如果 mounted 为 false,说明组件已经被从树中移除
            // mounted 是在什么时候被赋值为true的? 是在initState中
            if (mounted == true) {
              setState(() {
                print("setState:更新状态");
                imgUrl = imgUrl == img1 ? img2 : img1;
              });
            }
          },
          child: const Text("切换图片"),
        ),
        imageWidget(imgUrl: imgUrl ?? img1),
      ],
    );
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    print("build:构建MyApp");
    return MaterialApp(
      home: Scaffold(
        body: Column(children: const [Text('有无状态组件'), BannerWidget()]),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}

日志如下:

Restarted application in 1,560ms.
I/flutter (11459): build:构建MyApp
I/flutter (11459): createState:创建状态对象
I/flutter (11459): initState:初始化状态
I/flutter (11459): didChangeDependencies:依赖发生变化
I/flutter (11459): build:构建BannerWidget
I/flutter (11459): addPostFrameCallback:在build完成后
I/flutter (11459): 0:00:00.000000
I/flutter (11459): build:构建MyApp
I/flutter (11459): build:构建BannerWidget
I/flutter (11459): onPressed:按钮被点击
I/flutter (11459): setState:更新状态
I/flutter (11459): build:构建BannerWidget
I/flutter (11459): onPressed:按钮被点击
I/flutter (11459): setState:更新状态
I/flutter (11459): build:构建BannerWidget

StatelessWidget 生命周期

无状态组件,不需要处理生命周期,直接显示即可

abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key? key }) : super(key: key);

  /// Creates a [StatelessElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatelessElement createElement() => StatelessElement(this);

在源码中可见 createElement() 创建组件到组件树,不需要重写去维护

App生命周期

生命周期

名称 说明
resumed 应用程序可见且响应用户输入。
inactive 应用程序处于非激活状态,无法响应用户输入。
pause 应用程序不可见且无法响应用户输入,运行在后台。
detached 应用程序仍寄存在Flutter引擎上,但与平台 View 分离。
suspending 应用被挂起,此状态IOS永远不会回调

假设你正在使用一个时间管理APP

状态 场景例子 发生时机
resumed 你正盯着屏幕看着计时器数字往下跳,能点按钮、能滑动页面,App 正常响应你。 App 在前台、可见、可交互
inactive 你突然从屏幕顶部往下拉通知栏,计时器停留在当前界面,但你没法点击按钮。 系统临时打断,比如电话、通知、界面切换过渡
pause 你按 Home 键切回桌面,App 的计时器逻辑暂停(如果开发者没特殊处理),屏幕不显示 App。 App 进入后台,不可见,不响应输入
detached 你在后台清理了 App(但是引擎资源还没完全释放,比如热重启调试时),UI 和平台视图分离。 Flutter 引擎还在,但 View 被移除
suspending Android 上,系统为了省电/释放内存,把后台的 App 挂起;iOS 永远不会收到这个状态。 App 被系统挂起

如何监听APP的生命周期

第一步: 创建 StatefulWidget 组件,混入 WidgetsBindingObserver

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  ...
  • WidgetsBindingObserver 是一个 观察者接口,可以用来监听:

    • App 生命周期(前台/后台切换)

    • 系统设置变化(语言、主题、屏幕旋转等)

    • 内存压力

  • 混入(with)之后,就可以实现它的一系列回调方法。

第二步:添加观察者 addObserver

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance?.addObserver(this); //添加观察者
  }
  • initState() 是组件初始化时执行的。
  • addObserver(this) 表示当前类要接收生命周期和系统事件的通知。

生命周期变化时回调 didChangeAppLifecycleState(App 前后台切换)

//  生命周期变化时回调
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  super.didChangeAppLifecycleState(state);
  print("didChangeAppLifecycleState: $state");
}

state 可能的值:

  • resumed:应用可见,可交互(进入前台)。
  • inactive:可见但不可交互(如来电弹窗)。
  • paused:不可见(进入后台)。
  • detached:应用与宿主视图分离(极少用)。
  • suspending:挂起(iOS 永远不会回调)

具体代码

import "package:flutter/material.dart";

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

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

//实现WidgetsBindingObserver观察者
// abstract mixin class WidgetsBindingObserver {
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this); //添加观察者
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("App生命周期")),
        body: Column(children: <Widget>[const Text("首页")]),
      ),
    );
  }

  //  生命周期变化时回调
  //  resumed:应用可见并可响应用户操作,app进入前台
  //  inactive:用户可见,但不可响应用户操作,比如来了个电话,前后台切换的过渡状态
  //  paused:已经暂停了,用户不可见、不可操作,app进入后台
  //  suspending:应用被挂起,此状态IOS永远不会回调
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    print("didChangeAppLifecycleState: $state");
  }

  //当前系统改变了一些访问性活动的回调: 比如系统开启了“放大文字”“高对比度”等辅助功能时触发。
  @override
  void didChangeAccessibilityFeatures() {
    super.didChangeAccessibilityFeatures();
    print("didChangeAccessibilityFeatures");
  }

  //低内存回调: 当系统内存不足时触发(可以用来释放缓存资源)。
  @override
  void didHaveMemoryPressure() {
    super.didHaveMemoryPressure();
    print("didHaveMemoryPressure");
  }

  //用户本地设置变化时调用,如系统语言改变,比如用户在系统设置里把语言改成英文,会触发。
  @override
  void didChangeLocales(List<Locale>? locale) {
    super.didChangeLocales(locale);
    print("didChangeLocales");
  }

  //应用尺寸改变时回调,比如旋转屏幕(横竖屏切换)时触发。
  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    Size? size = WidgetsBinding.instance.window.physicalSize;
    print("didChangeMetrics  :宽:${size.width} 高:${size.height}");
  }

  //系统切换主题时回调:例如用户在系统里切换浅色模式 / 深色模式时触发
  @override
  void didChangePlatformBrightness() {
    super.didChangePlatformBrightness();
    print("didChangePlatformBrightness");
  }

  ///文字系数变化: 当用户调整系统字体大小时触发。
  @override
  void didChangeTextScaleFactor() {
    super.didChangeTextScaleFactor();
    print(
      "didChangeTextScaleFactor  :${WidgetsBinding.instance.window.textScaleFactor}",
    );
  }

  //当应用被销毁时调用:比如用户关闭了应用
  @override
  void dispose() {
    print("dispose:应用被销毁");
    super.dispose();
    // 组件销毁时必须移除观察者,否则可能造成内存泄漏。
    WidgetsBinding.instance.removeObserver(this); //销毁观察者
  }
}

输出:

Restarted application in 1,945ms.
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.inactive
D/VRI[MainActivity](12299): visibilityChanged oldVisibility=true newVisibility=false
I/flutter (12299): didHaveMemoryPressure
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.hidden
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.paused
D/ViewRootImpl(12299): Skipping stats log for color mode
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.hidden
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.inactive
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.resumed
D/InsetsController(12299): hide(ime(), fromIme=false)
I/ImeTracker(12299): com.example.flutter_quickstart_learn:6f9d52ef: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.inactive
D/VRI[MainActivity](12299): visibilityChanged oldVisibility=true newVisibility=false
I/flutter (12299): didHaveMemoryPressure
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.hidden
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.paused
D/ViewRootImpl(12299): Skipping stats log for color mode
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.hidden
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.inactive
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.resumed
D/InsetsController(12299): hide(ime(), fromIme=false)
I/ImeTracker(12299): com.example.flutter_quickstart_learn:58562098: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
I/uickstart_learn(12299): AssetManager2(0x71d909eda778) locale list changing from [] to [en-US]
3
I/flutter (12299): didChangeMetrics  :宽:1440.0 高:2560.0
I/flutter (12299): didChangeMetrics  :宽:2560.0 高:1440.0
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.inactive
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.resumed
D/InsetsController(12299): hide(ime(), fromIme=false)
I/ImeTracker(12299): com.example.flutter_quickstart_learn:96ce317d: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
2
I/flutter (12299): didChangeMetrics  :宽:2560.0 高:1440.0
I/flutter (12299): didChangeMetrics  :宽:1440.0 高:2560.0
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.inactive
I/flutter (12299): didChangeAppLifecycleState: AppLifecycleState.resumed
D/InsetsController(12299): hide(ime(), fromIme=false)
I/ImeTracker(12299): com.example.flutter_quickstart_learn:4398f924: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN

AppLifecycleState.hidden

是什么

官方定义Flutter 3.22 及以上版本):hidden 状态是 Flutter 在较新版本中引入的,表示应用程序当前对用户不可见,并且不会接收用户输入。这个状态通常在应用被完全隐藏时触发。它与 AppLifecycleState.inactive AppLifecycleState.paused有所不同:

  • inactive:应用处于非活动状态,但可能仍部分可见(例如在多窗口模式下或部分被覆盖)。
  • paused:应用完全在后台运行,但仍可能保持一些后台任务。
  • hidden:应用完全不可见,用户无法与之交互。

触发场景

  • 应用被切换到后台(例如用户按下 Home 键)。
  • 设备屏幕被锁定。
  • 应用被其他全屏应用覆盖(例如来电界面)。

用途hidden 状态可以用来暂停某些资源密集型任务(例如动画、视频播放)或保存应用状态,以优化性能和电量消耗。

三者的主要区别总结

状态 可见性 用户交互 触发场景示例 典型处理
inactive 部分可见或不可见 不可交互 分屏模式、系统对话框、切换到后台过渡 暂停用户输入处理,保持部分渲染
paused 完全不可见 不可交互 应用切换到后台(如按 Home 键) 保存状态,暂停大部分任务
hidden 完全不可见 不可交互 屏幕锁定、被全屏界面覆盖 暂停几乎所有活动,节省资源

×

喜欢就点赞,疼爱就打赏