Flutter调用原生图片资源

Posted Ever69

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter调用原生图片资源相关的知识,希望对你有一定的参考价值。

在Flutter与原生的混合开发中,经常遇到Flutter与原生都需要使用同一份图片的情况,并且由于原生是主导,图片资源都在原生这侧。
那么,Flutter可不可以直接使用原生的图片资源呢?
——答案是不可以的,Flutter并不能直接访问原生侧的图片资源,官方没有提供这样的API供我们使用,并且官方文档中页说明了不支持。

请注意,在 Flutter 1.0 beta 2 之前,Flutter 中定义的资源无法从本机端访问,反之亦然,Flutter 无法使用本机中的资源,因为它们位于不同的文件夹中。
从 Flutter beta 2 开始,Flutter资源存储在本机资源文件夹中,并使用 android 的本机端访问AssetManager:
val flutterAssetStream = assetManager.open("flutter_assets/assets/my_flutter_asset.png")
截止到 Flutter beta 2,Flutter 仍然无法访问原生资源,也无法访问原生资产。

简单讲就是,在Flutter 1.0 beta 2 之前,Flutter和原生都不能互相访问对方的资源,从 Flutter beta 2 之后,原生可以访问Flutter中的资源,但是Flutter还是不能访问原生中的资源。

那么,在Flutter侧再放一份图片资源呢?虽然可以这么做,但这样显然是不合理的,除了要维护两套资源文件外,安装包的大小也会增加,尤其是那种有很多图片资源的App。
所以,为了避免两份同样图片资源的存在,我们只能想办法让Flutter使用原生侧的图片资源,那么,到底怎么做呢?

虽然Flutter不能直接使用原生的图片资源,但是我们可以曲线救国嘛,直接不可以,那就间接咯~

Flutter中图片的加载方式

曲线救国前,先了解一下,在Flutter中,负责图片展示的小部Image有四种图片加载方式

  • Image.assest,用于从本地资源集合获取图像。
  • Image.network ,用于从 URL 获取图像。
  • Image.memory,用于从字节数组获取图像。
  • Image.file,用于从文件获取图像。

通过流的形式加载原生的图片资源


既然Flutter中的Image可以通过字节数组的形式加载图片,那么,我们是不是可以将原生中的图片转成字节数组,返回给Flutter进行图片加载呢?我觉得没毛病。
使用MethodChannel定义一个通信方法,向原生获取图片的字节数组。

Flutter侧代码

//字节数组
Uint8List _imageBytes = Uint8List.fromList([]);
//用于展示的小部件
RaisedButton(
  child: Text("通过流的形式调用原生图片"),
  onPressed: () 
    _getImageAsBytes("ic_launcher");
  ,
),
Image.memory(_imageBytes)
//获取图片字节数组的方法
Future<void> _getImageAsBytes(String imageName) async 
    Uint8List bytes =
        await platform.invokeMethod("imageAsBytes", 'name': '$imageName');
    setState(() 
      _imageBytes = bytes;
    );
  

原生侧代码
Android

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) 
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "samples.flutter.dev/battery")
                .setMethodCallHandler  call, result ->
                    when (call.method) 
                        "imageAsBytes" -> 
                            if (call.hasArgument("name")) 
                                val path = call.argument<String>("name")?.let  getImageAsBytes(it) 
                                if (path == null) 
                                    result.error("-1", "调用原生图片失败", null)
                                 else 
                                    result.success(path)
                                
                            
                        
                    
                
    
private fun getImageAsBytes(imageName: String): ByteArray? 
        var imageId = resources.getIdentifier(imageName, "mipmap", packageName)
        if (imageId == 0) 
            imageId = resources.getIdentifier(imageName, "drawable", packageName)
        
        if (imageId == 0) 
            return null
        
        val imageBitmap = BitmapFactory.decodeResource(resources, imageId)
        val  bos = ByteArrayOutputStream()
        imageBitmap.compress(Bitmap.CompressFormat.PNG,100,bos)
        return bos.toByteArray()
    

ios
略。。

通过文件的形式加载原生的图片资源


Flutter中的Image小部件可以通过文件的形式加载图片,我们大可以将原生中的图片以文件的形式保存在本地,再将文件的路径告知Flutter去加载,这样就可以间接的使用原生中的图片资源了。
ok,上代码。还是使用MethodChannel进行通信,对Flutter与原生通信不熟悉的朋友可以看这篇博客-Flutte与原生通信

Flutter侧代码

 //全局变量,图片文件地址
 String _imageFilePath = "";
 //小部件
 RaisedButton(
   child: Text("调用展示原生图片资源"),
   onPressed: () 
     _getImageAsFile("ic_launcher");
     ,
   ),
 Image.file(File(_imageFilePath))
 //获取图片文件路径的方法
 Future<void> _getImageAsFile(String imageName) async 
    String path = await platform.invokeMethod("image", 'name': '$imageName');
    setState(() 
      _imageFilePath = path;
    );
  

原生侧代码
Android

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) 
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "samples.flutter.dev/battery")
                .setMethodCallHandler  call, result ->
                    when (call.method) 
                        "imageAsFile" -> 
                            if (call.hasArgument("name")) 
                                val path = call.argument<String>("name")?.let  getImageAsFile(it) 
                                if (path.isNullOrEmpty()) 
                                    result.error("-1", "调用原生图片失败", null)
                                 else 
                                    result.success(path)
                                
                            
                        
                    
                
    

    private fun getImageAsFile(imageName: String): String 
        var imageFilePath = "$externalCacheDir$File.separator$imageName"
        val imageFile = File(imageFilePath)
        if (!imageFile.exists()) 
            var imageId = resources.getIdentifier(imageName, "mipmap", packageName)
            if (imageId == 0) 
                imageId = resources.getIdentifier(imageName, "drawable", packageName)
            
            if (imageId == 0) 
                return ""
            
            val imageBitmap = BitmapFactory.decodeResource(resources, imageId)
            val fos = FileOutputStream(imageFile)
            val success = imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
            if (!success) 
                imageFile.delete()
                return ""
            
        
        return imageFilePath
    

Ios
略(滑稽.jpg)

效果如何?


代码写完,读取个原生图片看看效果如何,以Android为例,读取mipmap文件夹下的ic_launcher图片(因为我这是一个Flutter工程,所以ic_launcher是Flutter的logo)。

运行App。

dangdan!Flutter读取原生图片,搞定~

以上是关于Flutter调用原生图片资源的主要内容,如果未能解决你的问题,请参考以下文章

Flutter自定义Widget—可加载原生图片资源的Image

Flutter自定义Widget—可加载原生图片资源的Image

Flutter 调用原生硬件 Api 实现照相机 拍照和相册选择 以及拍照上传

FlutterWeb 和 WebView 原生交互调用

Flutter 调用原生

原生与Flutter通信