Flutter---通过案例来详细了解状态管理

状态管理的方法的使用逻辑
Dart 复制代码
1.构造函数

构造函数 只适合初始化不依赖 widget 的成员变量

不要在构造函数中访问widget或context!

2.initState()

必须在这里:初始化依赖widget属性的状态、开启监听、初始化控制器

3.didChangeDependencies()

必须在这里处理InheritedWidget变化、初始化依赖BuildContext的逻辑

4.build()

必须在这里返回Widget树

5.didUpdateWidget()

必须在这里响应widget属性变化

6.deactivate()

必须在这里:准备组件从树中移除

7.dispose()

必须在这里:释放所有资源
效果图
Flutter组件建立的事件线
Dart 复制代码
1. 构造函数:创建 State 对象
2. initState:State 初始化,但 Widget 树还没完全建立
3. 组件被插入 Widget 树,获得真正的 BuildContext
4. didChangeDependencies:通知组件"你现在有完整的 context 了"
5. build:第一次构建 UI
关键点

1.监听应用的生命周期

Dart 复制代码
//监听应用的生命周期
//这是向 Flutter 引擎注册一个观察者,用来监听整个应用的生命周期事件。
WidgetsBinding.instance.addObserver(this);
//现在这个 State 对象可以接收生命周期事件了

为什么需要它?

Dart 复制代码
//监听应用状态变化
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  print('应用状态改变: $state');
  
  switch (state) {
    case AppLifecycleState.resumed:
      // 应用回到前台
      _resumeTimer();
      break;
    case AppLifecycleState.inactive:
      // 应用不活跃(如来电、分屏)
      break;
    case AppLifecycleState.paused:
      // 应用进入后台
      _pauseTimer();
      break;
    case AppLifecycleState.detached:
      // 应用被销毁
      break;
    case AppLifecycleState.hidden:
      // 应用被隐藏(某些平台)
      break;
  }
}


//监听系统变化
@override
void didChangeMetrics() {
  // 屏幕尺寸改变(旋转、分屏、键盘弹出)
  print('屏幕尺寸改变: ${WidgetsBinding.instance.window.physicalSize}');
}

@override
void didChangeTextScaleFactor() {
  // 系统字体大小改变
  print('文字缩放比例改变');
}

@override
void didChangePlatformBrightness() {
  // 系统主题模式改变(亮色/暗色)
  setState(() {
    _isDarkMode = WidgetsBinding.instance.window.platformBrightness == Brightness.dark;
  });
}

2.InheritedWidget

InheritedWidget 就像一个"共享公告板",所有子组件都能从它那里读取数据。

例子

Dart 复制代码
// 这些都是 InheritedWidget:
Theme.of(context)      // 获取主题
MediaQuery.of(context) // 获取屏幕信息
Localizations.of(context) // 获取本地化文本
Navigator.of(context)  // 导航器

3.BuildContext()

Dart 复制代码
// 每个 Widget 都有自己的 BuildContext
// 就像每个人都有自己的家庭地址

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {  // <- 这个 context 就是地址
    return Container();
  }
}

4.什么情况下didChangeDependencies()会调用

公告板内容变化时,Flutter 会通知所有"订阅了公告板"的组件:

Dart 复制代码
情况1:主题变化

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  
  // 当用户切换亮色/暗色主题时,这里会被调用
  final brightness = MediaQuery.of(context).platformBrightness;
  print('主题变了:$brightness');
}

情况2:屏幕旋转

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  
  // 屏幕旋转时,MediaQuery 数据变化
  final size = MediaQuery.of(context).size;
  print('屏幕尺寸变了:${size.width}x${size.height}');
}

情况3:语言切换

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  
  // 当用户切换App语言时
  final locale = Localizations.localeOf(context);
  print('语言变了:$locale');
}

5.什么情况下didUpdateWidget()会调用

Dart 复制代码
父组件重新构建(比如父组件的 setState())

给子组件传递了新的属性值

widget 的 type 和 key 都没变(还是同一个组件实例)

组件没有被销毁(还在 widget 树中)


比喻:
老板(父组件)说:"小张(子组件),你还继续做这个工作"
"但工作内容更新了,这是新要求(新属性)"
你就需要响应这个更新

举例:父组件重新构建并传入新属性

Dart 复制代码
// 父组件
class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  String _title = "旧标题";
  Color _color = Colors.blue;
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 子组件
        ChildWidget(
          title: _title,    // 属性1
          color: _color,    // 属性2
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _title = "新标题";  // ← 改变属性!
            });
          },
          child: Text('改标题'),
        ),
      ],
    );
  }
}

这个例子的触发过程

Dart 复制代码
1. 点击按钮 → setState()
2. 父组件重新构建
3. 给 ChildWidget 传入新的 title
4. Flutter 发现:还是同一个 ChildWidget
5. 但属性变了 → 调用 didUpdateWidget()

6.什么情况下deactivate()会调用

Dart 复制代码
比喻:deactivate() 就是组件说:"我要暂时离开舞台了,但可能还会回来!"

情况1:页面跳转

// 当前页面 A,跳转到页面 B
Navigator.push(context, MaterialPageRoute(builder: (context) => PageB()));

// 页面 A 会调用 deactivate()
// 但页面 A 还在导航堆栈中,可能还会回来

情况2:组件被暂时移除

// 条件渲染
if (showWidget) {
  MyWidget()  // 当 showWidget 从 true 变 false 时
}

// MyWidget 会调用 deactivate()
// 但组件对象还在内存中

情况3:在列表中移动位置

// AnimatedList、ReorderableListView 中移动项目
// 项目暂时从树中移除,然后插入新位置

代码实例

Dart 复制代码
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

  @override
  State<StatefulWidget> createState() => _HomePageState();
}


// ====================演示用的可复用组件---显示计数器的矩形=============================
class CounterDisplay extends StatelessWidget {
  final int count;
  final Color color;

  const CounterDisplay({super.key, required this.count, required this.color});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: color.withOpacity(0.1),
        border: Border.all(color: color, width: 2),
        borderRadius: BorderRadius.circular(10),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text('计数器显示:'),
          const SizedBox(height: 10),
          Text(
            '$count',
            style: TextStyle(
              fontSize: 48,
              fontWeight: FontWeight.bold,
              color: color,
            ),
          ),
        ],
      ),
    );
  }
}

class _HomePageState extends State<HomePage>
    with WidgetsBindingObserver { //用于监听应用生命周期

  //创建阶段:构造函数 只适合初始化不依赖 widget 的成员变量
  //不要在构造函数中访问widget或context!
  _HomePageState(){
    print("构造函数_HomePageState()被调用");
    print("此时widget属性还未关联,不能访问widget属性");
  }

  //内部状态
  int _counter = 0;  //计数器
  Timer? _timer; //定时器
  bool _isDarkMode = false; //主题模式的标志
  DateTime? _lastUpdateTime; //最后更新时间
  StreamSubscription<int>? _streamSubscription; //订阅
  Color _counterColor = Colors.blue;

  //创建阶段:initState()
  //必须在这里:初始化依赖widget属性的状态、开启监听、初始化控制器
  @override
  void initState() {
    super.initState();
    print("2. initState()被调用");
    print("widget属性已关联,可以访问:${widget.runtimeType}"); //runtimeType 是 Dart 中所有对象都有的一个属性,它返回对象的实际运行时类型。 这里显示的是HomePage

    //1.初始化需要widget属性的状态
    _counterColor = _isDarkMode ? Colors.amber : Colors.blue;

    //2.添加WidgetBinding观察者(监听应用生命周期)
    WidgetsBinding.instance.addObserver(this);
    print("添加应用生命周期监听");

    //3.启动一个计时器
    _startTimer();

    //4.监听一个stream
    _setupStreamListener();

    //5.模拟从共享首选项加载数据
    _loadInitialData();
  }

  //创建阶段:didChangeDependencies()
  //必须在这里处理InheritedWidget变化、初始化依赖BuildContext的逻辑
  @override
  void didChangeDependencies(){
    super.didChangeDependencies();

    print("3. didChangeDependencies()被调用");

    //1.处理InheritedWidget的变化
    //例如:MediaQuery、Theme、Localizations等
    final currentBrightness = MediaQuery.of(context).platformBrightness;
    print("检测到平台亮度:$currentBrightness");

    //2.初始化依赖context的逻辑
    //例如:检查路由参数
    final routeArgs = ModalRoute.of(context)?.settings.arguments;
    if (routeArgs != null) {
      print('获取到路由参数: $routeArgs');
    }

  }

  //创建阶段:build()
  //必须在这里返回Widget树
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
          title: Text('生命周期完整演示'),
        actions: [
          Switch(value: _isDarkMode, onChanged: _toggleTheme),
        ],
      ),

      body:Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 当前状态显示
            _buildStatusDisplay(),

            const SizedBox(height: 20),

            // 控制按钮
            _buildControlButtons(),

            const SizedBox(height: 20),

          ],
        ),
      ),

      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: '增加计数器',
        child: const Icon(Icons.add),
      ),

    );
  }


  //更新阶段:didUpdateWidget()
  //必须在这里响应widget属性变化
  @override
  void didUpdateWidget(covariant HomePage oldWidget) {
    super.didUpdateWidget(oldWidget);

    print('5. didUpdateWidget() 被调用');
    print('旧的widget: ${oldWidget.runtimeType}, 新的widget: ${widget.runtimeType}');

    // 1. 当父组件重建此widget并传入新属性时调用
    // 2. 比较新旧widget的属性,决定是否需要更新状态
    // 3. 这里可以添加逻辑来处理widget属性的变化

    _lastUpdateTime = DateTime.now();
  }

  //销毁阶段:deactivate()
  //必须在这里:准备组件从树中移除
  @override
  void deactivate() {
    print('6. deactivate() 被调用');
    print('组件即将从Widget树中移除,但可能被重新插入');

    // 1. 当组件从树中移除时调用
    // 2. 组件可能被暂时移除然后重新插入
    // 3. 这里应该清理临时资源,但保留可以恢复的状态

    super.deactivate();
  }

  //销毁阶段:dispose()
  // 必须在这里:释放所有资源
  @override
  void dispose() {
    print('dispose() 被调用');
    print('组件永久销毁,必须清理所有资源');

    // 1. 取消计时器
    _timer?.cancel();
    print('计时器已取消');

    // 2. 取消Stream订阅
    _streamSubscription?.cancel();
    print('Stream订阅已取消');

    // 3. 移除WidgetsBinding观察者
    WidgetsBinding.instance.removeObserver(this);
    print('应用生命周期监听已移除');

    // 4. 清理控制器、动画等
    print('所有资源已清理');

    super.dispose();
  }

  // 应用生命周期回调
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print('应用状态变化: $state');

    switch (state) {
      case AppLifecycleState.resumed:
        print('应用回到前台');
        // 恢复定时器、重新连接网络等
        _timer?.isActive ?? _startTimer(); // 如果定时器停了就重启
        break;

      case AppLifecycleState.paused:
        print('应用进入后台');
        break;

      case AppLifecycleState.inactive:
        print('应用不活跃(如来电、分屏)');
        break;

      case AppLifecycleState.detached:
        print('应用即将被销毁');
        break;

      case AppLifecycleState.hidden:
        print('应用被隐藏');
        break;
    }
  }


  //=================================计时器===================================
  void _startTimer(){
    int seconds = 0;

    //Timer.periodic - 创建周期性计时器,一秒执行一次
    _timer = Timer.periodic(const Duration(seconds: 1), (timer){

      if(!mounted){ //如果组件已卸载,停止计时器
        timer.cancel();
        return;
      }

      //更新UI,
      setState(() {
        seconds = timer.tick;// timer.tick 是计时器已经触发的次数(从1开始)
      });

      print("计时器运行:$seconds 秒");

    });
  }

  //===================================监听======================================
  void _setupStreamListener(){

    // 如果已有订阅,先取消
    _streamSubscription?.cancel();

    //模拟一个每秒发射数据的stream
    //Stream.periodic - 创建一个周期性产生数据的Stream
    final stream = Stream.periodic(
      const Duration(seconds: 2), //每两秒产生一次数据
        (count) => count * 10, //生成每次返回的数据
    ).take(20); //只取前20个数据


    //订阅这个Stream,开始监听数据
    _streamSubscription = stream.listen((data){

      print("Stream数据接收:$data");
    },onDone: (){ //当20次完成了之后,就执行以下代码
      //Stream完成时的回调函数
      print("Stream已完成");
    });

  }

  //=============================异步数据加载过程==============================
  Future<void> _loadInitialData() async{
    print("模拟加载初始数据...");
    await Future.delayed(const Duration(milliseconds: 500));
    print("初始数据加载完成");
  }

  //================================增加计数器=================================
  void _incrementCounter(){
    setState(() {
      _counter++;

      _counterColor = _counter % 3 == 0
        ? Colors.red
          : _counter % 2 == 0
          ? Colors.green : Colors.blue;
    });

    print("计数器增加到:$_counter");
  }

  //================================重置计数器=================================
  void _resetCounter(){
    setState(() {
      _counter = 0;
      _counterColor = Colors.blue;
    });
    print("计数器已重置");
  }


  //====================================切换主题按钮=============================
  void _toggleTheme(bool value){
    setState(() {
      _isDarkMode = value;
    });

    print("主题切换为:${_isDarkMode ? "深色" : "浅色"}");
  }

  void _simulateWidgetUpdate() {
    print('模拟父组件更新widget属性...');
    // 在实际应用中,父组件会重建此widget
  }

  //=================================跳转页面==============================
  void _navigateAndReturn() async {
    print('跳转到新页面...');

    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const SimplePage(),
      ),
    );

    print('从新页面返回: $result');
  }

  //==================================当前状态UI =================================
  Widget _buildStatusDisplay() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '当前状态:',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Row(
              children: [
                //显示的蓝色圆角矩形
                CounterDisplay(count: _counter, color: _counterColor),
                const SizedBox(width: 20),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('计数器: $_counter'),
                      Text('主题: ${_isDarkMode ? '深色' : '浅色'}'),
                      if (_lastUpdateTime != null)
                        Text('最后更新: ${_lastUpdateTime!.toString().substring(11, 19)}'),
                      Text('组件状态: ${mounted ? '已挂载' : '未挂载'}'),
                    ],
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  //=================================控制按钮==============================
  Widget _buildControlButtons() {
    return Wrap(
      spacing: 10,
      runSpacing: 10,
      children: [
        ElevatedButton(
          onPressed: _incrementCounter,
          child: const Text('增加计数器'),
        ),
        ElevatedButton(
          onPressed: _resetCounter,
          child: const Text('重置计数器'),
        ),
        ElevatedButton(
          onPressed: _simulateWidgetUpdate,
          child: const Text('模拟Widget更新'),
        ),
        ElevatedButton(
          onPressed: _navigateAndReturn,
          child: const Text('跳转页面'),
        ),
        ElevatedButton(
          onPressed: () {
            print('手动添加日志');
          },
          child: const Text('添加日志'),
        ),
      ],
    );
  }


}

//=====================================用于跳转的简单页面==========================
class SimplePage extends StatelessWidget {
  const SimplePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('新页面'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('点击返回查看deactivate效果'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context, '返回数据');
              },
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}
相关推荐
西西学代码5 小时前
Flutter---头像管理
flutter
2601_949833395 小时前
flutter_for_openharmony口腔护理app实战+意见反馈实现
android·javascript·flutter
向哆哆6 小时前
用 Flutter × OpenHarmony 构建智能健康提醒应用:健康档案管理实战
flutter·开源·鸿蒙·openharmony·开源鸿蒙
菜鸟小芯7 小时前
【开源鸿蒙跨平台开发先锋训练营】DAY8~DAY13 底部选项卡&动态功能实现
flutter·harmonyos
向哆哆7 小时前
Flutter × OpenHarmony 实战 | 打造画栈平台的顶部横幅组件
flutter·开源·鸿蒙·openharmony·开源鸿蒙
2501_944525547 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 预算详情页面
android·开发语言·前端·javascript·flutter·ecmascript
雨季6667 小时前
Flutter 三端应用实战:OpenHarmony 简易“动态主题切换卡片”交互模式
flutter·ui·交互·dart
向哆哆8 小时前
构建健康档案管理快速入口:Flutter × OpenHarmony 跨端开发实战
flutter·开源·鸿蒙·openharmony·开源鸿蒙
mocoding8 小时前
使用Flutter强大的图标库fl_chart优化鸿蒙版天气预报温度、降水量、湿度展示
flutter·华为·harmonyos
向哆哆8 小时前
构建智能健康档案管理与预约挂号系统:Flutter × OpenHarmony 跨端开发实践
flutter·开源·鸿蒙·openharmony·开源鸿蒙