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() 以避免内存泄漏。

相关推荐
流浪汉kylin1 小时前
Android 图片选择器改系统
android
前行的小黑炭1 小时前
Android 上下位机开发:串口是什么,为什么android版本都比较低?粘包半包的原因以及处理思路,缓冲区处理,以及超时清空缓冲区....
android
程一个大前端1 小时前
【Flutter高效开发】GetX指南:一文学会状态管理、路由与依赖注入
flutter
移动开发者1号1 小时前
你知道Android中配置resourcePrefix的作用吗?
android
tangweiguo030519872 小时前
Android Compose 系统 Scope 的优化实践
android
我命由我123452 小时前
Android Cordova 开发 - Cordova 快速入门(Cordova 环境配置、Cordova 第一个应用程序)
android·开发语言·前端框架·android studio·h5·安卓·android-studio
老板来根葱2 小时前
应用进程创建二三事
android·源码阅读
ak啊5 小时前
Flutter UI 组件应用一:布局、交互、动画与弹窗
flutter
Kongzue5 小时前
让DialogX的消息提示玩出花 - 自定义PopTip和 PopNotification的避让动画
android·java