import 'dart:convert'; import 'dart:io'; import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; import 'package:cs_resources/generated/assets.dart'; import 'package:shared/utils/image_path_utils.dart'; import 'package:shared/utils/reg_utils.dart'; import 'package:shared/utils/util.dart'; /* 图片加载(网络图片,Base64图片,File图片,Asset图片,支持圆角与边框,支持占位图) 尽可能少的嵌套布局,如果不支持部分属性则不会嵌套对应的Widget,由于图片与文本是最常用的控件,尽量减少嵌套层级提升性能 */ class MyLoadImage extends StatelessWidget { MyLoadImage( this.image, { Key? key, this.width, this.height, this.fit = BoxFit.cover, this.placeholderPath = '', this.cacheWidth, this.cacheHeight, this.isCircle, this.cornerRadius, this.borderColor, this.borderWidth, this.onClick, }) : super(key: key) { if (isCircle != null) { if (isCircle ?? true) { cornerRadius = width ?? 0 / 2; } } } final String? image; final double? width; final double? height; final BoxFit fit; final String placeholderPath; final int? cacheWidth; final int? cacheHeight; bool? isCircle = false; double? borderWidth = 0; Color? borderColor = Colors.transparent; VoidCallback? onClick; double? cornerRadius = 0; @override Widget build(BuildContext context) { //占位图 final Widget placeholder = placeholderPath.isEmpty ? Container( //如果没有设置占位图,使用默认的灰色背景内置Icon小图标 width: width, height: height, color:const Color(0xffE0E0E0), alignment: Alignment.center, child: const MyAssetImage( Assets.baseLibImageDefaultPlaceholder, width: 38, height: 32, ), ) : MyAssetImage( //如果有占位图,展示自己的占位图 placeholderPath, height: height, width: width, fit: fit, ); if (Utils.isEmpty(image) || image!.startsWith('http') || image!.startsWith('data:')) { //加载网络图片 return _buildDecorationNetImage(placeholder); } else if (Utils.isNotEmpty(image) && RegCheckUtils.isLocalImagePath(image!)) { //加载本地File路径的图片 return _buildDecorationFileImage(); } else { //加载本地资源的图片 return _buildDecorationAssetImage(); } } // ===================================== 网络图片与 Base64 图片 ↓ ===================================== // 网络图片加载布局- 是否携带触摸事件与圆角 Widget _buildDecorationNetImage(Widget placeholder) { if (cornerRadius != null && cornerRadius! > 0) { return Container( decoration: BoxDecoration( border: Border.all(width: borderWidth ?? 0, color: borderColor ?? Colors.transparent), borderRadius: BorderRadius.all(Radius.circular(cornerRadius ?? 0)), ), child: _buildGestureNetImg(placeholder), ); } else { return _buildGestureNetImg(placeholder); } } // 网络图片加载布局- 是否携带触摸事件 Widget _buildGestureNetImg(Widget placeholder) { return onClick != null ? GestureDetector( onTap: onClick, child: _buildClipImg(placeholder, placeholder), ) : _buildClipImg(placeholder, placeholder); } /// 真正的网络图片布局 (ExtendedImage框架) ClipRRect _buildClipImg(Widget placeholderWidget, Widget errorWidget) { if (image?.startsWith('data:') == true) { return ClipRRect( borderRadius: BorderRadius.circular(cornerRadius ?? 0), //加载 Base64 图片 child: Image.memory( base64.decode(image?.replaceFirst('data:image/png;base64,', '') ?? ''), width: width, height: height, fit: fit, )); } else if (image?.startsWith('http') == true) { return ClipRRect( borderRadius: BorderRadius.circular(cornerRadius ?? 0), //加载网络图片 child: ExtendedImage.network( image ?? "", width: width, height: height, fit: fit, timeLimit: const Duration(milliseconds: 30000), cache: true, //是否启用缓存 //状态监听 loadStateChanged: (ExtendedImageState state) { switch (state.extendedImageLoadState) { case LoadState.loading: return placeholderWidget; case LoadState.completed: return null; case LoadState.failed: return errorWidget; } }, )); } else { //由于这里只是加载Http图片与Base64图片,这里做一下兜底,异常的时候展示占位图背景 return ClipRRect( borderRadius: BorderRadius.circular(cornerRadius ?? 0), child: placeholderWidget, ); } } // ===================================== File 图片 ↓ ===================================== // 文件加载 - 是否携带触摸事件与圆角 Widget _buildDecorationFileImage() { if (cornerRadius != null && cornerRadius! > 0) { return Container( decoration: BoxDecoration( border: Border.all(width: borderWidth ?? 0, color: borderColor ?? Colors.transparent), borderRadius: BorderRadius.all(Radius.circular(cornerRadius ?? 0)), ), child: _buildGestureFileImage(), ); } else { return _buildGestureFileImage(); } } // 文件加载 - 是否携带触摸事件 Widget _buildGestureFileImage() { return onClick != null ? GestureDetector( onTap: onClick, child: _buildFileImage(), ) : _buildFileImage(); } // 文件的加载 Widget _buildFileImage() { return ClipRRect( borderRadius: BorderRadius.circular(cornerRadius ?? 0), child: Image.file( File(image!), height: height, width: width, cacheWidth: cacheWidth, cacheHeight: cacheHeight, fit: fit, excludeFromSemantics: true, ), ); } // ===================================== Asset 图片 ↓ ===================================== //带装饰的Asset图片资源 Widget _buildDecorationAssetImage() { if (cornerRadius != null && cornerRadius! > 0) { return Container( decoration: BoxDecoration( border: Border.all(width: borderWidth ?? 0, color: borderColor ?? Colors.transparent), borderRadius: BorderRadius.all(Radius.circular(cornerRadius ?? 0)), ), child: _buildGestureAssetImage(), ); } else { return _buildGestureAssetImage(); } } //带手势的Asset图片资源 Widget _buildGestureAssetImage() { return onClick != null ? GestureDetector( onTap: onClick, child: _buildAssetImg(), ) : _buildAssetImg(); } //真正的本地图片布局 MyAssetImage _buildAssetImg() { return MyAssetImage( image ?? "", height: height, width: width, fit: fit, cacheWidth: cacheWidth, cacheHeight: cacheHeight, ); } } /// 加载本地资源图片 class MyAssetImage extends StatelessWidget { const MyAssetImage(this.image, {Key? key, this.width, this.height, this.cacheWidth, this.cacheHeight, this.fit, this.color}) : super(key: key); final String image; final double? width; final double? height; final int? cacheWidth; final int? cacheHeight; final BoxFit? fit; final Color? color; @override Widget build(BuildContext context) { var finalPath = ImagePathUtils.getImgPath(image); return Image.asset( finalPath, package: 'cs_resources', height: height, width: width, cacheWidth: cacheWidth, cacheHeight: cacheHeight, fit: fit, color: color, /// 忽略图片语义 excludeFromSemantics: true, ); } }