ver1.00.00

update
This commit is contained in:
spasolreisa
2026-04-21 00:28:41 +08:00
parent b985cd1f9e
commit f5f62c828d
13 changed files with 1496 additions and 175 deletions

194
lib/tool/cacheImage.dart Normal file
View File

@@ -0,0 +1,194 @@
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();
},
);
}
}