状态管理¶
iOS控件有响应,按钮按下去有状态。
flutter增量渲染,没有必要拥有状态。
复杂的设计,一个页面需要改变的东西多。数据的保留。所以需要有状态的widget。底层中渲染逻辑和数据逻辑分开管理。保留的是数据逻辑,不保留的是渲染逻辑,UI每时每刻都在创建新的,但是数据一直保留。
一般整个页面是StatelessWidget
。需要setState(() {});
的,需要生命周期dispose()释放资源的,initState()异步请求数据的,就用StatefulWidget
。
//外部可能会传递数据
class StatefulWidgetDemo extends StatefulWidget {
const StatefulWidgetDemo({Key? key}) : super(key: key);
@override
State<StatefulWidgetDemo> createState() => _StatefulWidgetDemoState();
}
//状态管理者
class _StatefulWidgetDemoState extends State<StatefulWidgetDemo> {
@override
Widget build(BuildContext context) {
return Container();
}
}
StatefulWidget¶
-
继承StatefulWidget,用于对外提供接口。
-
实现createState方法,管理数据 state状态。
state¶
定义state类:class _state类名 extends State<哪个StatefulWidget 操纵的哪个>
继承State用来管理状态。
- 实现build方法,访问数据,数据在state里面。
- 通过setState设置/改变数据,会重新走一遍build方法,重新渲染整个UI,旧的舍弃掉了,new新的widget
-
推荐:定义const修饰的widget就不会new。父widget能设置const就设置const,不能设置就设置子widget为const。组件拆分的细一些。
- 例如:cell非常复杂的时候:把cell的一部分改为有状态的,而不是把整个cell改为有状态的(为了性能考虑)。
- 不推荐:在build方法外面new一个widget,build里面直接用,就不会重新new。context也用不了build方法中的BuildContext。
StatefulBuilder¶
StatefulBuilder
用于创建一个局部的可变状态,不需要为了仅仅更新一小部分 UI 而创建一个完整的 StatefulWidget
。
用处:当在 StatelessWidget
或者对话框中有一些可变状态时。
这里是一个简单的 StatefulBuilder
使用示例,它在一个对话框中使用,允许用户增加一个计数器的值而不需要关闭对话框:
import 'package:flutter/material.dart';
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
int counter = 0; // 初始计数器值
return AlertDialog(
title: Text('StatefulBuilder Demo'),
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('You have pushed the button this many times:'),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
);
},
),
actions: <Widget>[
TextButton(
child: Text('Increment'),
onPressed: () {
//counter++;
//// 这里我们告诉 StatefulBuilder 需要重建它的状态
//(context as Element).markNeedsBuild();
setState(() {
counter++;
});
},
),
],
);
},
);
},
child: Text('Show Dialog'),
);
}
}
在这个例子中,在 MyHomePage
类中创建了一个按钮,当按下时会显示一个对话框。对话框使用 StatefulBuilder
来创建一个可变的计数器。当用户点击 "Increment" 按钮时,计数器的值会增加,并且通过调用 setState
函数,告诉 StatefulBuilder
需要重建,以便更新显示的计数器值。
注意:上述代码中的 (context as Element).markNeedsBuild();
是一种强制更新 UI 的方法,推荐的做法是在 StatefulBuilder
的 builder
方法中调用 setState
函数来更新状态,告诉 StatefulBuilder
需要重建其子树,从而更新计数器的显示值。
访问和修改外部状态¶
状态提升,把自己写的组件里面的属性提升到父组件,由父组件传进来值。
如果是别人写的组件就不好实现这种方式。
class Foo extends StatelessWidget {
//外面传值,子组件访问外面的值
final int count;
//修改外部的值
//利用回传函数
// final void Function() onPressed;
final VoidCallback onPressed;
//回传函数带参数
final void Function(int) add;
const Foo({
Key? key,
required this.count,
required this.onPressed,
required this.add,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
color: Colors.red.withOpacity(0.5),
margin: const EdgeInsets.all(16),
child: Column(
children: [
const Center(
child: FlutterLogo(
size: 50,
),
),
Text('$count'),
ElevatedButton(
//修改外部的值
//利用回传函数
onPressed: () => onPressed(),
child: const Text("点击按钮,修改count的值"),
),
],
),
);
}
}
外部组件控制内部小组件¶
需要用到控制器。
double是值传递,对象是指针传递。值传递里外不同步。创建一个类,value是double类型的值。
内部组件构造函数参数有一个controller,外部组件controller成员传进来。类似TextField
。
1、extends一个ChangeNotifier
class AccountDetailListController with ChangeNotifier {
List _list = [];
//读数据
List get list => _list;
//写数据
set list(List newList) {
_list = newList;
notifyListeners(); //告诉监听者 刷新build
}
}
2、每当调用了上面的notifyListeners();
,如果有监听。则会调用builder。渲染范围尽量缩小,以节约性能。
child: ListenableBuilder(
listenable: widget.controller, //监听,
builder: (BuildContext context, Widget? child) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return DiscoverCell(
title: _options[index],
imageName: defaultImageName,
onTapCallBack: () {},
);
},
itemCount: _options.length,
);
},
),
里面也是状态提升。controller提到了父widget。
状态提升¶
提到父组件,父级组件的变量通过传参给子组件。
子组件修改父组件通过回传函数。
内部状态复杂的组件,内部状态封装控制器,通过状态提升,把控制器的实例化交给父级组件,通过传参和回调函数来控制控制器,即复杂组件的内部状态。
//新增账套成功之后,当前组件调用`callBack()`,父组件去实现callBack。
class AddAccount extends StatefulWidget {
final void Function() callBack;
const AddAccount({Key? key, required this.callBack}) : super(key: key);
@override
State<AddAccount> createState() => _AddAccountState();
}
通过一层一层的传参的问题:使用InheritedWidget
继承式组件。
InheritedWidget¶
InheritedWidget
继承式组件发生了更新才通知其它依赖的子组件更新。
适用于传的组件树比较深,组件比较多的情况。
在比较大的项目中做状态管理。数据共享。
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
/// 放到树的根 才能共享数据
return const MyColor(
color: Colors.yellow,
child: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
required this.title,
});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
Color _color = Colors.blue;
@override
Widget build(BuildContext context) {
return MyColor(
color: _color,
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:',),
const Foo(),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_color = Colors.black;
});
},
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
}
class Foo extends StatelessWidget {
const Foo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: MyColor.of(context).color,
// color: MyColor.maybeOf(context)?.color,
);
}
}
//class Foo extends StatefulWidget {
// final int count;
//
// const Foo({super.key, required this.count});
//
// @override
// _FooState createState() => _FooState();
// }
//
// class _FooState extends State<Foo> {
// @override
// void didChangeDependencies() {
// super.didChangeDependencies();
// }
//
// @override
// Widget build(BuildContext context) {
// return Container(
// width: 100,
// height: 100,
// color: MyColor.of(context).color,
// // color: MyColor.maybeOf(context)?.color,
// );
// }
// }
/// 继承式组件
class MyColor extends InheritedWidget {
final Color color;
const MyColor({
super.key,
required super.child,
required this.color,
});
static MyColor of(BuildContext context) {
///注入依赖
///在当前context开始,在组件树上找最近的某一种类型的InheritedWidget
final myColor = context.dependOnInheritedWidgetOfExactType<MyColor>()!;
///获取依赖,只获得一次。并没有注入依赖,添加监听
// final myColor = context.getInheritedWidgetOfExactType<MyColor>();
return myColor;
}
static MyColor? maybeOf(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyColor>();
}
//该回调决定当前color发生变化时,是否通知子组件依赖color的Widget(使用了MyColor.of的组件)
@override
bool updateShouldNotify(covariant MyColor oldWidget) {
///当前组件变化时,子组件是否需要重绘。
// TODO: implement updateShouldNotify
print('object: ${oldWidget.color} -> $color');
return oldWidget.color != color;
}
}
Provider¶
在pubspec.yaml
引入状态管理的框架provider
。
管理页面数据,也可以使用数据共享,数据共享是单向传递。
Provider更加方便,各个页面共享数据。
传递数据,通讯。
1、ChangeNotifier¶
创建一个需要共享的状态类,这个类需要继承 ChangeNotifier
notifyListeners(); //告诉监听者 刷新build
import 'package:flutter/material.dart';
//定义一个数据模型 通过混入ChangeNotifier管理监听者(通知模式)
class AccountDetailListController with ChangeNotifier {
List<String> _list = [];
//读数据
List<String> get list => _list;
//写数据
set list(List<String> newList) {
_list = newList;
notifyListeners(); //告诉监听者 刷新build
}
void hhh() {
_list = ["fdsjklfj"];
notifyListeners();
}
}
2、widget树的比较高的层面使用 ChangeNotifierProvider
来提供这个状态¶
有一个create: (BuildContext context){}
方法需要传上面的ChangeNotifier
。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Account extends StatelessWidget {
const Account({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => AccountDetailListController(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.yellow,
primarySwatch: Colors.grey, //主题色 影响整个app
),
home: const RootPage(),
),
);
}
}
取数据Provider.of<T>(context)
¶
在任何widget的build方法中
var li =
Provider.of<AccountDetailListController>(context, listen: false).list;
print("llllll:$li");
第二个页面¶
性能优化:最小颗粒度的渲染刷新UI。使用Consumer
。
Consumer包括:
- key
- builder
- child
notifyListeners()发出通知的时候,下面的bulider会重新走一遍,刷新组件。
Consumer<AccountDetailListController>(
builder: (context, value, child) {
return Container();
},
)
//第二个页面
class ProviderTwo extends StatelessWidget {
const ProviderTwo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// //取数据 -- 不再统一的取数据
// final _counter = Provider.of<CountModel>(context);
return Scaffold(
appBar: AppBar(
title: const Text('ProviderTwo'),
),
body: Center(
//哪里用数据,就在哪里取数据
child: Consumer<CountModel>(
builder: (context, CountModel counter, child) =>
Text('第二个页面count:${counter.counter}'),
),
),
floatingActionButton: Consumer(
child: const MyIcon(), //通过child隔离开,数据变的时候,child不需要重新渲染
builder: (context, CountModel counter, child) => FloatingActionButton(
onPressed: counter.increment,
child: child,
),
),
);
}
}
//自定义icon
class MyIcon extends StatelessWidget {
const MyIcon({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
print('MyIcon build');
return const Icon(Icons.add);
}
}
案例二¶
context.watch<LogoModel>();
添加监听。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => LogoModel(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Logo(),
ControlPanel(),
],
),
),
);
}
}
class Logo extends StatelessWidget {
const Logo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
///添加监听
final model = context.watch<LogoModel>();
return Card(
child: Transform.flip(
flipX: model.flipX,
flipY: model.flipY,
child: FlutterLogo(
size: model.size,
),
),
);
}
}
class ControlPanel extends StatefulWidget {
const ControlPanel({Key? key}) : super(key: key);
@override
State<ControlPanel> createState() => _ControlPanelState();
}
class _ControlPanelState extends State<ControlPanel> {
@override
Widget build(BuildContext context) {
final model = context.watch<LogoModel>();
return Card(
margin: const EdgeInsets.all(32),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Flip X: '),
Switch(
value: model.flipX,
onChanged: (bool value) {
model.flipX = value;
},
),
const Text('Flip Y: '),
Switch(
value: model.flipY,
onChanged: (bool value) {
model.flipY = value;
},
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Size: '),
Slider(
min: 50,
max: 300,
value: model.size,
onChanged: (value) {
model.size = value;
},
),
],
),
],
),
);
}
}
class LogoModel extends ChangeNotifier {
bool _flipX = false;
bool get flipX => _flipX;
set flipX(value) {
_flipX = value;
notifyListeners();
}
bool _flipY = false;
bool get flipY => _flipY;
set flipY(value) {
_flipY = value;
notifyListeners();
}
double _size = 100.0;
double get size => _size;
set size(value) {
_size = value;
notifyListeners();
}
}
利用泛型自己实现Provider¶
///利用范型做通用封装Provider
///多套一层StatefulWidget,State的build方法return一个InheritedWidget
///create方法:创建extends ChangeNotifier的model
class ChangeNotifierProvider<T extends Listenable> extends StatefulWidget {
final T Function() create;
final Widget child;
const ChangeNotifierProvider(
{super.key, required this.create, required this.child});
@override
State<ChangeNotifierProvider> createState() =>
_ChangeNotifierProviderState<T>();
static T of<T>(BuildContext context, {required bool listen}) {
///是否监听
if (listen) {
return context
.dependOnInheritedWidgetOfExactType<_InheritedWidget<T>>()!
.model;
} else {
return context
.getInheritedWidgetOfExactType<_InheritedWidget<T>>()!
.model;
}
}
}
class _ChangeNotifierProviderState<T extends Listenable>
extends State<ChangeNotifierProvider<T>> {
late T model;
@override
void initState() {
super.initState();
model = widget.create();
}
@override
Widget build(BuildContext context) {
///借助`ListenableBuilder`监听model,每当model改变时,组件进行重绘。
return ListenableBuilder(
listenable: model,
builder: (BuildContext context, Widget? child) {
return _InheritedWidget(model: model, child: widget.child);
},
);
}
}
//InheritedWidget
class _InheritedWidget<T> extends InheritedWidget {
final T model;
const _InheritedWidget({
super.key,
required this.model,
required super.child,
});
@override
bool updateShouldNotify(covariant _InheritedWidget oldWidget) {
// TODO: implement updateShouldNotify
return true;
}
}
///扩展
extension Consumer on BuildContext {
T watch<T>() => ChangeNotifierProvider.of(this, listen: true);
T read<T>() => ChangeNotifierProvider.of(this, listen: false);
}
使用自己Provider示例¶
- 创建
LogoModel extends ChangeNotifier
- 创建
LogoModelProvider extends InheritedWidget
。 - 在组件树最上层组件插入LogoModelProvider,负责向整个组件树下面传递model。
- 子组件
- 使用
final model = context.dependOnInheritedWidgetOfExactType<LogoModelProvider>()!.model;
查找model。这个方法可以在LogoModelProvider封装一个of方法,直接调用of方法。 - 借助
ListenableBuilder
监听model,每当model改变时,组件进行重绘。
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: () => LogoModel(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Logo(),
ControlPanel(),
],
),
),
);
}
}
class Logo extends StatelessWidget {
const Logo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
///添加监听
// final model = ChangeNotifierProvider.of<LogoModel>(context);
final model = context.watch<LogoModel>();
return Card(
child: Transform.flip(
flipX: model.flipX,
flipY: model.flipY,
child: FlutterLogo(
size: model.size,
),
),
);
}
}
class ControlPanel extends StatelessWidget {
const ControlPanel({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// final model = ChangeNotifierProvider.of<LogoModel>(context);
final model = context.watch<LogoModel>();
///监听
return Card(
margin: const EdgeInsets.all(32),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Flip X: '),
Switch(
value: model.flipX,
onChanged: (bool value) {
model.flipX = value;
},
),
const Text('Flip Y: '),
Switch(
value: model.flipY,
onChanged: (bool value) {
model.flipY = value;
},
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Size: '),
Slider(
min: 50,
max: 300,
value: model.size,
onChanged: (value) {
model.size = value;
},
),
],
),
],
),
);
}
}
class LogoModel extends ChangeNotifier {
bool _flipX = false;
bool get flipX => _flipX;
set flipX(value) {
_flipX = value;
notifyListeners();
}
bool _flipY = false;
bool get flipY => _flipY;
set flipY(value) {
_flipY = value;
notifyListeners();
}
double _size = 100.0;
double get size => _size;
set size(value) {
_size = value;
notifyListeners();
}
}
model是同一个¶
final model = context.watch<AccountDetailListController>();
,获取的model是同一个。
保持小部件状态¶
每次切换进入页面都会走initState(),如果不想刷新数据的话,需要混入。
Mixins混入¶
目的:给一个类增加功能,是多继承。
保持部件状态有一个前提:在widget树中,不会被销毁。
AutomaticKeepAliveClientMixin¶
AutomaticKeepAliveClientMixin
是Flutter框架中的一个mixin,它是用来帮助管理页面状态的。
在Flutter中,当有一个widget(例如列表中的一个项目)离开视图时,它的状态可能会被销毁,以释放内存。但是有时候可能希望保持这个widget的状态,即使它离开了视图,它的状态也会被保持,当它再次出现时,它可以恢复到以前的状态。这就是AutomaticKeepAliveClientMixin
的用途。
- 使用with混入
AutomaticKeepAliveClientMixin
。 - 重写
wantKeepAlive
方法,使其返回true
。这将告诉Flutter你希望保持这个widget的状态。 - 需要在
build
方法中调用super.build(context)
,以确保AutomaticKeepAliveClientMixin
可以做它需要做的事情。
这个mixin特别适用于那些加载数据或者有用户交互的widget,例如滚动列表或者输入表单,因为这些widget的状态恢复可能会耗费更多的资源。
简单示例:
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context); // Do not remove this line, it's needed for AutomaticKeepAliveClientMixin.
return Container(); // Replace with your own widget.
}
}