问题
Background - want to utilise a dynamic list of items for a StatefulWidget. In my usecase the widget will be calling a CustomePainter (canvas) so sometimes there will be a varying number of images to be drawn on the canvas, hence within parent StatefulWidget would like to have an "array of images".
Question - if using an array as the state variable what do I need to do programmtically (if anything) to ensure only the items that have changed within the array do infact get "redrawn", in particular in this case get "re-painted" on the canvas.
(Perhaps there are two separate answers here one for the array having (a) standard widgets, and one for the case where (b) items are being passed to a CustomePainter for painting on a canvas??)
As an example see code below (@ch271828n provided this to assist me here on a separate question - Getting 'Future<Image>' can't be assigned to a variable of type 'Image' in this Flutter/Dart code?). This code highlights the main idea, but doesn't include the passing onto a CustomPainter as parameters.
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'),
),
);
}
}
回答1:
Firstly, talking about build
s: Indeed you need a state management solution. Maybe look at https://flutter.dev/docs/development/data-and-backend/state-mgmt/options. Personally I suggest MobX
which requires few boilerplate and can make development much faster.
Using setState
is not a good idea. The setState
, when looking into source code, does nothing but:
void setState(VoidCallback fn) {
assert(...);
_element.markNeedsBuild();
}
So it is nothing but markNeedsBuild
- the whole stateful widget is called build
again. Your callback passed into setState has nothing special.
Thus this way cannot trigger partial rebuild.
Secondly, you only want to reduce repaint, but not reduce number of builds, because flutter is designed such that build
can be called by 60fps easily. Then things become easy:
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;
}
}
Notice that, you can call setState
in homepage whenever you like, because that is cheap. (if your homepage has a lot of children widget then maybe not good, then you need state management solution). But the painter will not repaint unless shouldRepaint
says so. Yeah!
来源:https://stackoverflow.com/questions/65838077/array-of-items-as-state-in-a-statefulwidget-ensuring-setstate-only-triggers