my_load_image.dart 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:extended_image/extended_image.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:cs_resources/generated/assets.dart';
  6. import 'package:shared/utils/image_path_utils.dart';
  7. import 'package:shared/utils/reg_utils.dart';
  8. import 'package:shared/utils/util.dart';
  9. /*
  10. 图片加载(网络图片,Base64图片,File图片,Asset图片,支持圆角与边框,支持占位图)
  11. 尽可能少的嵌套布局,如果不支持部分属性则不会嵌套对应的Widget,由于图片与文本是最常用的控件,尽量减少嵌套层级提升性能
  12. */
  13. class MyLoadImage extends StatelessWidget {
  14. MyLoadImage(
  15. this.image, {
  16. Key? key,
  17. this.width,
  18. this.height,
  19. this.fit = BoxFit.cover,
  20. this.placeholderPath = '',
  21. this.cacheWidth,
  22. this.cacheHeight,
  23. this.isCircle,
  24. this.cornerRadius,
  25. this.borderColor,
  26. this.borderWidth,
  27. this.onClick,
  28. }) : super(key: key) {
  29. if (isCircle != null) {
  30. if (isCircle ?? true) {
  31. cornerRadius = width ?? 0 / 2;
  32. }
  33. }
  34. }
  35. final String? image;
  36. final double? width;
  37. final double? height;
  38. final BoxFit fit;
  39. final String placeholderPath;
  40. final int? cacheWidth;
  41. final int? cacheHeight;
  42. bool? isCircle = false;
  43. double? borderWidth = 0;
  44. Color? borderColor = Colors.transparent;
  45. VoidCallback? onClick;
  46. double? cornerRadius = 0;
  47. @override
  48. Widget build(BuildContext context) {
  49. //占位图
  50. final Widget placeholder = placeholderPath.isEmpty
  51. ? Container(
  52. //如果没有设置占位图,使用默认的灰色背景内置Icon小图标
  53. width: width,
  54. height: height,
  55. color:const Color(0xffE0E0E0),
  56. alignment: Alignment.center,
  57. child: const MyAssetImage(
  58. Assets.baseLibImageDefaultPlaceholder,
  59. width: 38,
  60. height: 32,
  61. ),
  62. )
  63. : MyAssetImage(
  64. //如果有占位图,展示自己的占位图
  65. placeholderPath,
  66. height: height,
  67. width: width,
  68. fit: fit,
  69. );
  70. if (Utils.isEmpty(image) || image!.startsWith('http') || image!.startsWith('data:')) {
  71. //加载网络图片
  72. return _buildDecorationNetImage(placeholder);
  73. } else if (Utils.isNotEmpty(image) && RegCheckUtils.isLocalImagePath(image!)) {
  74. //加载本地File路径的图片
  75. return _buildDecorationFileImage();
  76. } else {
  77. //加载本地资源的图片
  78. return _buildDecorationAssetImage();
  79. }
  80. }
  81. // ===================================== 网络图片与 Base64 图片 ↓ =====================================
  82. // 网络图片加载布局- 是否携带触摸事件与圆角
  83. Widget _buildDecorationNetImage(Widget placeholder) {
  84. if (cornerRadius != null && cornerRadius! > 0) {
  85. return Container(
  86. decoration: BoxDecoration(
  87. border: Border.all(width: borderWidth ?? 0, color: borderColor ?? Colors.transparent),
  88. borderRadius: BorderRadius.all(Radius.circular(cornerRadius ?? 0)),
  89. ),
  90. child: _buildGestureNetImg(placeholder),
  91. );
  92. } else {
  93. return _buildGestureNetImg(placeholder);
  94. }
  95. }
  96. // 网络图片加载布局- 是否携带触摸事件
  97. Widget _buildGestureNetImg(Widget placeholder) {
  98. return onClick != null
  99. ? GestureDetector(
  100. onTap: onClick,
  101. child: _buildClipImg(placeholder, placeholder),
  102. )
  103. : _buildClipImg(placeholder, placeholder);
  104. }
  105. /// 真正的网络图片布局 (ExtendedImage框架)
  106. ClipRRect _buildClipImg(Widget placeholderWidget, Widget errorWidget) {
  107. if (image?.startsWith('data:') == true) {
  108. return ClipRRect(
  109. borderRadius: BorderRadius.circular(cornerRadius ?? 0),
  110. //加载 Base64 图片
  111. child: Image.memory(
  112. base64.decode(image?.replaceFirst('data:image/png;base64,', '') ?? ''),
  113. width: width,
  114. height: height,
  115. fit: fit,
  116. ));
  117. } else if (image?.startsWith('http') == true) {
  118. return ClipRRect(
  119. borderRadius: BorderRadius.circular(cornerRadius ?? 0),
  120. //加载网络图片
  121. child: ExtendedImage.network(
  122. image ?? "",
  123. width: width,
  124. height: height,
  125. fit: fit,
  126. timeLimit: const Duration(milliseconds: 30000),
  127. cache: true,
  128. //是否启用缓存
  129. //状态监听
  130. loadStateChanged: (ExtendedImageState state) {
  131. switch (state.extendedImageLoadState) {
  132. case LoadState.loading:
  133. return placeholderWidget;
  134. case LoadState.completed:
  135. return null;
  136. case LoadState.failed:
  137. return errorWidget;
  138. }
  139. },
  140. ));
  141. } else {
  142. //由于这里只是加载Http图片与Base64图片,这里做一下兜底,异常的时候展示占位图背景
  143. return ClipRRect(
  144. borderRadius: BorderRadius.circular(cornerRadius ?? 0),
  145. child: placeholderWidget,
  146. );
  147. }
  148. }
  149. // ===================================== File 图片 ↓ =====================================
  150. // 文件加载 - 是否携带触摸事件与圆角
  151. Widget _buildDecorationFileImage() {
  152. if (cornerRadius != null && cornerRadius! > 0) {
  153. return Container(
  154. decoration: BoxDecoration(
  155. border: Border.all(width: borderWidth ?? 0, color: borderColor ?? Colors.transparent),
  156. borderRadius: BorderRadius.all(Radius.circular(cornerRadius ?? 0)),
  157. ),
  158. child: _buildGestureFileImage(),
  159. );
  160. } else {
  161. return _buildGestureFileImage();
  162. }
  163. }
  164. // 文件加载 - 是否携带触摸事件
  165. Widget _buildGestureFileImage() {
  166. return onClick != null
  167. ? GestureDetector(
  168. onTap: onClick,
  169. child: _buildFileImage(),
  170. )
  171. : _buildFileImage();
  172. }
  173. // 文件的加载
  174. Widget _buildFileImage() {
  175. return ClipRRect(
  176. borderRadius: BorderRadius.circular(cornerRadius ?? 0),
  177. child: Image.file(
  178. File(image!),
  179. height: height,
  180. width: width,
  181. cacheWidth: cacheWidth,
  182. cacheHeight: cacheHeight,
  183. fit: fit,
  184. excludeFromSemantics: true,
  185. ),
  186. );
  187. }
  188. // ===================================== Asset 图片 ↓ =====================================
  189. //带装饰的Asset图片资源
  190. Widget _buildDecorationAssetImage() {
  191. if (cornerRadius != null && cornerRadius! > 0) {
  192. return Container(
  193. decoration: BoxDecoration(
  194. border: Border.all(width: borderWidth ?? 0, color: borderColor ?? Colors.transparent),
  195. borderRadius: BorderRadius.all(Radius.circular(cornerRadius ?? 0)),
  196. ),
  197. child: _buildGestureAssetImage(),
  198. );
  199. } else {
  200. return _buildGestureAssetImage();
  201. }
  202. }
  203. //带手势的Asset图片资源
  204. Widget _buildGestureAssetImage() {
  205. return onClick != null
  206. ? GestureDetector(
  207. onTap: onClick,
  208. child: _buildAssetImg(),
  209. )
  210. : _buildAssetImg();
  211. }
  212. //真正的本地图片布局
  213. MyAssetImage _buildAssetImg() {
  214. return MyAssetImage(
  215. image ?? "",
  216. height: height,
  217. width: width,
  218. fit: fit,
  219. cacheWidth: cacheWidth,
  220. cacheHeight: cacheHeight,
  221. );
  222. }
  223. }
  224. /// 加载本地资源图片
  225. class MyAssetImage extends StatelessWidget {
  226. const MyAssetImage(this.image, {Key? key, this.width, this.height, this.cacheWidth, this.cacheHeight, this.fit, this.color}) : super(key: key);
  227. final String image;
  228. final double? width;
  229. final double? height;
  230. final int? cacheWidth;
  231. final int? cacheHeight;
  232. final BoxFit? fit;
  233. final Color? color;
  234. @override
  235. Widget build(BuildContext context) {
  236. var finalPath = ImagePathUtils.getImgPath(image);
  237. return Image.asset(
  238. finalPath,
  239. package: 'cs_resources',
  240. height: height,
  241. width: width,
  242. cacheWidth: cacheWidth,
  243. cacheHeight: cacheHeight,
  244. fit: fit,
  245. color: color,
  246. /// 忽略图片语义
  247. excludeFromSemantics: true,
  248. );
  249. }
  250. }