前言:
前面做过 原生内嵌 flutter,
今天尝试下 flutter 内嵌原生组件 ImageView;
效果如下: flutter按钮 控制原生组件切换显示网络图片
第一步创建 Flutter Plugins 工程
这个步骤没啥说的;估计 AndroidStuido 版本不同 界面会有差异;
当然你也可以 命令行 进行创建
工程创建完毕后,下面的目录大致如下:
你可以直接运行这个工程,就能把这个 flutter plugins 模板工程跑起来,
第二步编写 插件脚本 lib 目录
flutter_plugins.dart 是系统自动帮你创建的我们不用管
我们创建 native_image.dart, native_image_controller.dart 这两个脚本
native_image.dart 用于UI展示
这里注册了一个 切换图片的事件
void initState() { // TODO: implement initState super.initState(); // 执行的切换图命令, 参数就是 图片的url widget.controller.addListener(() { // print( // '-----------------------------imageUrl:${widget.controller.imageUrl}'); _channel .invokeListMethod("changeImage", {'url': widget.controller.imageUrl}); }); }
这里是实际创建原生界面的容器 根据不同系统进行的创建 (ios部分在下一章我们会讲到)
_loadNativeView() { if (Platform.isAndroid) { return AndroidView( viewType: 'imageView', //视图标识符 要和原生 保持一致 要不然加载不到视图 onPlatformViewCreated: onPlatformViewCreated, //原生视图创建成功的回调 creationParams: <String, dynamic>{ 'initUrl': widget.initUrl }, //给原生传递初始化参数 就是上面定义的初始化参数 // 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec] // 如果存在 creationParams,则该值不能为null creationParamsCodec: const StandardMessageCodec(), ); } else if (Platform.isIOS) { return UiKitView(viewType: 'imageView'); } }
完整的脚本
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_plugins/native_image_controller.dart'; class NativeImage extends StatefulWidget { final NativeImageController controller; final String initUrl; const NativeImage({Key? key, required this.initUrl, required this.controller}) : super(key: key); @override _NativeImageState createState() => _NativeImageState(); } class _NativeImageState extends State<NativeImage> { static const MethodChannel _channel = MethodChannel('flutter_plugins_native_image'); @override void initState() { // TODO: implement initState super.initState(); // 执行的切换图命令, 参数就是 图片的url widget.controller.addListener(() { // print( // '-----------------------------imageUrl:${widget.controller.imageUrl}'); _channel .invokeListMethod("changeImage", {'url': widget.controller.imageUrl}); }); } @override void dispose() { // TODO: implement dispose super.dispose(); widget.controller.removeListener(() {}); } @override Widget build(BuildContext context) { return Container( color: Colors.grey, width: MediaQuery.of(context).size.width, height: 200, child: _loadNativeView(), ); } Future<void> onPlatformViewCreated(int id) async { // return widget.onCreated( new FlutterPlugins().) print('-----创建成功'); } _loadNativeView() { if (Platform.isAndroid) { return AndroidView( viewType: 'imageView', //视图标识符 要和原生 保持一致 要不然加载不到视图 onPlatformViewCreated: onPlatformViewCreated, //原生视图创建成功的回调 creationParams: <String, dynamic>{ 'initUrl': widget.initUrl }, //给原生传递初始化参数 就是上面定义的初始化参数 // 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec] // 如果存在 creationParams,则该值不能为null creationParamsCodec: const StandardMessageCodec(), ); } else if (Platform.isIOS) { return UiKitView(viewType: 'imageView'); } } }
native_image_controller.dart 用于原生的ImageView 显示图片的控制
代码很简单
import 'package:flutter/cupertino.dart'; class NativeImageController extends ChangeNotifier { late String imageUrl; changeImage(String url) { imageUrl = url; notifyListeners(); } }
第三步编写 原生脚本 Android 目录
点击右上角 Open for Editing in Android Studio
其中: FlutterPluginsPlugin.java 系统生成 不用管 我们 新建 NativeViewPluginsView.java 和 NativeViewPluginsFactory.java NativeViewPluginsFactory 是我们的一个工厂方法 承接创建的 原生页面固定写法package com.john.flutter_plugins; import android.content.Context; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; // https://blog.csdn.net/WangQingLei0307/article/details/122680290 public class NativeViewPluginsFactory extends PlatformViewFactory { private BinaryMessenger binaryMessenger = null; public NativeViewPluginsFactory(BinaryMessenger messenger){ super(StandardMessageCodec.INSTANCE); this.binaryMessenger = messenger; } /** * @param createArgsCodec the codec used to decode the args parameter of {@link #create}. */ public NativeViewPluginsFactory(MessageCodec<Object> createArgsCodec) { super(createArgsCodec); } @Override public PlatformView create(Context context, int viewId, Object args) { return new NativeViewPluginsView(context,viewId,args,this.binaryMessenger); } }
NativeViewPluginsView 继承 ImageView 是我们真正用进行页面操作的地方这个方法中我们主要进行 原生页面的设置;以及事件的注册
public NativeViewPluginsView(Context context, int viewId, Object args, BinaryMessenger binaryMessenger) { super(context); this.context = context; Toast.makeText(context, "原生插件启用成功id:"+((Map)args).isEmpty(), Toast.LENGTH_LONG).show(); // ImageView 相关设置 setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)); setBackgroundColor(Color.argb(255,75,79,79)); //0完全透明 255不透明 // 设置默认图片 参数取自默认传参方式 imageUrl =((Map<String, String>) args).get("initUrl"); new WorkThread().start(); /// 注册方法 用于切换显示的图片 methodChannel = new MethodChannel(binaryMessenger, "flutter_plugins_native_image"); methodChannel.setMethodCallHandler(this); }
完整脚本
package com.john.flutter_plugins; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.SurfaceTexture; import android.os.Handler; import android.os.Message; import android.os.StrictMode; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.Toast; import androidx.annotation.NonNull; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Map; import io.flutter.Log; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.platform.PlatformView; public class NativeViewPluginsView extends ImageView implements PlatformView, MethodChannel.MethodCallHandler, TextureView.SurfaceTextureListener { public Context context; private MethodChannel methodChannel = null; private String imageUrl = "https://img.gbm001.com/media/catalog/product/pic/x/PCVJYJD1.png"; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { Log.i("---------------------","获得图片"); setImageBitmap( (Bitmap)msg.obj); } }; public NativeViewPluginsView(Context context, int viewId, Object args, BinaryMessenger binaryMessenger) { super(context); this.context = context; Toast.makeText(context, "原生插件启用成功id:"+((Map)args).isEmpty(), Toast.LENGTH_LONG).show(); // ImageView 相关设置 setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)); setBackgroundColor(Color.argb(255,75,79,79)); //0完全透明 255不透明 // 设置默认图片 参数取自默认传参方式 imageUrl =((Map<String, String>) args).get("initUrl"); new WorkThread().start(); /// 注册方法 用于切换显示的图片 methodChannel = new MethodChannel(binaryMessenger, "flutter_plugins_native_image"); methodChannel.setMethodCallHandler(this); } @Override public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { } @Override public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { return false; } @Override public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { handleCall(call, result); } // 事件回调 private void handleCall(MethodCall methodCall, MethodChannel.Result result){ //Toast.makeText(context, "收到 Flutter 调用", Toast.LENGTH_LONG).show(); if (methodCall.method.equals("changeImage")){ //Log.i("url", methodCall.argument("url")); imageUrl = methodCall.argument("url"); new WorkThread().start(); } } @Override public View getView() { return this; } @Override public void dispose() { } //工作线程 private class WorkThread extends Thread { @Override public void run() { //......处理比较耗时的操作 Message msg = new Message(); Bitmap bitmap = getHttpBitmap(imageUrl); msg.obj = bitmap; handler.sendMessage(msg); } public Bitmap getHttpBitmap(String url){ URL myFileURL; Bitmap bitmap=null; try{ myFileURL = new URL(url); //获得连接 HttpURLConnection conn=(HttpURLConnection)myFileURL.openConnection(); //设置超时时间为6000毫秒,conn.setConnectionTiem(0);表示没有时间限制 conn.setRequestMethod("GET"); conn.setConnectTimeout(6000); //连接设置获得数据流 conn.setDoInput(true); //不使用缓存 conn.setUseCaches(false); //这句可有可无,没有影响 //conn.connect(); //得到数据流 InputStream is = conn.getInputStream(); //解析得到图片 bitmap = BitmapFactory.decodeStream(is); //关闭数据流 is.close(); }catch(Exception e){ e.printStackTrace(); //Toast.makeText(context, "图片出错", Toast.LENGTH_LONG).show(); Log.i("-----------图片出错",e.toString()); } return bitmap; } } }
这里有个地方要注意下;
- 因为是网络图片 需要的权限等等是要加上的;
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 允许写手机存储(必须) --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
- 而且这个地方下载图片的过程中使用多线程;多线程不能直接访问UI线程;所以用到 Handler 进行数据传递; 我在想这里换成异步网络请求应该会优雅一些
第四步 在Flutter 工程中使用我们的插件
马上就要大功告成 了! 我们返回到 Flutter 工程中,在 lib 文件夹中 编辑我们的 main.dart
直接使用 我们插件中写的 NativeImage
完整脚本
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_plugins/flutter_plugins.dart'; import 'package:flutter_plugins/native_image.dart'; import 'package:flutter_plugins/native_image_controller.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { String _platformVersion = 'Unknown'; final NativeImageController controller = NativeImageController(); final List<String> images = [ 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fgss0.baidu.com%2F-Po3dSag_xI4khGko9WTAnF6hhy%2Fzhidao%2Fpic%2Fitem%2F5366d0160924ab188c376d623efae6cd7b890b45.jpg&refer=http%3A%2F%2Fgss0.baidu.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652700915&t=cee2b6aad854f8f859d23986be137e30', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F201610%2F31%2F20161031211042_FfTLG.thumb.1000_0.png&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652701005&t=829ecac7047856867820e2c4fb0afcc5', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.js.xinhuanet.com%2Ftitlepic%2F111724%2F1117245314_1448349306603_title0h.jpg&refer=http%3A%2F%2Fwww.js.xinhuanet.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652701166&t=44b77ec0737766b99958f4510acad99f', 'https://img1.baidu.com/it/u=3294782826,883543847&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=334', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fa.zdmimg.com%2F202110%2F26%2F6177cd2a57db88955.jpg_e1080.jpg&refer=http%3A%2F%2Fa.zdmimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1652701313&t=03f121950c96459d40adc812de7b81b4', 'https://img0.baidu.com/it/u=2007762294,622825342&fm=253&fmt=auto&app=120&f=JPEG?w=960&h=600', ]; int currentIndex = 0; @override void initState() { super.initState(); initPlatformState(); } // Platform messages are asynchronous, so we initialize in an async method. Future<void> initPlatformState() async { String platformVersion; // Platform messages may fail, so we use a try/catch PlatformException. // We also handle the message potentially returning null. try { platformVersion = await FlutterPlugins.platformVersion ?? 'Unknown platform version'; } on PlatformException { platformVersion = 'Failed to get platform version.'; } // If the widget was removed from the tree while the asynchronous platform // message was in flight, we want to discard the reply rather than calling // setState to update our non-existent appearance. if (!mounted) return; setState(() { _platformVersion = platformVersion; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: Center( child: Column( children: [ NativeImage( initUrl: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1113%2F0F420110430%2F200F4110430-6-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1658406221&t=4fcb0dc9d8c5d7a123ae4aff0d18e588", controller: controller, ), Text('Running on: $_platformVersion\n'), Spacer(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ OutlinedButton( onPressed: () { if (currentIndex < 0) { currentIndex = images.length - 1; } if (currentIndex >= images.length) { currentIndex = 0; } controller.changeImage(images[currentIndex]); currentIndex--; }, child: Text("上一张"), ), OutlinedButton( onPressed: () { if (currentIndex >= images.length) { currentIndex = 0; } if (currentIndex < 0) { currentIndex = images.length - 1; } controller.changeImage(images[currentIndex]); currentIndex++; }, child: Text("下一张"), ), // TextField(controller: ,) ], ) ], ), ), ), ); } }
到此为止都OK了
工程链接https://download.csdn.net/download/nicepainkiller/85733897