解码图像并添加到小部件列表 [flutter]
Decode image and add to list of widgets [flutter]
我想从 Windows 中的一个文件夹中加载和显示多个图像,根据它们的大小(例如,大、中、图标等...)对它们进行分组。
为了在显示它们之前知道它们的大小,我使用 'package:image/image.dart'
中的 decodeImage
函数为每个图像循环列表。
我想要实现的是每次迭代后图像出现在屏幕上,而不会阻塞 UI 直到所有加载过程完成。
我有一个图像模型列表,一旦我将每个图像添加到列表中,我就会调用 setState
以更新视图,但所有图像都出现在循环的末尾。
我该如何解决?
这是调试控制台的输出(显示所有图像大约需要 10 秒):
C:\flutter\flutter-desktop-embedding\example>flutter run
Launching lib\main.dart on Windows in debug mode...
Building Windows application...
flutter: 2020-01-12 16:00:12.604871
flutter: 2020-01-12 16:00:13.160340
flutter: 2020-01-12 16:00:13.656014
Syncing files to device Windows...
flutter: 2020-01-12 16:00:14.050997
flutter: 2020-01-12 16:00:14.561593
flutter: 2020-01-12 16:00:15.040311
flutter: 2020-01-12 16:00:15.502076
flutter: 2020-01-12 16:00:15.936912
flutter: 2020-01-12 16:00:16.404174
flutter: 2020-01-12 16:00:16.919285
flutter: 2020-01-12 16:00:17.481826
flutter: 2020-01-12 16:00:17.941551
flutter: 2020-01-12 16:00:18.400322
flutter: 2020-01-12 16:00:18.864382
flutter: 2020-01-12 16:00:19.297922
flutter: 2020-01-12 16:00:19.745731
flutter: 2020-01-12 16:00:20.218457
flutter: 2020-01-12 16:00:20.654293
flutter: 2020-01-12 16:00:21.120047
flutter: 2020-01-12 16:00:21.629715
Syncing files to device Windows... 13.892ms (!)
这是代码(这里我使用了一张 1920x1080 的 jpg 图片):
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as dart_image;
void main() {
// See https://github.com/flutter/flutter/wiki/Desktop-shells#target-platform-override
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
runApp(new MyApp());
}
class ImageModel {
final File file;
final double height;
final double width;
ImageModel(this.file, {this.width, this.height});
}
class MyApp extends StatefulWidget {
MyApp();
@override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
List<ImageModel> imageModelList = [];
@override
void initState() {
super.initState();
imageModelList = [];
_loadImages();
}
void _loadImages() {
final imagePaths = List.filled(20, 'C:/flutter/test/elephant.jpg');
for (final imagePath in imagePaths) {
final file = File(imagePath);
file.readAsBytes().then((bytes) {
final image = dart_image.decodeImage(bytes);
// Here i should read the size to group the images based on their size.
// For now i just add images with 1920x1080 size.
if (image != null && image.width == 1920 && image.height == 1080) {
final width = image.width.toDouble();
final height = image.height.toDouble();
final imageModel = ImageModel(file, width: width, height: height);
print(DateTime.now());
setState(() => imageModelList.add(imageModel));
}
}).catchError((err) {});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: SingleChildScrollView(
child: Wrap(
children: imageModelList.map((imageModel) {
return new Container(
child: Image.file(
imageModel.file,
width: 160,
filterQuality: FilterQuality.medium,
),
);
}).toList(),
),
),
),
title: 'title',
);
}
}
发生这种情况是因为您在主隔离上一次调用 dart_image.decodeImage(bytes)
20 次,这将使分配给主隔离的 CPU 核心 100% 忙于解码图像,直到它们全部完成。因此,尽管正确调用 setState
,但 CPU 太忙了,无法在将布局传递给 GPU 进行绘制之前计算布局,因此您的应用程序完全冻结。
为防止这种情况发生,您需要将艰苦的工作委托给其他分离株。 Flutter 提供 compute
函数,它将生成一个新的 isolate,运行 使用提供的参数在其中提供的函数,以及 return 带有结果的 Future。对于您的用例,它看起来像这样:
dart_image.Image image = await compute<List<int>, dart_image.Image>(dart_image.decodeImage, byteArray);
这是一篇包含非常相似示例的文章:https://www.gladimdim.org/flutter-unblocking-ui-thread-with-isolates-compute-function-ck03qrwnj000peks1zvhvko1x
如果您的应用会经常这样做,您可能希望在应用启动时创建一个持久隔离池 - 理想情况下,隔离的数量与 合乎逻辑的 CPU 核(-1 表示主隔离)- 然后使用这些隔离来解码图像而不是 compute
函数。这将显着提高应用程序的性能,因为在创建新隔离时会产生开销(在数百毫秒的范围内,具体取决于设备性能),而在隔离之间传递数据的开销仅为几十毫秒(同样,取决于设备性能和传递的数据量)。
请注意,如果您过度使用 compute
,某些平台甚至可能会出现异常行为。自从我上次尝试以来,Flutter 可能已经改进了这一点,但是 iOS 应用程序经常收到来自 compute
的结果,但在执行几个 compute
函数时延迟几秒或根本没有立刻。当我用持久隔离物替换 compute
时,这个问题完全消失了。
我想从 Windows 中的一个文件夹中加载和显示多个图像,根据它们的大小(例如,大、中、图标等...)对它们进行分组。
为了在显示它们之前知道它们的大小,我使用 'package:image/image.dart'
中的 decodeImage
函数为每个图像循环列表。
我想要实现的是每次迭代后图像出现在屏幕上,而不会阻塞 UI 直到所有加载过程完成。
我有一个图像模型列表,一旦我将每个图像添加到列表中,我就会调用 setState
以更新视图,但所有图像都出现在循环的末尾。
我该如何解决?
这是调试控制台的输出(显示所有图像大约需要 10 秒):
C:\flutter\flutter-desktop-embedding\example>flutter run
Launching lib\main.dart on Windows in debug mode...
Building Windows application...
flutter: 2020-01-12 16:00:12.604871
flutter: 2020-01-12 16:00:13.160340
flutter: 2020-01-12 16:00:13.656014
Syncing files to device Windows...
flutter: 2020-01-12 16:00:14.050997
flutter: 2020-01-12 16:00:14.561593
flutter: 2020-01-12 16:00:15.040311
flutter: 2020-01-12 16:00:15.502076
flutter: 2020-01-12 16:00:15.936912
flutter: 2020-01-12 16:00:16.404174
flutter: 2020-01-12 16:00:16.919285
flutter: 2020-01-12 16:00:17.481826
flutter: 2020-01-12 16:00:17.941551
flutter: 2020-01-12 16:00:18.400322
flutter: 2020-01-12 16:00:18.864382
flutter: 2020-01-12 16:00:19.297922
flutter: 2020-01-12 16:00:19.745731
flutter: 2020-01-12 16:00:20.218457
flutter: 2020-01-12 16:00:20.654293
flutter: 2020-01-12 16:00:21.120047
flutter: 2020-01-12 16:00:21.629715
Syncing files to device Windows... 13.892ms (!)
这是代码(这里我使用了一张 1920x1080 的 jpg 图片):
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as dart_image;
void main() {
// See https://github.com/flutter/flutter/wiki/Desktop-shells#target-platform-override
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
runApp(new MyApp());
}
class ImageModel {
final File file;
final double height;
final double width;
ImageModel(this.file, {this.width, this.height});
}
class MyApp extends StatefulWidget {
MyApp();
@override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
List<ImageModel> imageModelList = [];
@override
void initState() {
super.initState();
imageModelList = [];
_loadImages();
}
void _loadImages() {
final imagePaths = List.filled(20, 'C:/flutter/test/elephant.jpg');
for (final imagePath in imagePaths) {
final file = File(imagePath);
file.readAsBytes().then((bytes) {
final image = dart_image.decodeImage(bytes);
// Here i should read the size to group the images based on their size.
// For now i just add images with 1920x1080 size.
if (image != null && image.width == 1920 && image.height == 1080) {
final width = image.width.toDouble();
final height = image.height.toDouble();
final imageModel = ImageModel(file, width: width, height: height);
print(DateTime.now());
setState(() => imageModelList.add(imageModel));
}
}).catchError((err) {});
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: SingleChildScrollView(
child: Wrap(
children: imageModelList.map((imageModel) {
return new Container(
child: Image.file(
imageModel.file,
width: 160,
filterQuality: FilterQuality.medium,
),
);
}).toList(),
),
),
),
title: 'title',
);
}
}
发生这种情况是因为您在主隔离上一次调用 dart_image.decodeImage(bytes)
20 次,这将使分配给主隔离的 CPU 核心 100% 忙于解码图像,直到它们全部完成。因此,尽管正确调用 setState
,但 CPU 太忙了,无法在将布局传递给 GPU 进行绘制之前计算布局,因此您的应用程序完全冻结。
为防止这种情况发生,您需要将艰苦的工作委托给其他分离株。 Flutter 提供 compute
函数,它将生成一个新的 isolate,运行 使用提供的参数在其中提供的函数,以及 return 带有结果的 Future。对于您的用例,它看起来像这样:
dart_image.Image image = await compute<List<int>, dart_image.Image>(dart_image.decodeImage, byteArray);
这是一篇包含非常相似示例的文章:https://www.gladimdim.org/flutter-unblocking-ui-thread-with-isolates-compute-function-ck03qrwnj000peks1zvhvko1x
如果您的应用会经常这样做,您可能希望在应用启动时创建一个持久隔离池 - 理想情况下,隔离的数量与 合乎逻辑的 CPU 核(-1 表示主隔离)- 然后使用这些隔离来解码图像而不是 compute
函数。这将显着提高应用程序的性能,因为在创建新隔离时会产生开销(在数百毫秒的范围内,具体取决于设备性能),而在隔离之间传递数据的开销仅为几十毫秒(同样,取决于设备性能和传递的数据量)。
请注意,如果您过度使用 compute
,某些平台甚至可能会出现异常行为。自从我上次尝试以来,Flutter 可能已经改进了这一点,但是 iOS 应用程序经常收到来自 compute
的结果,但在执行几个 compute
函数时延迟几秒或根本没有立刻。当我用持久隔离物替换 compute
时,这个问题完全消失了。