Flutter StatefulWidget中作为状态的项数组&;确保;“设置状态”;是否仅为阵列中已更改的项目触发更新?

Flutter StatefulWidget中作为状态的项数组&;确保;“设置状态”;是否仅为阵列中已更改的项目触发更新?,flutter,dart,dart-async,Flutter,Dart,Dart Async,Background-希望为StatefulWidget使用动态项目列表。在我的用例中,小部件将调用CustomePainter(canvas),因此有时会在画布上绘制不同数量的图像,因此在父状态中,小部件希望有一个“图像数组” 问题-如果使用数组作为状态变量,我需要以编程方式(如果有)执行什么操作,以确保只有数组中已更改的项才能“重新绘制”,尤其是在这种情况下,在画布上“重新绘制” (这里可能有两个单独的答案,一个是针对具有(a)标准小部件的数组,另一个是针对(b)项被传递给CustomePa

Background-希望为StatefulWidget使用动态项目列表。在我的用例中,小部件将调用CustomePainter(canvas),因此有时会在画布上绘制不同数量的图像,因此在父状态中,小部件希望有一个“图像数组”

问题-如果使用数组作为状态变量,我需要以编程方式(如果有)执行什么操作,以确保只有数组中已更改的项才能“重新绘制”,尤其是在这种情况下,在画布上“重新绘制”

(这里可能有两个单独的答案,一个是针对具有(a)标准小部件的数组,另一个是针对(b)项被传递给CustomePainter以便在画布上绘制的情况??)

例如,请参阅下面的代码(@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'),
      ),
    );
  }
}
导入'dart:typed_data';
将“dart:ui”导入为ui;
进口“包装:颤振/材料.省道”;
导入“包:flifter/services.dart”;
void main(){
runApp(MyApp());
}
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回材料PP(
主页:MyHomePage(),
);
}
}
类MyHomePage扩展StatefulWidget{
MyHomePage({Key}):超级(Key:Key);
@凌驾
_MyHomePageState createState()=>\u MyHomePageState();
}
类_MyHomePageState扩展状态{
列出背景图片;
@凌驾
void initState(){
super.initState();
_asyncint();
}
Future\u asyninit()异步{
最终图像名称=['firstimage','secondimage','thirdimage'];
//注意:这样做,您的图像将**同时**加载,而不是**等待一个图像完成后再开始下一个图像**
最终期货=[对于(imageNames中的最终名称)loadImage(名称)];
最终图像=等待未来。等待(未来);
设置状态(){
_背景图像=图像;
});
}
未来加载映像(imageString)异步{
ByteData bd=等待rootBundle.load(imageString);
//ByteData bd=等待rootBundle.load(“图形/bar-1920×1080.jpg”);
最终Uint8List字节=Uint8List.view(bd.buffer);
final ui.Codec Codec=wait ui.instantialeimagecodec(字节);
final ui.Image Image=(wait codec.getNextFrame()).Image;
返回图像;
//设置状态(()=>ImageStateVariable=image);
}
@凌驾
小部件构建(构建上下文){
返回脚手架(
正文:中(
child:_backgroundImages!=null?您的小部件(_backgroundImages):Text(‘您正在加载该图像’),
),
);
}
}

首先,谈论
build
s:实际上,您需要一个状态管理解决方案。也许看看。就我个人而言,我建议使用
MobX
,它只需要很少的样板文件,而且可以使开发更快

使用
setState
不是一个好主意。查看源代码时,
设置状态
,除了:

  void setState(VoidCallback fn) {
    assert(...);
    _element.markNeedsBuild();
  }
因此它只不过是
markNeedsBuild
——整个有状态小部件再次被称为
build
。传递到setState的回调没有什么特殊的。 因此,这种方式不能触发部分重建

其次,您只想减少重新绘制,但不想减少构建的数量,因为颤振的设计使得
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;
  }
}
类MyHomePage扩展StatefulWidget{
MyHomePage({Key}):超级(Key:Key);
@凌驾
_MyHomePageState createState()=>\u MyHomePageState();
}
类_MyHomePageState扩展状态{
@凌驾
小部件构建(构建上下文){
返回堆栈(
儿童:[
用于(图像中的最终图像)
定制油漆(
画家:(图),
),
],
);
}
}
类_MyPainter扩展自定义painter{
最终用户界面。图像;
_我的画师(这张图);
@凌驾
虚空绘制(ui.Canvas画布,ui.Size){
//油漆
}
@凌驾
布尔应该重新绘制(协变的_mypainteroldDelegate){
返回oldDelegate.image!=this.image;
}
}

请注意,您可以随时在主页中调用
setState
,因为这很便宜。(如果你的主页有很多子部件,那么可能不好,那么你需要状态管理解决方案)。但是,除非
应该重画,否则画家不会重画。是啊

谢谢-我计划将图像数组传递给CustomPainter(这样所有图像都可以在同一画布上绘制)。因此,在本例中,我也可以这样认为,但是“shouldRepaint”函数在迭代图像(假设它们可能是具有唯一图像名称的映射,然后是ui.image)和确定是否有更改方面必须更聪明一些。然后,如果有任何更改,画布需要重新绘制,对吗?我正在考虑使用Bloc,但是希望使用Bloc存储应用程序的状态,这将指示要加载的图像。也许是一个单独的问题(?),但目标是在这个用例中使用Bloc,它将如何工作。您可以使用Bloc“监视”更改,但在类别更改之后,您必须以异步方式加载图像。。。加载图像后是否需要触发任何“setState”等价项?@Greg about bloc+async,搜索该关键字会得到:@Greg如果你将数组传递给一个画师,那么如果你为shouldRepaint返回true,它会重新绘制所有内容。@Greg记住,在flift中,你经常使用很多小部件。所以也许把你的画分成我上面展示的部分