Files
UnionApp/lib/tool/cacheImage.dart
spasolreisa f5f62c828d ver1.00.00
update
2026-04-21 00:28:41 +08:00

194 lines
6.0 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:crypto/crypto.dart';
import 'package:path/path.dart' as p;
/// 一个支持自动 Dio 缓存的 Image 组件
/// 用法完全对齐 Image.network(url)
class CacheImage extends StatefulWidget {
final String url;
final double? width;
final double? height;
final BoxFit? fit;
final double scale;
final AlignmentGeometry alignment;
final ImageRepeat repeat;
final FilterQuality filterQuality;
final Color? color;
final BlendMode? colorBlendMode;
final ImageLoadingBuilder? loadingBuilder;
final ImageErrorWidgetBuilder? errorBuilder;
final bool matchTextDirection;
// 使用位置参数接收 url其余为可选命名参数
const CacheImage.network(
this.url, {
Key? key,
this.width,
this.height,
this.fit,
this.scale = 1.0,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.filterQuality = FilterQuality.low,
this.color,
this.colorBlendMode,
this.loadingBuilder,
this.errorBuilder,
this.matchTextDirection = false,
}) : super(key: key);
@override
State<CacheImage> createState() => _CacheImageState();
}
class _CacheImageState extends State<CacheImage> {
File? _localFile;
bool _isInit = false;
/// 统一日志工具(支持发布/调试环境)
void _log(String message, {bool isError = false}) {
// 发布环境只打印错误日志,调试环境全部打印
if (kReleaseMode) {
if (isError) {
print('[CacheImage-ERROR] $message');
}
} else {
final tag = isError ? '[CacheImage-ERROR]' : '[CacheImage-INFO]';
print('$tag $message');
}
}
@override
void initState() {
super.initState();
_log('图片组件初始化URL: ${widget.url}');
_handleCache();
}
// 当外部传入的 url 发生变化时,重新触发缓存逻辑
@override
void didUpdateWidget(CacheImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.url != widget.url) {
_log('URL 发生变化,重新加载缓存\n旧URL: ${oldWidget.url}\n新URL: ${widget.url}');
_handleCache();
}
}
Future<void> _handleCache() async {
try {
_log('开始处理图片缓存逻辑URL: ${widget.url}');
// 1. 生成唯一文件名 (MD5)
final String fileName = md5.convert(utf8.encode(widget.url)).toString();
_log('生成文件MD5: $fileName');
// 获取临时目录 (建议缓存放在临时目录,由系统根据空间决定是否清理)
final Directory cacheDir = await getTemporaryDirectory();
final File file = File(p.join(cacheDir.path, 'image_cache', fileName));
_log('本地缓存路径: ${file.path}');
// 2. 检查本地是否存在
if (await file.exists()) {
_log('✅ 本地缓存已存在,直接加载本地文件');
if (mounted) {
setState(() {
_localFile = file;
_isInit = true;
});
}
return;
}
// 3. 本地不存在,使用 Dio 下载
_log(' 本地无缓存,开始从网络下载图片');
final dio = Dio();
final response = await dio.get(
widget.url,
options: Options(responseType: ResponseType.bytes),
);
_log('✅ 图片下载完成,数据大小: ${response.data.length} bytes');
// 4. 尝试保存到本地 (Quiet Mode: 失败仅打印,不抛出异常)
try {
await file.parent.create(recursive: true);
await file.writeAsBytes(response.data);
_log('✅ 图片成功保存到本地缓存');
if (mounted) {
setState(() {
_localFile = file;
});
}
} catch (e) {
_log('❌ 本地保存失败 (不影响显示): $e', isError: true);
}
} catch (e) {
_log('❌ 下载或逻辑处理失败: $e', isError: true);
} finally {
if (mounted) {
setState(() => _isInit = true);
}
_log('🏁 图片缓存逻辑执行完成');
}
}
@override
Widget build(BuildContext context) {
// 如果本地文件已就绪,直接加载本地文件
if (_localFile != null) {
_log('使用本地缓存图片渲染');
return Image.file(
_localFile!,
key: widget.key,
width: widget.width,
height: widget.height,
fit: widget.fit,
alignment: widget.alignment,
repeat: widget.repeat,
filterQuality: widget.filterQuality,
color: widget.color,
colorBlendMode: widget.colorBlendMode,
matchTextDirection: widget.matchTextDirection,
// 如果文件读取过程中出错,尝试回退到网络或报错组件
errorBuilder: widget.errorBuilder ?? (context, error, stack) {
_log('❌ 本地图片加载失败,回退到网络加载', isError: true);
return _buildNetworkImage();
},
);
}
// 默认或下载中,渲染网络图片
_log('使用网络图片渲染(加载中/无缓存)');
return _buildNetworkImage();
}
Widget _buildNetworkImage() {
return Image.network(
widget.url,
key: widget.key,
width: widget.width,
height: widget.height,
fit: widget.fit,
scale: widget.scale,
alignment: widget.alignment,
repeat: widget.repeat,
filterQuality: widget.filterQuality,
color: widget.color,
colorBlendMode: widget.colorBlendMode,
matchTextDirection: widget.matchTextDirection,
loadingBuilder: widget.loadingBuilder,
errorBuilder: (context, error, stack) {
_log('❌ 网络图片加载失败: $error', isError: true);
if (widget.errorBuilder != null) {
return widget.errorBuilder!(context, error, stack);
}
return const SizedBox();
},
);
}
}