PowerImage
一个强大的插件,充分利用原生图片库的能力在Flutter端显示图片。
功能
-
支持加载ui.Image的能力,在基于外部纹理的解决方案中,用户无法获取真实的ui.Image来使用,这使得图片库在该特殊使用场景下无能为力。
-
支持图片预加载能力。就像flutter precacheImage一样。这在一些需要高图片显示速度的场景下非常有用。
-
添加了纹理缓存来连接flutter的imageCache!统一了图片缓存,避免了混合原生图片造成的内存问题。
-
支持模拟器。在flutter-1.23.0-18.1.pre之前,模拟器无法显示Texture Widget。
-
改进了自定义图片类型通道。解决了业务自定义图片获取的需求。
-
完美的异常捕获和收集。
-
支持动画。
用法
安装
- power_image:建议使用最新版本
- power_image_ext:您需要根据您使用的flutter版本选择版本。详情请参见 power_image_ext!
将以下内容添加到您的 pubspec.yaml 文件中
dependencies:
power_image:
git:
url: '[email protected]:alibaba/power_image.git'
ref: '0.1.0'
dependency_overrides:
power_image_ext:
git:
url: '[email protected]:alibaba/power_image_ext.git'
ref: '2.5.3'
设置
Flutter
1. 将 ImageCache 替换为 ImageCacheExt。
/// call before runApp()
PowerImageBinding();
或者
/// return ImageCacheExt in createImageCache(),
/// if you have extends with WidgetsFlutterBinding
class XXX extends WidgetsFlutterBinding {
@override
ImageCache createImageCache() {
return ImageCacheExt();
}
}
2. 设置 PowerImageLoader
初始化并设置全局默认渲染模式,renderingTypeTexture为纹理模式,renderingTypeExternal为ffi模式。此外,PowerImageSetupOptions中还有异常报告,可以设置异常报告的采样率。
PowerImageLoader.instance.setup(PowerImageSetupOptions(renderingTypeTexture,
errorCallbackSamplingRate: 1.0,
errorCallback: (PowerImageLoadException exception) {
}));
iOS
PowerImage提供了基本的图片类型,包括网络、文件、原生资源和flutter资源。用户需要自定义相应的加载器。
[[PowerImageLoader sharedInstance] registerImageLoader:[PowerImageNetworkImageLoader new] forType:kPowerImageImageTypeNetwork];
[[PowerImageLoader sharedInstance] registerImageLoader:[PowerImageAssetsImageLoader new] forType:kPowerImageImageTypeNativeAsset];
[[PowerImageLoader sharedInstance] registerImageLoader:[PowerImageFlutterAssertImageLoader new] forType:kPowerImageImageTypeAsset];
[[PowerImageLoader sharedInstance] registerImageLoader:[PowerImageFileImageLoader new] forType:kPowerImageImageTypeFile];
加载器需要遵循 PowerImageLoaderProtocol 协议
typedef void(^PowerImageLoaderCompletionBlock)(BOOL success, PowerImageResult *imageResult);
@protocol PowerImageLoaderProtocol <NSObject>
@required
- (void)handleRequest:(PowerImageRequestConfig *)requestConfig completed:(PowerImageLoaderCompletionBlock)completedBlock;
@end
网络图片加载器示例
- (void)handleRequest:(PowerImageRequestConfig *)requestConfig completed:(PowerImageLoaderCompletionBlock)completedBlock {
/// CDN optimization, you need transfer reqSize to native image loader!
/// CDN optimization, you need transfer reqSize to native image loader!
/// like this: [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:requestConfig.srcString] viewSize:reqSize completed:
CGSize reqSize = requestConfig.originSize;
/// attention.
[[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:requestConfig.srcString] options:nil progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
} completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
if (image != nil) {
completedBlock([PowerImageResult successWithImage:image]);
}else {
completedBlock([PowerImageResult failWithMessage:error.localizedDescription]);
}
}];
}
原生资源加载器示例
- (void)handleRequest:(PowerImageRequestConfig *)requestConfig completed:(PowerImageLoaderCompletionBlock)completedBlock {
UIImage *image = [UIImage imageNamed:requestConfig.srcString];
if (image) {
completedBlock([PowerImageResult successWithImage:image]);
}else {
completedBlock([PowerImageResult failWithMessage:@"MyAssetsImageLoader UIImage imageNamed: nil"]);
}
}
Flutter 资源加载器示例
- (void)handleRequest:(PowerImageRequestConfig *)requestConfig completed:(PowerImageLoaderCompletionBlock)completedBlock {
UIImage *image = [self flutterImageWithName:requestConfig];
if (image) {
completedBlock([PowerImageResult successWithImage:image]);
} else {
completedBlock([PowerImageResult failWithMessage:@"flutterImageWithName nil"]);
}
}
- (UIImage*)flutterImageWithName:(PowerImageRequestConfig *)requestConfig {
NSString *name = requestConfig.srcString;
NSString *package = requestConfig.src[@"package"];
NSString *filename = [name lastPathComponent];
NSString *path = [name stringByDeletingLastPathComponent];
for (int screenScale = [UIScreen mainScreen].scale; screenScale > 1; --screenScale) {
NSString *key = [self lookupKeyForAsset:[NSString stringWithFormat:@"%@/%d.0x/%@", path, screenScale, filename] fromPackage:package];
UIImage *image = [UIImage imageNamed:key inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:nil];
if (image) {
return image;
}
}
NSString *key = [self lookupKeyForAsset:name fromPackage:package];
/// webp iOS < 14 not support
if ([name hasSuffix:@".webp"] && !(@available(ios 14.0, *))) {
NSString *mPath = [[NSBundle mainBundle] pathForResource:key ofType:nil];
NSData *webpData = [NSData dataWithContentsOfFile:mPath];
return [UIImage sd_imageWithWebPData:webpData];
}
return [UIImage imageNamed:key inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:nil];
}
- (NSString *)lookupKeyForAsset:(NSString *)asset fromPackage:(NSString *)package {
if (package && [package isKindOfClass:[NSString class]] && ![package isEqualToString:@""]) {
return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
}else {
return [FlutterDartProject lookupKeyForAsset:asset];
}
}
文件加载器示例
- (void)handleRequest:(PowerImageRequestConfig *)requestConfig completed:(PowerImageLoaderCompletionBlock)completedBlock {
UIImage *image = [[UIImage alloc] initWithContentsOfFile:requestConfig.srcString];
if (image) {
completedBlock([PowerImageResult successWithImage:image]);
} else {
completedBlock([PowerImageResult failWithMessage:@"UIImage initWithContentsOfFile nil"]);
}
}
Android
PowerImage提供了基本的图片类型,包括网络、文件、原生资源和flutter资源。用户需要自定义相应的加载器。
PowerImageLoader.getInstance().registerImageLoader(
new PowerImageNetworkLoader(this.getApplicationContext()), "network");
PowerImageLoader.getInstance().registerImageLoader(
new PowerImageNativeAssetLoader(this.getApplicationContext()), "nativeAsset");
PowerImageLoader.getInstance().registerImageLoader(
new PowerImageFlutterAssetLoader(this.getApplicationContext()), "asset");
PowerImageLoader.getInstance().registerImageLoader(
new PowerImageFileLoader(this.getApplicationContext()), "file");
加载器需要遵循 PowerImageLoaderProtocol 协议
public interface PowerImageLoaderProtocol {
void handleRequest(PowerImageRequestConfig request, PowerImageResult result);
}
网络图片加载器示例
@Override
public void handleRequest(PowerImageRequestConfig request, PowerImageResult result) {
Glide.with(context).load(request.srcString()).into(new CustomTarget<Drawable>(
request.width <= 0 ? Target.SIZE_ORIGINAL : request.width,
request.height <= 0 ? Target.SIZE_ORIGINAL : request.height){
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
if (resource instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable)resource;
result.onResult(true, bitmapDrawable.getBitmap());
} else if (resource instanceof GifDrawable) {
result.onResult(true, ((GifDrawable) resource).getFirstFrame());
} else {
result.onResult(false, null);
}
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
result.onResult(false, null);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
}
原生资源加载器示例
@Override
public void handleRequest(PowerImageRequestConfig request, PowerImageResult result) {
Resources resources = context.getResources();
int resourceId = 0;
try {
resourceId = resources.getIdentifier(request.srcString(),
"drawable", context.getPackageName());
} catch (Resources.NotFoundException e) {
// 资源未找到
e.printStackTrace();
}
if (resourceId == 0) {
result.onResult(false, null);
return;
}
Glide.with(context).load(resourceId).into(
new CustomTarget<Drawable>(request.width <= 0 ? Target.SIZE_ORIGINAL : request.width,
request.height <= 0 ? Target.SIZE_ORIGINAL : request.height) {
@Override
public void onResourceReady(@NonNull Drawable resource,
@Nullable Transition<? super Drawable> transition) {
if (resource instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) resource;
result.onResult(true, bitmapDrawable.getBitmap());
} else {
result.onResult(false, null);
}
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
result.onResult(false, null);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
}
Flutter 资源加载器示例
@Override
public void handleRequest(PowerImageRequestConfig request, PowerImageResult result) {
String name = request.srcString();
if (name == null || name.length() <= 0) {
result.onResult(false, null);
return;
}
String assetPackage = "";
if (request.src != null) {
assetPackage = (String) request.src.get("package");
}
String path;
if (assetPackage != null && assetPackage.length() > 0) {
path = FlutterMain.getLookupKeyForAsset(name, assetPackage);
} else {
path = FlutterMain.getLookupKeyForAsset(name);
}
if (path == null || path.length() <= 0) {
result.onResult(false, null);
return;
}
Uri asset = Uri.parse("file:///android_asset/" + path);
Glide.with(context).load(asset).into(
new CustomTarget<Drawable>(request.width <= 0 ? Target.SIZE_ORIGINAL : request.width,
request.height <= 0 ? Target.SIZE_ORIGINAL : request.height) {
@Override
public void onResourceReady(@NonNull Drawable resource,
@Nullable Transition<? super Drawable> transition) {
if (resource instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) resource;
result.onResult(true, bitmapDrawable.getBitmap());
} else if (resource instanceof GifDrawable) {
result.onResult(true, ((GifDrawable) resource).getFirstFrame());
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
result.onResult(false, null);
}
});
}
文件加载器示例
@Override
public void handleRequest(PowerImageRequestConfig request, PowerImageResult result) {
String name = request.srcString();
if (name == null || name.length() <= 0) {
result.onResult(false, null);
return;
}
Uri asset = Uri.parse("file://" + name);
Glide.with(context).load(asset).into(
new CustomTarget<Drawable>(request.width <= 0 ? Target.SIZE_ORIGINAL : request.width,
request.height <= 0 ? Target.SIZE_ORIGINAL : request.height) {
@Override
public void onResourceReady(@NonNull Drawable resource,
@Nullable Transition<? super Drawable> transition) {
if (resource instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) resource;
result.onResult(true, bitmapDrawable.getBitmap());
} else if (resource instanceof GifDrawable) {
result.onResult(true, ((GifDrawable) resource).getFirstFrame());
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
result.onResult(false, null);
}
});
}
API
网络图片
PowerImage.network(
String src, {
Key? key,
String? renderingType,
double? imageWidth,
double? imageHeight,
this.width,
this.height,
this.frameBuilder,
this.errorBuilder,
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
this.semanticLabel,
this.excludeFromSemantics = false,
})
原生资源
PowerImage.nativeAsset(
String src, {
Key? key,
String? renderingType,
double? imageWidth,
double? imageHeight,
this.width,
this.height,
this.frameBuilder,
this.errorBuilder,
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
this.semanticLabel,
this.excludeFromSemantics = false,
})
Flutter 资源
PowerImage.asset(
String src, {
Key? key,
String? renderingType,
double? imageWidth,
double? imageHeight,
String? package,
this.width,
this.height,
this.frameBuilder,
this.errorBuilder,
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
this.semanticLabel,
this.excludeFromSemantics = false,
})
File(文件)
PowerImage.file(String src,
{Key key,
this.width,
this.height,
this.frameBuilder,
this.errorBuilder,
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
String renderingType,
double imageWidth,
double imageHeight})
自定义图片类型
/// 自定义 imageType\src
/// 效果:将src encode 后,完成地传递给 native 对应 imageType 注册的 loader
/// 使用场景:
/// 例如,自定义加载相册照片,通过自定义 imageType 为 "album",
/// native 侧注册 "album" 类型的 loader 自定义图片的加载。
PowerImage.type(
String imageType, {
required PowerImageRequestOptionsSrc src,
Key? key,
String? renderingType,
double? imageWidth,
double? imageHeight,
this.width,
this.height,
this.frameBuilder,
this.errorBuilder,
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
this.semanticLabel,
this.excludeFromSemantics = false,
})
.options
/// 更加灵活的方式,通过自定义options来展示图片
///
/// PowerImageRequestOptions({
/// @required this.src, //资源
/// @required this.imageType, //资源类型,如网络图,本地图或者自定义等
/// this.renderingType, //渲染方式,默认全局
/// this.imageWidth, //图片的渲染的宽度
/// this.imageHeight, //图片渲染的高度
/// });
///
/// PowerExternalImageProvider(FFI[bitmap]方案)
/// PowerTextureImageProvider(texture方案)
///
/// 使用场景:
/// 例如,自定义加载相册照片,通过自定义 imageType 为 "album",
/// native 侧注册 "album" 类型的 loader 自定义图片的加载。
///
PowerImage.options(
PowerImageRequestOptions options, {
Key? key,
this.width,
this.height,
this.frameBuilder,
this.errorBuilder,
this.fit = BoxFit.cover,
this.alignment = Alignment.center,
this.semanticLabel,
this.excludeFromSemantics = false,
})
示例
网络
return PowerImage.network(
'https://github.flutterdart.cn/assets-for-api-docs/assets/widgets/owl.jpg',
width: 100,
height: 100,
);
最佳实践
工作原理
https://mp.weixin.qq.com/s/TdTGK21S-Yd3aD-yZDoYyQ