Flutter中各类Controller的本质

Flutter中提供了最基本的状态管理的方式------ setState ,当"状态"改变时,只需要调用一下该方法,界面即可刷新,展示最新的状态。

当"最小化"的组件自己管理自己的状态时,这是最正常不过的方式了。

但当有父组件来管理子组件的状态时,父组件直接调用setState,会产生刷新范围过大等问题,造成资源浪费,所以需要一些"迂回"的手段。

如下举例ChildA和ChildB,自行管理自己的状态时,相互不受影响。

scala 复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(child: ParentWidget()),
      ),
    );
  }
}

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

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  @override
  Widget build(BuildContext context) {
    return const Column(
      children: [
        ChildA(),
        ChildB(),
      ],
    );
  }
}

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

  @override
  State<ChildA> createState() => _ChildAState();
}

class _ChildAState extends State<ChildA> {
  int countA = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              countA += 1;
            });
          },
          child: const Text('change countA'),
        ),
        Text('countA $countA'),
      ],
    );
  }
}

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

  @override
  State<ChildB> createState() => _ChildBState();
}

class _ChildBState extends State<ChildB> {
  int countB = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              countB += 1;
            });
          },
          child: const Text('change countB'),
        ),
        Text('countB $countB'),
      ],
    );
  }
}

当父组件来管理 ChildB 的状态时,父组件直接调用 setState,导致自身重新执行build、所有子组件重新执行build。这里 ChildA 省略const优化,可以看到它重新执行build。

scala 复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(child: ParentWidget()),
      ),
    );
  }
}

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

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int countB = 0;

  void changeB() {
    setState(() {
      countB += 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: changeB,
          child: const Text('change countB'),
        ),
        ChildA(),
        ChildB(
          countB: countB,
        ),
      ],
    );
  }
}

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

  @override
  State<ChildA> createState() => _ChildAState();
}

class _ChildAState extends State<ChildA> {
  int countA = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              countA += 1;
            });
          },
          child: const Text('change countA'),
        ),
        Text('countA $countA'),
      ],
    );
  }
}

class ChildB extends StatefulWidget {
  const ChildB({
    required this.countB,
    super.key,
  });

  final int countB;

  @override
  State<ChildB> createState() => _ChildBState();
}

class _ChildBState extends State<ChildB> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('countB ${widget.countB}'),
      ],
    );
  }
}

根据经验,刷新区域当然是越小越好,有什么方式可以让父组件修改状态后,直接通知到需要变更状态的子组件,而不惊动其余组件呢?

观察者模式可以很好的解决这一问题,当主题对象状态发生变化时,它的所有依赖者会收到通知,以此来执行接下来的动作。

Flutter提供的 ChangeNotifier 就实现了观察者模式。

这里定义一个 ChangeNotifier ,包裹住一个状态,同时提供一个add方法,来修改状态。

scala 复制代码
class CountHolder extends ChangeNotifier {
  CountHolder(this._value);

  int _value;

  int get value => _value;

  set value(int newValue) {
    _value = newValue;
    notifyListeners();
  }

  void add() {
    value++;
  }
}

将使用 countB 逻辑地方,全部替换为使用 CountHolder

scala 复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(child: ParentWidget()),
      ),
    );
  }
}

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

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  final _countHolder = CountHolder(0);

  void changeB() {
    _countHolder.add();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: changeB,
          child: const Text('change countB'),
        ),
        ChildA(),
        ChildB(countHolder: _countHolder),
      ],
    );
  }
}

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

  @override
  State<ChildA> createState() => _ChildAState();
}

class _ChildAState extends State<ChildA> {
  int countA = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              countA += 1;
            });
          },
          child: const Text('change countA'),
        ),
        Text('countA $countA'),
      ],
    );
  }
}

class ChildB extends StatefulWidget {
  const ChildB({
    required this.countHolder,
    super.key,
  });

  final CountHolder countHolder;

  @override
  State<ChildB> createState() => _ChildBState();
}

class _ChildBState extends State<ChildB> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ListenableBuilder(
          listenable: widget.countHolder,
          builder: (_, __) {
            return Text('countB ${widget.countHolder.value}');
          },
        ),
      ],
    );
  }
}

class CountHolder extends ChangeNotifier {
  CountHolder(this._value);

  int _value;

  int get value => _value;

  set value(int newValue) {
    _value = newValue;
    notifyListeners();
  }

  void add() {
    value++;
  }
}

这时,在父组件 changeB 的时候,只会有B组件的 Text 进行重建。

ChangeNotifier 的订阅过程不是这里的重点,顺着 ListenableBuilder 的 listenable 参数很容易看到原理。最终数据改变后也是调用到 setState 方法。

scss 复制代码
  源码:
  void _handleChange() {
    if (!mounted) {
      return;
    }
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }

这里如果将 CountHolder 改个名字,改成 CountController ,是否就是平时使用各类 controller 的样子了?(可以自行修改看看,透过现象看本质)

Flutter 中常见的几种 Controller:

复制代码
TextEditingController - 用于控制和监听文本输入,管理文本字段的状态,如获取/设置文本内容、控制光标位置等
ScrollController - 管理可滚动组件的滚动行为,允许程序控制滚动位置、监听滚动事件等
PageController - 专门用于PageView组件,控制页面的切换、滚动和动画效果
TabController - 用于TabBar和TabBarView之间的协调,管理标签页的切换状态
AnimationController - 动画控制器,管理动画的运行、暂停、反向等状态,是Flutter动画系统的核心

从技术实现角度看,这些 Controller 本质上都是特殊的 ChangeNotifier ,它们直接或间接地继承ChangeNotifier(Listenable),它们:

less 复制代码
内部维护一个或多个状态值   (如例子中 CountHolder 的 _value )
提供改变这些状态的方法   (如例子中 CountHolder 的 add() )
实现通知机制,当状态变化时通知监听者   (如例子中 CountHolder 的 notifyListeners() )

它们这么做是遵循了Flutter的单向数据流模式,作为状态与UI之间的桥梁,使代码结构更加清晰,逻辑更加分离。

最后别忘了!通常需要手动 dispose() 以避免内存泄漏。

相关推荐
应用市场12 分钟前
用本机跑的开源音乐模型,给安卓小游戏做 20 秒无缝循环背景音乐
android·python·游戏开发·soundpool·音频处理
企业数字化笔记17 分钟前
表单发布前怎么验收?版本引用、字段兼容和历史数据回归
android·数据挖掘·回归
生信分析笔记23 分钟前
Nature 正刊 GPN-Star 基因组语言模型
android
渣渣盟24 分钟前
Flink 状态管理深度剖析:从 KeyedState 到 RocksDB,状态如何存储、恢复与扩缩容
android·大数据·flink
avi911130 分钟前
电影开源Jevdo - 源码介绍(vue版)没有android 和 没有wpf
android·wpf·框架·数据爬虫·meego·电影管理
OxYGC36 分钟前
[工程] PHP2Shell 漏洞解析:两条 PHP 内核 RCE,为什么应用层的防护全部失效
android·开发语言·php
拥小抱16 小时前
Compose 知识点
android
文章永久免费只为良心16 小时前
Web应用安全与防护_第三章_日志文件包含_从一道 CTFshow 题目讲透「日志投毒型 LFI → RCE」:原理、时序陷阱与全变种攻防手册
android·网络安全·渗透测试·漏洞危害·漏洞描述·日志投毒·红队指南
AIsoft_868917 小时前
学术讲座录音如何整理?录音APP可行吗
android·iphone
Patrick在香港19 小时前
一个斜杠,把毫秒变成十几秒
android·数据库·python·性能优化·正则表达式·回溯·redos