状态提升
状态提升是 Flutter 中的一种状态管理方法,用于在多个组件之间共享状态。通过将状态提升到组件树的更高层次,可以更容易地管理和更新状态。
示例
假设我们有两个子组件,分别是 Counter 和 CounterDisplay,它们都用于显示计数器的值。我们可以将计数器的状态提升到父组件 CounterApp 中,并通过这两个子组件来展示状态。
class CounterApp extends StatefulWidget {
@override
_CounterAppState createState() => _CounterAppState();
}
class _CounterAppState extends State<CounterApp> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Counter App')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Counter(counter: _counter),
CounterDisplay(counter: _counter),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
),
);
}
}
class Counter extends StatelessWidget {
final int counter;
Counter({required this.counter});
@override
Widget build(BuildContext context) {
return Text('Counter: $counter', style: TextStyle(fontSize: 24));
}
}
class CounterDisplay extends StatelessWidget {
final int counter;
CounterDisplay({required this.counter});
@override
Widget build(BuildContext context) {
return Text('The current count is: $counter', style: TextStyle(fontSize: 18));
}
}
如何访问和修改外部状态
子组件访问父组件的状态
子组件访问父组件的状态很简单,只需要将父组件的状态作为参数传递给子组件即可。
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ChildWidget(counter: _counter),
],
);
}
}
class ChildWidget extends StatelessWidget {
final int counter;
ChildWidget({required this.counter});
@override
Widget build(BuildContext context) {
return Text('Counter: $counter');
}
}
子组件修改父组件的状态
子组件修改父组件状态也很简单,通过传递一个回调函数给子组件,子组件在需要修改状态时调用该回调函数即可。
class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ChildWidget(onIncrement: _incrementCounter),
],
);
}
}
class ChildWidget extends StatelessWidget {
final Function onIncrement;
ChildWidget({required this.onIncrement});
@override
Widget build(BuildContext context) {
return TextButton(onPressed: onIncrement, child: Text('Increment'));
}
}
所谓「控制器」到底是什么
示例
当我们使用 TextField 时,我们通常会使用 TextEditingController 来管理文本框的状态。我们可以通过 TextEditingController 的 text 属性来获取和设置文本框的文本内容。并且监听 TextEditingController 的 text 属性变化。
TextEditingController _controller = TextEditingController();
_controller.text = 'Hello, World!';
_controller.addListener(() {
print(_controller.text);
});
TextField(
controller: _controller,
)
自定义一个Controller
下面我们通过自己定义一个Controller来实现一个计数器。从而理解所谓的「控制器」到底是什么。
class CounterController extends ChangeNotifier {
int _counter = 0;
int get counter => _counter;
set counter(int value) {
_counter = value;
notifyListeners();
}
void increment() {
_counter++;
notifyListeners();
}
}
class CounterWidget extends StatelessWidget {
final CounterController controller;
CounterWidget({required this.controller});
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (context, child) {
return Text('Counter: ${controller.counter}');
},
);
}
}
main() {
runApp(
MyApp(),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final CounterController controller = CounterController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter App')),
body: Column(
children: [
CounterWidget(controller: controller),
TextButton(onPressed: () {
controller.increment();
}, child: Text('Increment')),
],
),
);
}
}
详解 ChangeNotifier 机制
从上面可以看到,ChangeNotifier 机制的核心在于 notifyListeners 方法。每当状态发生变化时,我们都需要调用 notifyListeners 方法来通知所有监听该状态的组件。
至于组件如何监听状态的变化,我们使用了 ListenableBuilder 组件。当状态发生变化时,ListenableBuilder 组件会重新构建。从而实现状态的变化。
除了 ChangeNotifier 之外,还有 ValueNotifer 等组件,用于管理状态。
继承式组件 InheritedWidget
InheritedWidget 是 Flutter 中的一种组件,用于在组件树中共享状态。通过继承 InheritedWidget 组件,我们可以将状态传递给子组件。
示例
import 'package:flutter/material.dart';
class MyColor extends InheritedWidget {
final Color color;
final Function() changeColor;
MyColor({
required this.color,
required this.changeColor,
required Widget child,
}) : super(child: child);
@override
bool updateShouldNotify(MyColor oldWidget) {
return oldWidget.color != color;
}
static MyColor of(BuildContext context) {
// 根据类型获取最近的 MyColor 组件 并 添加依赖
// 对应的还有 `context.getInheritedWidgetOfExactType<MyColor>()` 获取最近的 MyColor 组件 但不添加依赖
return context.dependOnInheritedWidgetOfExactType<MyColor>()!;
}
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Color _color = Colors.blue;
void _changeColor() {
setState(() {
_color = _color == Colors.blue ? Colors.red : Colors.blue;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyColor(
color: _color,
changeColor: _changeColor,
child: Scaffold(
appBar: AppBar(title: Text('MyColor Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const ColorDisplayWidget(),
SizedBox(height: 20),
ElevatedButton(
onPressed: _changeColor,
child: Text('Change Color'),
),
],
),
),
),
),
);
}
}
class ColorDisplayWidget extends StatelessWidget {
const ColorDisplayWidget({super.key});
@override
Widget build(BuildContext context) {
final myColor = MyColor.of(context);
return Container(
width: 100,
height: 100,
color: myColor.color,
child: Center(
child: Text(
'Current Color',
style: TextStyle(color: Colors.white),
),
),
);
}
}
void main() {
runApp(MyApp());
}
上面的例子中 ColorDisplayWidget 是一个 const 组件,但是当我们点击按钮之后,ColorDisplayWidget 的颜色仍然能够发生变化。就是因为我们通过 InheritedWidget 的方式获取了color的值
当
dependOnInheritedWidgetOfExactType方法被调用时,Flutter 会自动将当前的BuildContext添加到InheritedWidget的依赖列表中。当InheritedWidget的状态发生变化时,Flutter 会自动调用updateShouldNotify方法,从而通知所有依赖于该状态的组件重新构建。 这时 子组件的 生命周期方法didChangeDependencies会被调用。
另外我们可以通过 updateShouldNotifyDependents 方法来决定是否通知所有依赖于该状态的组件重新构建。从而实现更精细化的状态管理。
class MyColor extends InheritedWidget {
final Color color;
final Function() changeColor;
MyColor({
required this.color,
required this.changeColor,
required Widget child,
}) : super(child: child);
@override
bool updateShouldNotify(MyColor oldWidget) {
return oldWidget.color != color;
}
@override
bool updateShouldNotifyDependents(MyColor oldWidget, Set<Object> dependencies) {
// 如果依赖列表发生变化,则通知所有依赖于该状态的组件重新构建
if (dependencies.contains('color')) {
return oldWidget.color != color;
}
return false;
}
static MyColor of(BuildContext context) {
// 根据类型获取最近的 MyColor 组件 并 添加依赖
// 对应的还有 `context.getInheritedWidgetOfExactType<MyColor>()` 获取最近的 MyColor 组件 但不添加依赖
return context.dependOnInheritedWidgetOfExactType<MyColor>()!;
}
}
自己动手做个 Provider
我们的项目目录结构如下:
lib
├── change_notifier_provider.dart
├── main.dart
├── model
│ └── logo_model.dart
└── ui
├── control_panel.dart
└── logo.dart
// change_notifier_provider.dart
import 'package:flutter/material.dart';
class ChangeNotifierProvider<T extends Listenable> extends StatefulWidget {
const ChangeNotifierProvider(
{super.key, required this.create, required this.child});
final Widget child;
final T Function(BuildContext context) create;
@override
State<ChangeNotifierProvider> createState() =>
_ChangeNotifierProviderState<T>();
static T of<T>(BuildContext context, {required bool listen}) {
if (listen) {
return context.dependOnInheritedWidgetOfExactType<_InnerInheritedWidget<T>>()!.model;
} else {
return context.getInheritedWidgetOfExactType<_InnerInheritedWidget<T>>()!.model;
}
}
}
class _ChangeNotifierProviderState<T extends Listenable>
extends State<ChangeNotifierProvider<T>> {
late T model;
@override
void initState() {
super.initState();
model = widget.create(context);
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: model,
builder: (BuildContext context, Widget? child) {
return _InnerInheritedWidget(
model: model,
child: child!,
);
},
child: widget.child,
);
}
}
class _InnerInheritedWidget<T> extends InheritedWidget {
const _InnerInheritedWidget({super.key, required super.child, required this.model});
final T model;
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
return true;
}
}
extension ChangeNotifierProviderExtension on BuildContext {
T watch<T extends Listenable>() {
return ChangeNotifierProvider.of<T>(this, listen: true);
}
T read<T extends Listenable>() {
return ChangeNotifierProvider.of<T>(this, listen: true);
}
}
// model/logo_model.dart
import 'package:flutter/material.dart';
class LogoModel extends ChangeNotifier {
bool _flipX = false;
bool _flipY = false;
double _size = 50;
bool get flipX => _flipX;
set flipX(bool value) {
_flipX = value;
notifyListeners();
}
bool get flipY => _flipY;
set flipY(bool value) {
_flipY = value;
notifyListeners();
}
double get size => _size;
set size(double value) {
_size = value;
notifyListeners();
}
}
// ui/control_panel.dart
import 'package:flutter/material.dart';
import '/change_notifier_provider.dart';
import '/model/logo_model.dart';
class ControlPanel extends StatefulWidget {
const ControlPanel({super.key});
@override
State<ControlPanel> createState() => _ControlPanelState();
}
class _ControlPanelState extends State<ControlPanel> {
@override
Widget build(BuildContext context) {
var model = context.watch<LogoModel>();
return Card(
margin: const EdgeInsets.all(32),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Size'),
Slider(
value: model.size,
min: 0,
max: 100,
onChanged: (value) {
model.size = value;
},
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('FlipX'),
Switch(
value: model.flipX,
onChanged: (value) {
model.flipX = value;
},
),
const SizedBox(width: 32),
const Text('FlipY'),
Switch(
value: model.flipY,
onChanged: (value) {
model.flipY = value;
},
),
],
),
],
),
),
);
}
}
// ui/logo.dart
import 'package:flutter/material.dart';
import '/change_notifier_provider.dart';
import '/model/logo_model.dart';
class Logo extends StatelessWidget {
const Logo({super.key});
@override
Widget build(BuildContext context) {
var model = context.read<LogoModel>();
return Card(
child: Transform.flip(
flipX: model.flipX,
flipY: model.flipY,
child: FlutterLogo(size: model.size + 50.0),
),
);
}
}
// main.dart
import 'package:flutter/material.dart';
import 'change_notifier_provider.dart';
import 'model/logo_model.dart';
import 'ui/control_panel.dart';
import 'ui/logo.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.blue,
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ChangeNotifierProvider<LogoModel>(
create: (BuildContext context) => LogoModel(),
child: const Column(
children: [
Logo(),
ControlPanel(),
],
),
),
),
);
}
}
效果
