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

相关推荐
移动开发者1号2 小时前
解析401 Token过期自动刷新机制:Kotlin全栈实现指南
android·kotlin
移动开发者1号2 小时前
网络缓存策略与DiskLruCache解析
android·kotlin
一只柠檬新14 小时前
Web和Android的渐变角度区别
android
志旭14 小时前
从0到 1实现BufferQueue GraphicBuffer fence HWC surfaceflinger
android
_一条咸鱼_15 小时前
Android Runtime堆内存架构设计(47)
android·面试·android jetpack
用户20187928316715 小时前
WMS(WindowManagerService的诞生
android
用户20187928316715 小时前
通俗易懂的讲解:Android窗口属性全解析
android
openinstall15 小时前
A/B测试如何借力openinstall实现用户价值深挖?
android·ios·html
二流小码农15 小时前
鸿蒙开发:资讯项目实战之项目初始化搭建
android·ios·harmonyos
志旭16 小时前
android15 vsync源码分析
android