项目数组作为 StatefulWidget 中的状态并确保 "setState" 仅触发数组中已更改项目的更新?
array of items as state in a StatefulWidget & ensuring "setState" only triggers updates for items in the array that has changed?
背景 - 想要为 StatefulWidget 使用动态项目列表。在我的用例中,小部件将调用 CustomePainter (canvas),因此有时会在 canvas 上绘制不同数量的图像,因此在父 StatefulWidget 中希望有一个“数组图片"。
问题 - 如果使用数组作为状态变量,我需要以编程方式做什么(如果有的话)以确保只有数组中已更改的项目才真正得到“重新绘制”,特别是在这种情况下,在 canvas.
上“重新绘制”
(也许这里有两个不同的答案,一个是针对具有 (a) 标准小部件的数组,另一个是针对 (b) 项目被传递给 CustomePainter 以在 canvas??)
例如,请参见下面的代码(@ch271828n 提供此代码是为了帮助我解决一个单独的问题 - )。此代码突出了主要思想,但不包括作为参数传递给 CustomPainter。
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<ui.Image> _backgroundImages;
@override
void initState() {
super.initState();
_asyncInit();
}
Future<void> _asyncInit() async {
final imageNames = ['firstimage', 'secondimage', 'thirdimage'];
// NOTE by doing this, your images are loaded **simutanously** instead of **waiting for one to finish before starting the next**
final futures = [for (final name in imageNames) loadImage(name)];
final images = await Future.wait(futures);
setState(() {
_backgroundImages = images;
});
}
Future<ui.Image> loadImage(imageString) async {
ByteData bd = await rootBundle.load(imageString);
// ByteData bd = await rootBundle.load("graphics/bar-1920×1080.jpg");
final Uint8List bytes = Uint8List.view(bd.buffer);
final ui.Codec codec = await ui.instantiateImageCodec(bytes);
final ui.Image image = (await codec.getNextFrame()).image;
return image;
// setState(() => imageStateVarible = image);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _backgroundImages != null ? YourWidget(_backgroundImages) : Text('you are loading that image'),
),
);
}
}
首先,谈谈build
s:确实你需要一个状态管理解决方案。也许看看 https://flutter.dev/docs/development/data-and-backend/state-mgmt/options。我个人建议 MobX
,它需要很少的样板文件并且可以使开发更快。
使用 setState
不是一个好主意。 setState
,在查看源代码时,只做:
void setState(VoidCallback fn) {
assert(...);
_element.markNeedsBuild();
}
所以它只不过是 markNeedsBuild
- 整个有状态小部件再次被调用 build
。您传递给 setState 的回调没有什么特别之处。
因此这种方式不能触发部分重建。
其次,你只想减少重绘,而不是减少构建次数,因为 flutter 的设计使得 build
可以轻松调用 60fps。那么事情就变得简单了:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Stack(
children: [
for (final image in _yourImages)
CustomPaint(
painter: _MyPainter(image),
),
],
);
}
}
class _MyPainter extends CustomPainter {
final ui.Image image;
_MyPainter(this.image);
@override
void paint(ui.Canvas canvas, ui.Size size) {
// paint it
}
@override
bool shouldRepaint(covariant _MyPainter oldDelegate) {
return oldDelegate.image != this.image;
}
}
请注意,您可以随时在首页调用setState
,因为这样很便宜。 (如果你的主页有很多子部件那么可能不好,那么你需要状态管理解决方案)。但是画家 不会 重画,除非 shouldRepaint
这么说。是啊!
背景 - 想要为 StatefulWidget 使用动态项目列表。在我的用例中,小部件将调用 CustomePainter (canvas),因此有时会在 canvas 上绘制不同数量的图像,因此在父 StatefulWidget 中希望有一个“数组图片"。
问题 - 如果使用数组作为状态变量,我需要以编程方式做什么(如果有的话)以确保只有数组中已更改的项目才真正得到“重新绘制”,特别是在这种情况下,在 canvas.
上“重新绘制”(也许这里有两个不同的答案,一个是针对具有 (a) 标准小部件的数组,另一个是针对 (b) 项目被传递给 CustomePainter 以在 canvas??)
例如,请参见下面的代码(@ch271828n 提供此代码是为了帮助我解决一个单独的问题 -
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<ui.Image> _backgroundImages;
@override
void initState() {
super.initState();
_asyncInit();
}
Future<void> _asyncInit() async {
final imageNames = ['firstimage', 'secondimage', 'thirdimage'];
// NOTE by doing this, your images are loaded **simutanously** instead of **waiting for one to finish before starting the next**
final futures = [for (final name in imageNames) loadImage(name)];
final images = await Future.wait(futures);
setState(() {
_backgroundImages = images;
});
}
Future<ui.Image> loadImage(imageString) async {
ByteData bd = await rootBundle.load(imageString);
// ByteData bd = await rootBundle.load("graphics/bar-1920×1080.jpg");
final Uint8List bytes = Uint8List.view(bd.buffer);
final ui.Codec codec = await ui.instantiateImageCodec(bytes);
final ui.Image image = (await codec.getNextFrame()).image;
return image;
// setState(() => imageStateVarible = image);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _backgroundImages != null ? YourWidget(_backgroundImages) : Text('you are loading that image'),
),
);
}
}
首先,谈谈build
s:确实你需要一个状态管理解决方案。也许看看 https://flutter.dev/docs/development/data-and-backend/state-mgmt/options。我个人建议 MobX
,它需要很少的样板文件并且可以使开发更快。
使用 setState
不是一个好主意。 setState
,在查看源代码时,只做:
void setState(VoidCallback fn) {
assert(...);
_element.markNeedsBuild();
}
所以它只不过是 markNeedsBuild
- 整个有状态小部件再次被调用 build
。您传递给 setState 的回调没有什么特别之处。
因此这种方式不能触发部分重建。
其次,你只想减少重绘,而不是减少构建次数,因为 flutter 的设计使得 build
可以轻松调用 60fps。那么事情就变得简单了:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Stack(
children: [
for (final image in _yourImages)
CustomPaint(
painter: _MyPainter(image),
),
],
);
}
}
class _MyPainter extends CustomPainter {
final ui.Image image;
_MyPainter(this.image);
@override
void paint(ui.Canvas canvas, ui.Size size) {
// paint it
}
@override
bool shouldRepaint(covariant _MyPainter oldDelegate) {
return oldDelegate.image != this.image;
}
}
请注意,您可以随时在首页调用setState
,因为这样很便宜。 (如果你的主页有很多子部件那么可能不好,那么你需要状态管理解决方案)。但是画家 不会 重画,除非 shouldRepaint
这么说。是啊!