介绍
增强版的 image 组件,可以显示本地\assest\base64\网络图片,支持图片缓存、加载中提示、加载失败提示。夜间模式下增加图片蒙版
- 本地\assest\base64采用原生的Image组件显示 因为网络图片涉及到图片缓存这里使用到了三方插件CachedNetworkImage
-
夜间模式下增加图片蒙版使用ShaderMask 组件给图片上覆盖一层颜色达到效果
代码演示
基础用法
基础用法与原生Image组件一致,可以设置高度,宽度,图片混合颜色,图片拉伸样式,可以设置圆角矩形角度
//展示网络图片资源
HcImage(
imagePath:'https://rs-channel.huanqiucdn.cn/imageDir/8dc52be8f380054d30ea8071e728d19au5.jpg'),
//展示assest图片资源
HcImage(
imagePath:'assets/images/group.png'),
//展示本机图片资源(需要申请权限)
HcImage(
imagePath:'/storage/emulated/0/Android/data/group.png'),
//展示base64图片资源(带不带头均可)
HcImage(
imagePath:'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='),
圆形图片
通过.circle构造可以设置图片变圆,必选参数为size 和图片路径
HcImage.circle(
imagePath:
'https://rs-channel.huanqiucdn.cn/imageDir/8dc52be8f380054d30ea8071e728d19au5.jpg',
size: 120.0,
),
图片加载中
HcImage提供了一个默认的加载中的组件,如需自定义可以传入loadingBuilder
HcImage(
loadingBuilder: (context, url, progress) =>
CircularProgressIndicator(
value: progress.progress,),
imagePath:'https://rs-channel.huanqiucdn.cn/imageDir/8dc52be8f380054d30ea8071e728d19au5.jpg'),
图片加载失败
HcImage组件提供了默认的加载失败提示,支持通过errorBuilder传入自定义组件
本地图片加载出错会返回堆栈信息 网络图片出错会返回网址
HcImage(
loadingBuilder: (context, url, progress) =>
CircularProgressIndicator(
value: progress.progress,
),
errorBuilder: (context,error,url)=>Text("图片出错了"),
imagePath:
'https://rs-channel.huanqiucdn.cn/imageDir/8dc52be8f380054d30ea8071e728d19au5.jpg'),
API
props
参数 | 说明 | 类型 | 默认值 | 是否必填 |
---|---|---|---|---|
imagePath | 图片地址(本地\assets\网络\base64) | String | - | true |
errorBuilder | 图片出错占位符 | Function | - | false |
loadingBuilder | 图片加载占位符 | Function | - | false |
httpHeader | 请求图片的标题头 | Map<String, String> | - | false |
height | 图片高度 | double | - | false |
width | 图片的宽度 | double | - | false |
fit | 图片填充模式,具体查看BoxFit | BoxFit | BoxFit.fill | false |
filterQuality | 图片质量 | FilterQuality | FilterQuality.low | false |
color | 图片混合颜色 | Color | - | false |
colorBlendMode | 图片混色模式,具体查看BlendMode | BlendMode | - | false |
alignment | 图片对齐方式 | Alignment | Alignment.center | false |
repeat | 盒子大于图片时的图片重复方式 | ImageRepeat | ImageRepeat.noRepeat | false |
borderRadius | 图片的圆角大小 | BorderRadius | BorderRadius.zero | false |
Function
方法名 | 说明 | 参数 | 返回类型 |
---|---|---|---|
errorBuilder | 图片出错占位符 | (BuildContext context,dynamic error, dynamic url) | Widget |
loadingBuilder | 图片加载占位符 | ( BuildContext context, String url,DownloadProgress progress) | Widget |
项目源码
///图片加载出错的构建函数
/// 本地图片构建出错返回堆栈信息 网络图片返回网络路径
typedef ImageErrorBuilder = Widget Function(
BuildContext context,
dynamic error,
dynamic url,
);
class HcImage extends StatelessWidget {
//图片地址
final String imagePath;
//图片出错的占位符
final ImageErrorBuilder? errorBuilder;
//加载中的占位符
final ProgressIndicatorBuilder? loadingBuilder;
//请求图片的标题头
final Map<String, String>? httpHeader;
///高度
final double? height;
///宽度
final double? width;
///图片的 Fit
final BoxFit? fit;
///图片质量
final FilterQuality? filterQuality;
//图片混合颜色
final Color? color;
/// 混合模式
final BlendMode? colorBlendMode;
/// 对齐方式 默认居中
final Alignment alignment;
//是否重复
final ImageRepeat repeat;
final ThemeMode themeMode;
//图片的圆角
final BorderRadius? borderRadius;
const HcImage({
super.key,
required this.imagePath,
this.errorBuilder,
this.loadingBuilder,
this.httpHeader,
this.height,
this.width,
this.fit,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.filterQuality = FilterQuality.low,
this.color,
this.themeMode=ThemeMode.dark,
this.colorBlendMode,
this.borderRadius,
});
HcImage.circle({
super.key,
required this.imagePath,
this.errorBuilder,
this.loadingBuilder,
this.httpHeader,
required double size,
this.filterQuality,
this.color,
this.themeMode=ThemeMode.dark,
this.colorBlendMode,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
}) : width = size,
height = size,
borderRadius = BorderRadius.circular(size / 2),
fit = BoxFit.cover;
@override
Widget build(BuildContext context) {
// 获取图片的信息
Map<String, dynamic> imageInfo = _checkAndFixImagePath(imagePath);
//图片的照片属性
HcImageType type = imageInfo['type'];
//图片的路径
String imageUrl = imageInfo['url'];
//本地图片展示的Provider;
ImageProvider? imageProvider;
switch (type) {
case HcImageType.assets:
imageProvider = AssetImage(imageUrl);
break;
case HcImageType.file:
imageProvider = FileImage(File(imageUrl));
break;
case HcImageType.base64:
Uint8List bytes = const Base64Decoder().convert(imageUrl);
imageProvider = MemoryImage(bytes);
break;
case HcImageType.network:
imageProvider = null;
break;
}
//夜间模式增加遮罩 日间模式不增加
Color maskColor = themeMode == ThemeMode.dark
? Colors.grey
: Colors.white;
return ClipRRect(
borderRadius:
borderRadius ?? BorderRadius.circular(size / 2) ,
child: ShaderMask(
shaderCallback: (Rect bounds) {
return LinearGradient(colors: [maskColor, maskColor])
.createShader(bounds);
},
child: imageProvider != null
? Image(
image: imageProvider,
width: width,
height: height,
fit: fit,
color: color,
alignment: alignment,
repeat: repeat,
colorBlendMode: colorBlendMode,
filterQuality: filterQuality ??FilterQuality.low,
errorBuilder: (context, error, stackTrace) =>
_imageErrorBuilder(context, error, stackTrace),
)
: CachedNetworkImage(
imageUrl: imageUrl,
width: width,
height: height,
httpHeaders:httpHeader,
fit: fit,
color: color,
alignment: alignment,
repeat: repeat,
colorBlendMode: colorBlendMode,
errorWidget: (context, url, error) =>
_imageErrorBuilder(context, error, url),
progressIndicatorBuilder: (context, url, progress) =>
_imageLoadingBuilder(context, url, progress),
),
),
);
}
///图片加载出错的方法
Widget _imageErrorBuilder(BuildContext context, dynamic error, dynamic url) {
if (errorBuilder != null) {
return errorBuilder!(context, error, url);
}
return Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.zero,
),
padding: const EdgeInsets.all(20),
child: const Center(
child: Icon(Icons.error_outline,
size: 32)));
}
///图片加载中的方法
Widget _imageLoadingBuilder(context, url, progress) {
if (loadingBuilder != null) {
return loadingBuilder!(context, url, progress);
}
return Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.zero,
),
padding: const EdgeInsets.all(20),
child: const CircularProgressIndicator(),
);
}
}
判断图片来源的方法
///图片的类型
/// assets资源文件
/// file 本机文件
/// base64图片
/// network资源
enum HcImageType { assets, file, base64, network }
/// 检测图片类型并格式化路径
static Map<String, dynamic> checkAndFixImagePath(String url) {
Map<String, dynamic> result = {};
// /开头可能是本地资源也可能是网络资源的 相对地址
// 本地资源路径规则
// Android: /data/user 或/storage/emulated/0/Android..
// ios /var/mobile/...
if (url.startsWith('/')) {
File file = File(url);
//检测文件是否存在
bool isExists = file.existsSync();
if (isExists) {
result['type'] = HcImageType.file;
result['url'] = url;
return result;
}
}
//如果是网络或者带/非本地资源
if (url.toLowerCase().startsWith('http') || url.startsWith('/')) {
result['type'] = HcImageType.network;
result['url'] = url;
} else if (!url.contains('.') && checkBase64(url)) {
//如果是base64获取去头后的路径
result['type'] = HcImageType.base64;
List<String> base64SplitList = url.split(",");
result['url'] = base64SplitList[base64SplitList.length - 1];
} else {
//String字符串不是本地图片也不是网络图片也不是base64 开头没有带/ 所以是assets资源
result['type'] = HcImageType.assets;
result['url'] = url;
}
return result;
}
//判断字符串是否为base64
static bool checkBase64(String str) {
RegExp base64Head = RegExp(r"data:\S+/\S+;base64,");
//如果默认是64的头 那么就返回true
if (base64Head.hasMatch(str) ) {
return true;
}
//切割base64取尾段
List<String> splitList = str.split(',');
str = splitList[splitList.length - 1];
return checkIsBase64(str);
}
static checkIsBase64(String str) {
RegExp regExp = RegExp(
r"(data:\S+/\S+;base64,)?(([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)){1}");
bool firstCheck = regExp.hasMatch(str);
//初步判断是否符合Base64格式
if (!firstCheck) {
return false;
}
//尝试看是否能还原成Unit8List
try {
Uint8List decStr = const Base64Decoder().convert(str);
return true;
} catch (e) {
return false;
}
}
}