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

GitHub

查看 Github