在不使用 setState 或 StatefulWidget 的情况下更新小部件的状态
通常,在 Flutter 中,要动态更新屏幕的 UI,您可以使用 setState。setState 在状态类中被调用,因此您需要一个 StatefulWidget 来调用 setState,因为 StatelessWidgets 没有状态类。但是,使用 ValueNotifier 和 ValueListenableBuilder,您甚至可以在 StatelessWidgets 中动态更新屏幕的 UI。
关于 setState
StatefulWidget 本身是不可变的(无法修改),因此我们使用State Object来修改 UI。
我们告诉这个状态对象使用一个名为 setState()的函数来更新我们屏幕的 UI
。你可以在这里阅读更多关于它的信息。
每当您想更改State对象的内部状态时,请在传递给 setState 的函数中进行更改。
setState(() { _myState = newValue; });
复制代码
它的作用是为它所在的状态调用 build() 方法,但只有在它执行了您在 {} 中告诉它执行的操作之后。才会生效。
但是,调用 setState 会重建整个 UI,无论更改多么小。例如看看这个应用程序:
setState 演示
import 'dart:developer';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Demo',
home: TestScreen(),
);
}
}
class TestScreen extends StatefulWidget {
const TestScreen({Key? key}) : super(key: key);
@override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
int counter = 0;
int buildCount = 0;
@override
Widget build(BuildContext context) {
log('-----build called------');
buildCount++;
return Scaffold(
appBar: AppBar(
title: Text('$buildCount'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Build method called ...',
style: TextStyle(fontSize: 24.0, color: Colors.grey),
),
const SizedBox(
height: 5.0,
),
Text(
'$buildCount times',
style: const TextStyle(fontSize: 20.0, color: Colors.black),
),
const SizedBox(
height: 20.0,
),
const Text(
'Counter',
style: TextStyle(fontSize: 24.0, color: Colors.grey),
),
const SizedBox(
height: 5.0,
),
Text(
'$counter times',
style: const TextStyle(fontSize: 20.0, color: Colors.black),
),
const SizedBox(
height: 50.0,
),
ElevatedButton(
onPressed: () => setState(() {
buildCount = 0;
counter = 0;
}),
child: const Text('Reset'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() {
counter++;
}),
child: const Icon(Icons.add),
),
);
}
}
复制代码
执行上述代码后,您会看到当您按下 FAB 时,每次都会调用 build 方法。尽管只需要更新一个文本小部件,但整个 UI 正在不必要地重建。这就是我个人在这些情况下更喜欢 ValueNotifier 的原因。使用 ValueNotifier,可以避免不必要的重建,而只更新需要更新的小部件的状态。
值通知器
ValueNotifier 是一种特殊类型的类,它扩展了 ChangeNotifer。
ValueNotifier 可以保存单个值。 值本身可以是任何类型。 它可以是 int、String、bool 或您自己的数据类型。
使用 ValueNotifier
可以提高 Flutter 应用程序的性能,因为它可以帮助减少小部件的重建次数。与setState
重建 整个小部件树的方法相比StatefulWidget
, ValueNotitier
性能要好得多。
只有订阅了 ValueNotifier 的小部件会在值被替换为不等于由相等运算符 == 评估的旧值时收到通知,此类通知其侦听器,并且仅重建该小部件的状态。。
ValueNotifier 声明如下
ValueNotifier<你的数据类型> variable_name = ValueNotifier(initial_value);
复制代码
例如:
ValueNotifier<int> counter = ValueNotifier(0);
复制代码
这个 ValueNotifier 发出一个初始设置为 0 的整数值。
但这还不够,到目前为止,我们刚刚声明了一个变量,要在这个特定值发生变化时实际更新 UI,我们需要使用ValueListenableBuilder
ValueListenableBuilder
ValueListenableBuilder 与 ValueNotifier 配合得非常好。您还可以将它与随时间变化的其他值一起使用。它侦听 ValueNotifier 发出的值,并在发出新值时重建。
ValueListenableBuilder 的构造函数如下所示
const ValueListenableBuilder({
required this.valueListenable,
required this.builder,
this.child,
})
复制代码
valueListenable 是您传递 ValueNotifier 的地方,
builder 是负责构建在 ValueNotifier 中的值更改时传递给它的小部件的函数。 这是您在值更改时传递要更改的小部件的地方。
child 参数是您传递任何不会根据值的变化而变化的小部件树的地方。 无论值发生任何变化,都不会重建此小部件树。 使用这个预先构建的孩子完全是可选的,但在某些情况下可以显着提高性能,因此是一个很好的做法。
现在,将我们的 ValueNotifier 与 ValueListenableBuilder 结合起来。 看看这个代码片段。
import 'dart:developer';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Demo',
home: TestScreen(),
);
}
}
class TestScreen extends StatefulWidget {
const TestScreen({Key? key}) : super(key: key);
@override
_TestScreenState createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
ValueNotifier<int> counter = ValueNotifier(0);
int buildCount = 0;
@override
void dispose() {
counter.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
log('-----build called------');
buildCount++;
return Scaffold(
appBar: AppBar(
title: Text('$buildCount'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Build method called ...',
style: TextStyle(fontSize: 24.0, color: Colors.grey),
),
const SizedBox(
height: 5.0,
),
Text(
'$buildCount times',
style: const TextStyle(fontSize: 20.0, color: Colors.black),
),
const SizedBox(
height: 20.0,
),
const Text(
'Counter',
style: TextStyle(fontSize: 24.0, color: Colors.grey),
),
const SizedBox(
height: 5.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ValueListenableBuilder<int>(
valueListenable: counter,
builder: (context, value, child) => Text(
'${counter.value} times',
style: const TextStyle(fontSize: 20.0, color: Colors.black),
),
),
const SizedBox(
width: 10.0,
),
const Icon(
Icons.animation_rounded,
color: Colors.black,
),
],
),
const SizedBox(
height: 50.0,
),
ElevatedButton(
onPressed: () {
buildCount = 0;
counter.value = 0;
},
child: const Text('Reset'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.value++;
},
child: const Icon(Icons.add),
),
);
}
}
复制代码
此代码片段只是对之前代码片段的重构。 在这里,您可以看到任何地方都没有调用 setState。 只需要 StatefulWidget 是因为我们需要处理计数器 ValueNotifer。
在上面的代码片段中:
ValueNotifier<int> counter = ValueNotifier(0);
复制代码
这是我们声明 ValueNotifier 对象并使用默认值 0 对其进行初始化的地方。
@override
void dispose() {
super.dispose();
counter.dispose();
}
复制代码
请记住在 statefulwidget 的 dispose 方法中处理 ValueNotifier 以避免任何内存泄漏。
ValueListenableBuilder<int>(
valueListenable : counter,
builder : ( context , value , child ) => Text(
'${ counter . value } times',
style : const TextStyle( fontSize : 20.0, color : Colors.black),
),
),
复制代码
在这里,您可以在 ValueListenableBuilder 的 builder 方法中提供要在状态更改时更新的小部件,并将计数器变量提供为 valueListenable: 。 这告诉 ValueListenableBuilder 侦听计数器变量并在该计数器变量的值发生任何更改时更新 Text 小部件。
使用 ValueNotifier 和 ValueListenableBuilder 后,您可以看到 build 方法只被调用一次,即最初构建 UI 时。
您现在可以使用这些小部件并探索使用这些小部件的不同方式来提高应用程序的性能,因为 ValueListenableBuilder 几乎可以应用于任何小部件。 (甚至使用 PrefferedSize 小部件来搭建 AppBars)。
(甚至使用PrefferedSize小部件来搭建 AppBars)。
评论