在StatefulWidget&中的项目数组作为状态确保"setState"只触发已更改数组中项目的更新? [英] array of items as state in a StatefulWidget & ensuring "setState" only triggers updates for items in the array that has changed?

查看:40
本文介绍了在StatefulWidget&中的项目数组作为状态确保"setState"只触发已更改数组中项目的更新?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景-希望为StatefulWidget使用项目的动态列表.在我的用例中,小部件将调用CustomePainter(画布),因此有时会在画布上绘制不同数量的图像,因此父级StatefulWidget内希望有一个图像数组".

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.

(也许这里有两个单独的答案,一个是具有(a)标准小部件的数组,另一个是将(b)项传递到CustomePainter以便在画布上绘画的情况?)

作为示例,请参见下面的代码(@ ch271828n在一个单独的问题上为我提供了帮助-).该代码突出了主要思想,但不包括传递给CustomPainter作为参数.

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'),
      ),
    );
  }
}

推荐答案

首先,讨论 build s:实际上,您需要一种状态管理解决方案.也许看看 https://flutter.dev/docs/development/data-and-backend/state-mgmt/options .我个人建议使用 MobX ,它需要很少的样板,并且可以使开发更快.

Firstly, talking about builds: 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.

使用 setState 并不是一个好主意.当查看源代码时, setState 只会执行以下操作:

Using setState is not a good idea. The setState, when looking into source code, does nothing but:

  void setState(VoidCallback fn) {
    assert(...);
    _element.markNeedsBuild();
  }

因此,它不过是 markNeedsBuild -整个有状态的小部件再次称为 build .传递给setState的回调没有什么特别的.因此,这种方式无法触发部分重建.

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.

第二,您只想减少重绘,而不是减少生成次数,因为颤动的目的是可以轻松以60fps的速度调用 build .然后事情变得简单了:

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;
  }
}

请注意,您可以随时在主页中调用 setState ,因为这样做很便宜.(如果您的主页上有很多子窗口小部件,则可能效果不佳,那么您需要状态管理解决方案).但是除非 shouldRepaint 这样说,否则画家将重新绘画.是的!

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!

这篇关于在StatefulWidget&amp;中的项目数组作为状态确保"setState"只触发已更改数组中项目的更新?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆