我知道处理此类事情的更常见方法涉及使用
ChangeNotifierProvider
(CNP)等,但我想了解Provider
实际上是如何工作的。
如果我有一个
StatelessWidget
和 Provider
在它的小部件树后代中的某个地方,并且 StatelessWidget
被重新创建(即,它的 build()
方法再次运行),我希望创建一个新的 Provider
。 因此,如果 Provider
是根据已传递给该 StatelessWidget
的值创建的,我希望使用该新值创建一个新的 Provider
。 然后,任何使用 Provider.of<T>(context, listen:true)
的下游小部件都会更新......我想。
...但这并没有发生。 当使用新的输入值重建父级
StatelessWidget
时,Provider
不会重建自身。 为什么不呢?
再说一次,我并不是说这将是一个很好的方法,我只是想了解为什么会发生这样的情况。
这里有一些基本代码来说明我的意思: 在 DartPad 中。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: CounterUpper(),
),
),
);
}
}
class _CounterUpperState extends State<CounterUpper>
with TickerProviderStateMixin {
Ticker? ticker;
double animationValue = 0;
@override
void initState() {
print('Running _CenterUpperState.initState()');
super.initState();
ticker = Ticker((Duration elapsed) {
if (elapsed.inSeconds - animationValue > 1) {
setState(() {
print(
'Running _CenterUpperState.setState() with animationValue: $animationValue and elapsed.inSeconds: ${elapsed.inSeconds}');
animationValue = elapsed.inSeconds.toDouble();
});
}
if (elapsed.inSeconds > 5) {
ticker?.stop();
}
});
ticker!.start();
}
@override
Widget build(BuildContext context) {
print(
'Running _CenterUpperState.build() with animationValue: $animationValue');
return ProviderHolder(animationValue: animationValue);
}
}
class ProviderHolder extends StatelessWidget {
const ProviderHolder({super.key, required this.animationValue});
final double animationValue;
@override
Widget build(BuildContext context) {
print(
'Running ProviderHolder.build() with animationValue: $animationValue');
return Provider<ValueWrapper>(
create: (_) {
print('Running Provider.create() with animationValue: $animationValue');
return ValueWrapper(animationValue);
},
child: ContentHolder(),
);
}
}
class ContentHolder extends StatelessWidget {
const ContentHolder({super.key});
@override
Widget build(BuildContext context) {
final double providerValue =
Provider.of<ValueWrapper>(context, listen: true).value;
print('Running ContentHolder.build() with providerValue: $providerValue');
return Text(providerValue.toString());
}
}
class ValueWrapper {
const ValueWrapper(this.value);
final double value;
}
class CounterUpper extends StatefulWidget {
const CounterUpper({super.key});
@override
_CounterUpperState createState() => _CounterUpperState();
}
我尝试过以多种不同的方式重新排列(父小部件的不同版本,包括
StatefulWidget
)。 我知道最常见的方法是使用一些本身可以操纵的对象的 Provider
。 类似于包含整数以及一些更改该整数值的方法的基本类。 然后,您只需使用这些方法来更改值,而不是重新创建父窗口小部件。
但是……为什么这不起作用? 它的行为就像
Provider
被定义为 const,即使它传递的值不是 const。
提供程序不会在重建时再次调用创建,有趣的是,您在自己的日志记录中包含此信息,创建日志仅被调用一次,这就是提供程序内部处理创建函数的方式(即使重建运行,提供程序选择忽略您的创建脚本,以免重新创建已存在的对象)。
就像留下一些选项的编程一样,
选项 3 就是我要做的,类似这样的(快速完成,以便可以优化,但它可以作为一个基本示例):
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: CounterUpper(),
),
),
);
}
}
class CounterUpper extends StatefulWidget {
const CounterUpper({super.key});
@override
State<CounterUpper> createState() => _CounterUpperState();
}
class _CounterUpperState extends State<CounterUpper> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<ValueWrapper>(create: (_) => ValueWrapper(), child: const ContentHolder());
}
}
class ContentHolder extends StatelessWidget {
const ContentHolder({super.key});
@override
Widget build(BuildContext context) {
return Text(Provider.of<ValueWrapper>(context, listen: true).animationValue.toString());
}
}
class ValueWrapper with ChangeNotifier {
ValueWrapper() : animationValue = 0 {
ticker = Ticker((Duration elapsed) {
if (elapsed.inSeconds - animationValue > 1) {
updateAnimationValue(elapsed.inSeconds.toDouble());
animationValue = elapsed.inSeconds.toDouble();
}
if (elapsed.inSeconds > 5) {
ticker.stop();
}
});
ticker.start();
}
double animationValue;
late Ticker ticker;
void updateAnimationValue(double value) {
animationValue = value;
notifyListeners();
}
}