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