内容如题,
使用场景一:(图片不发生变化)
适用于一个widget里的图不会发生变化,但界面其他可能发生setstate刷新。可能导致这个不会发生变化的图也一直在那刷新。
dart
类使用:
String imgPath = 'xxx';
String iconPath = 'yyy';
SizedBox(
width: 160,
height: 90,
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: AlarmSnapshotImage(
key: ValueKey(filePath),
imageUrl: imgPath,
iconPath: iconPath,
),
),
),
),
功能类:
class AlarmSnapshotImage extends StatefulWidget {
final String? imageUrl;
final String? iconPath;
const AlarmSnapshotImage({
super.key,
required this.imageUrl,
this.iconPath,
});
@override
State<AlarmSnapshotImage> createState() => AlarmSnapshotImageState();
}
class AlarmSnapshotImageState extends State<AlarmSnapshotImage> {
ImageProvider? _imageProvider;
bool _loadFailed = false;
@override
void initState() {
super.initState();
_loadImage();
}
@override
void didUpdateWidget(AlarmSnapshotImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.imageUrl != widget.imageUrl) {
_loadImage();
}
}
void _loadImage() {
_loadFailed = false;
final imageUrl = widget.imageUrl;
if (imageUrl == null || imageUrl.isEmpty) {
_imageProvider = null;
return;
}
final file = File(imageUrl);
_imageProvider = file.existsSync() ? FileImage(file) : null;
}
Widget _placeholder() {
return Container(
width: 160,
height: 90,
color: Colors.grey[800],
child: const Center(
child: Icon(Icons.image, color: Colors.white, size: 40),
),
);
}
Widget _offlinePlaceholder() {
return Container(
width: 160,
height: 90,
color: Colors.grey[800],
child: Center(
child: Image.asset(
widget.iconPath ?? '',
width: 30,
height: 30,
color: Constants.ios_white.withAlpha(150),
),
),
);
}
@override
Widget build(BuildContext context) {
final imageUrl = widget.imageUrl;
if (imageUrl == null || imageUrl.isEmpty) {
return _placeholder();
}
if (imageUrl == 'offline') {
return _offlinePlaceholder();
}
if (_loadFailed || _imageProvider == null) {
return _placeholder();
}
return SizedBox(
width: 160,
height: 90,
child: Image(
image: _imageProvider!,
fit: BoxFit.cover,
gaplessPlayback: true,
errorBuilder: (context, error, stackTrace) {
print('Error loading image($imageUrl): $error');
if (!_loadFailed && mounted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) setState(() => _loadFailed = true);
});
}
return _placeholder();
},
),
);
}
}
使用场景二:(图片发生变化,但路径名是一样的)
适用于一个widget里的图会发生变化,然而路径是一致,这种情况有两种解决方案,一种是周期性的刷新图片,另一种是图片更新时发出notify,从而刷新。这里介绍周期性刷新的基础做法,当然两者结合是更好(更新时发出notify,发现图片发生变化时更新)。
dart
类使用:
StatefulBuilder(builder: (context, setState) {
camSnapshotStatesetter = setState;
return Container(
width: cardWidth,
height: imageHeight,
decoration: BoxDecoration(
color: Colors.grey[850],
),
child: camera.snapshot != null && camera.snapshot!.isNotEmpty
? CameraImageRefresher(
imagePath: camera.snapshot!,
width: cardWidth,
height: imageHeight
)
: Center(
child: Icon(
Icons.photo_size_select_actual_rounded,
color: Colors.white70,
size: 40
)
),
);
}),
功能类:
class CameraImageRefresher extends StatefulWidget {
final String imagePath;
final double width;
final double height;
const CameraImageRefresher({
Key? key,
required this.imagePath,
required this.width,
required this.height,
}) : super(key: key);
@override
State<CameraImageRefresher> createState() => _CameraImageRefresherState();
}
class _CameraImageRefresherState extends State<CameraImageRefresher> {
ImageProvider? _imageProvider;
Timer? _timer;
bool _isLoading = false;
DateTime? _lastModified;
int? _lastFileSize;
@override
void initState() {
super.initState();
_initImageLoader();
_startRefreshTimer();
}
Future<void> _initImageLoader() async {
if (!mounted) return;
setState(() => _isLoading = true);
try {
await _loadImage();
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
@override
void didUpdateWidget(CameraImageRefresher oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.imagePath != widget.imagePath || _hasImageFileChanged()) {
_loadImage();
}
}
bool _hasImageFileChanged() {
final file = File(widget.imagePath);
if (!file.existsSync()) {
return _lastModified != null || _lastFileSize != null;
}
try {
final stat = file.statSync();
return _lastModified != stat.modified || _lastFileSize != stat.size;
} catch (_) {
return true;
}
}
Future<void> _loadImage() async {
final file = File(widget.imagePath);
if (!file.existsSync()) {
if (mounted) setState(() => _imageProvider = null);
return;
}
await _evictCache(file);
if (!mounted) return;
try {
final bytes = await file.readAsBytes();
final stat = await file.stat();
if (!mounted) return;
setState(() {
_lastModified = stat.modified;
_lastFileSize = stat.size;
_imageProvider = MemoryImage(bytes)
..resolve(ImageConfiguration.empty)
.addListener(ImageStreamListener((_, __) {
if (mounted) setState(() {});
}));
});
} catch (e) {
debugPrint('Image load error: $e');
if (mounted) {
setState(() {
_imageProvider = null;
_lastModified = null;
_lastFileSize = null;
});
}
}
}
Future<void> _evictCache(File file) async {
try {
final oldProvider = FileImage(file);
await oldProvider.evict();
imageCache.clearLiveImages();
} catch (e) {
debugPrint('Cache eviction error: $e');
}
}
void _startRefreshTimer() {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 30), (_) {
// print('0000---> timer refresh Timer running');
if (mounted && _hasImageFileChanged()) _loadImage();
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
return _imageProvider != null
? Image(
key: ValueKey(widget.imagePath),
image: _imageProvider!,
width: widget.width,
height: widget.height,
fit: BoxFit.cover,
frameBuilder: (_, child, frame, __) {
return AnimatedOpacity(
opacity: frame == null ? 0 : 1,
duration: const Duration(milliseconds: 500),
child: child,
);
},
errorBuilder: (_, __, ___) => _buildErrorWidget(),
)
: _buildErrorWidget();
}
Widget _buildErrorWidget() {
return SizedBox(
width: widget.width,
height: widget.height,
child: Center(
child: Icon(
Icons.photo_size_select_actual_rounded,
color: Colors.white70, size: 40
)
),
);
}
}