在 Flutter 中,通常使用 Image.file 来从文件加载一个图片。下面一个小 Demo:
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_utils/dialog_tool.dart';
class DDD extends StatefulWidget {
@override
_DDDState createState() => _DDDState();
}
class _DDDState extends State<DDD> {
List<CameraDescription> cameras;
CameraController controller;
Future<void> initCam() async {
cameras = await availableCameras();
controller = CameraController(cameras[1], ResolutionPreset.ultraHigh,
enableAudio: false);
await controller.initialize();
setState(() {});
}
@override
void initState() {
initCam();
super.initState();
}
File file;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SizedBox(width: 400, child: buildCamera(controller)),
file == null
? const SizedBox()
: SizedBox(width: 300, child: Image.file(file)),
RaisedButton(
onPressed: () async {
showLoadingDialog(context);
final ff = File('/sdcard/temp2.jpg');
if (ff.existsSync()) {
await ff.delete();
}
await controller.takePicture(ff.path);
file = ff;
Navigator.pop(context);
setState(() {});
},
)
],
),
);
}
Widget buildCamera(CameraController controller) {
if (controller == null) return const SizedBox();
if (!controller.value.isInitialized) {
return Container();
}
return RotatedBox(
quarterTurns: 1,
child: AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
),
);
}
}

在这个demo中,左侧是一个前置摄像机预览,中间是一个 Image Widget,用于加载摄像机拍照所得的文件,右侧是一个按钮,用于出发摄像机拍照。
当点击右侧按钮时,对前置摄像机进行拍照,中间 Image Widget 会加载照片文件。
在第一次拍照加载时没事没有问题的,但当第二次点击这个右侧按钮,中间的 Image Widget 并不会更新。这是由于 Image Widget 更新机制的所造成的问题。
@override
void didUpdateWidget(Image oldWidget) {
super.didUpdateWidget(oldWidget);
if (_isListeningToStream &&
(widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
final ImageStreamListener oldListener = _getListener();
_imageStream!.addListener(_getListener(recreateListener: true));
_imageStream!.removeListener(oldListener);
}
if (widget.image != oldWidget.image)
_resolveImage();
}
if (widget.image != oldWidget.image)
这行代码用于决定是否更新 Image Widget。由于此处时 FileImage
所以我们再看一下 FileImage 的内部实现,时重写了 == 操作符。
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is FileImage
&& other.file.path == file.path
&& other.scale == scale;
}
所以在对 FileImage 进行 == 操作时,只会验证 类型、文件路径、scale。所以在我们这个demo中,由于拍照会写入到同一个文件(/sdcard/temp2.jpg
),即使图片不一样也不会出发更新操作。
这时候你可以选择更改文件存储路径:
RaisedButton(
onPressed: () async {
showLoadingDialog(context);
final ff = File('/sdcard/temp2${DateTime.now().millisecondsSinceEpoch}.jpg');
if (ff.existsSync()) {
await ff.delete();
}
await controller.takePicture(ff.path);
file = ff;
Navigator.pop(context);
setState(() {});
},
)
这样我们就可以愉快的更新图片了😝。
但是当你不想更改存储路径时,这该怎么办?
这时候,我们可以绕过 Image.file,使用 Image.memory 来解决图片不更新:
// ...
file == null ? const SizedBox() : SizedBox(width: 300, child: Image.memory(file.readAsBytesSync())),
// ...
(不规范写法,请勿模仿)
除了这两种方法我们还可以通过清楚 ImageCache 同时更新 Image.file:
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SizedBox(width: 400, child: buildCamera(controller)),
file == null
? const SizedBox()
: SizedBox(
width: 300, child: Image.file(file, key: ValueKey(DateTime.now()),)),
RaisedButton(
onPressed: () async {
showLoadingDialog(context);
final ff = File('/sdcard/temp2.jpg');
if (ff.existsSync()) await ff.delete();
await controller.takePicture(ff.path);
file = ff;
imageCache.clear();
imageCache.clearLiveImages();
Navigator.pop(context);
setState(() {});
},
)
],
),
);
}
清除 ImageCache 还有另一种方式,是调用 FlieImage 的 evict 方法:
Future<bool> evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async {
cache ??= imageCache;
final T key = await obtainKey(configuration);
return cache!.evict(key);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
SizedBox(width: 400, child: buildCamera(controller)),
file == null
? const SizedBox()
: SizedBox(
width: 300, child: Image(image: file, key: ValueKey(DateTime.now()),)),
RaisedButton(
onPressed: () async {
showLoadingDialog(context);
final ff = File('/sdcard/temp2.jpg');
if (ff.existsSync()) await ff.delete();
await controller.takePicture(ff.path);
await file?.evict();
file = FileImage(ff);
Navigator.pop(context);
setState(() {});
},
)
],
),
);
}
参考:
https://github.com/flutter/flutter/issues/17419
Comments | NOTHING