Flutter布局中嵌入Android原生组件 - 全景图组件封装

Flutter布局中嵌入Android原生组件 - 全景图组件封装

Flutter已经拥有大量的UI组件库,但是有一些特殊的视图它并没有,这时候就需要Native来实现这样的视图,然后在Flutter端调用。这里以封装一个全景图组件为例讲解在Flutter布局中怎样嵌入Android原生组件。

项目地址:flutter_panorama
全景图插件:GoogleVr (这里是老版本的实现方式)

Flutter全景图组件

在Android工程中编写并注册原生组件

添加原生组件的流程基本是这样的:

  1. 编写Native组件,它需要实现Flutter的PlatformView,用于提供原生的组件视图。
  2. 创建PlatformViewFactory用于生成PlatformView。
  3. 创建FlutterPlugin用于注册原生组件。注意:这里新老版本写法不太一样,我的Flutter版本是v1.12.13+hotfix.9。

创建Native组件

在build.gradle中引入GoogleVr的依赖。

dependencies {implementation 'com.google.vr:sdk-panowidget:1.180.0'
}

创建FlutterPanoramaView,实现PlatformView和MethodCallHandler。
在这里实现MethodCallHandler,而不是在FlutterPlugin中实现,是因为每一个视图独立对应一个通信方法。
注意:panoramaView = new VrPanoramaView(context); 这里的context必须是Activity的context

public class FlutterPanoramaView implements PlatformView, MethodChannel.MethodCallHandler {// Method通道private final MethodChannel methodChannel;// 原生全景图private final VrPanoramaView panoramaView;// 加载图片的异步任务private ImageLoaderTask imageLoaderTask;private VrPanoramaView.Options options = new VrPanoramaView.Options();;FlutterPanoramaView(final Context context,BinaryMessenger messenger,int id,Map<String, Object> params) {// 创建视图,这里的context必须是Activity的contextpanoramaView = new VrPanoramaView(context);// 配置参数if (params.get("enableInfoButton") == null || !(boolean) params.get("enableInfoButton")) {panoramaView.setInfoButtonEnabled(false);}if (params.get("enableFullButton") == null || !(boolean) params.get("enableFullButton")) {panoramaView.setFullscreenButtonEnabled(false);}if (params.get("enableStereoModeButton") == null || !(boolean) params.get("enableStereoModeButton")) {panoramaView.setStereoModeButtonEnabled(false);}if (params.get("imageType") != null) {options.inputType = (int) params.get("imageType") ;}// 加载图像imageLoaderTask = new ImageLoaderTask(context);imageLoaderTask.execute((String)params.get("uri"), (String)params.get("asset"), (String)params.get("packageName"));// 为每一个组件实例注册MethodChannel,通过ID区分methodChannel = new MethodChannel(messenger, "plugins.vincent/panorama_" + id);methodChannel.setMethodCallHandler(this);}@Overridepublic void onMethodCall(MethodCall call, MethodChannel.Result result) {// TODO 处理Flutter端传过来的方法}@Overridepublic View getView() {// 在这里返回原生视图return panoramaView;}@Overridepublic void dispose() {imageLoaderTask = null;}private boolean isHTTP(Uri uri) {if (uri == null || uri.getScheme() == null) {return false;}String scheme = uri.getScheme();return scheme.equals("http") || scheme.equals("https");}private class ImageLoaderTask extends AsyncTask<String, String, Bitmap> {final Context context;public ImageLoaderTask(Context context) {this.context = context;}@Overrideprotected Bitmap doInBackground(String... strings) {if (strings == null || strings.length < 1) {return null;}String path = strings[0];String asset = strings[1];String packageName = strings[2];Bitmap image = null;if (!TextUtils.isEmpty(asset)) {// Flutter的Asset资源String assetKey;if (!TextUtils.isEmpty(packageName)) {assetKey = FlutterMain.getLookupKeyForAsset(asset, packageName);} else {assetKey = FlutterMain.getLookupKeyForAsset(asset);}try {AssetManager assetManager = context.getAssets();AssetFileDescriptor fileDescriptor = assetManager.openFd(assetKey);image = BitmapFactory.decodeStream(fileDescriptor.createInputStream());} catch (Exception e) {e.printStackTrace();}} else {Uri uri = Uri.parse(path);if (isHTTP(uri)) {// 网络资源try {URL fileUrl = new URL(path);InputStream is = fileUrl.openConnection().getInputStream();image = BitmapFactory.decodeStream(is);is.close();} catch (IOException e) {e.printStackTrace();}} else {// 存储卡资源try {File file = new File(uri.getPath());if (!file.exists()) {throw new FileNotFoundException();}image = BitmapFactory.decodeFile(uri.getPath());panoramaView.loadImageFromBitmap(image, null);} catch (IOException | InvalidParameterException e) {e.printStackTrace();}}}return image;}@Overrideprotected void onPostExecute(Bitmap bitmap) {super.onPostExecute(bitmap);methodChannel.invokeMethod("onImageLoaded", bitmap == null ? 0 : 1);// 处理回调if (bitmap == null) {Toast.makeText(context, "全景图片加载失败",Toast.LENGTH_LONG).show();return;}panoramaView.loadImageFromBitmap(bitmap, options);}}
}

创建PlatformViewFactory

接下来创建FlutterPanoramaFactory,它继承自PlatformViewFactory:

public class FlutterPanoramaFactory extends PlatformViewFactory {private final BinaryMessenger messenger;private final Context context;FlutterPanoramaFactory(Context context, BinaryMessenger messenger) {super(StandardMessageCodec.INSTANCE);this.context = context;this.messenger = messenger;}@Overridepublic PlatformView create(Context context1, int viewId, Object args) {Map<String, Object> params = (Map<String, Object>) args;// args是由Flutter传过来的自定义参数return new FlutterPanoramaView(this.context, messenger, viewId, params);}
}

在create方法中能够获取到三个参数。context是Android上下文(这里并不是一个Activity的context),viewId是生成组件的ID,args是Flutter端传过来的自定义参数。

注册全景图插件

编写FlutterPanoramaPlugin,它实现了FlutterPlugin。为了获取到Flutter中的Activity的context,同时也实现了ActivityAware。

public class FlutterPanoramaPlugin implements FlutterPlugin, ActivityAware {private FlutterPluginBinding flutterPluginBinding;public static void registerWith(Registrar registrar) {registrar.platformViewRegistry().registerViewFactory("plugins.vincent/panorama",new FlutterPanoramaFactory(registrar.activeContext(), registrar.messenger()));}@Overridepublic void onAttachedToEngine(FlutterPluginBinding binding) {this.flutterPluginBinding = binding;}@Overridepublic void onDetachedFromEngine(FlutterPluginBinding binding) {this.flutterPluginBinding = null;}@Overridepublic void onAttachedToActivity(ActivityPluginBinding binding) {BinaryMessenger messenger = this.flutterPluginBinding.getBinaryMessenger();this.flutterPluginBinding.getPlatformViewRegistry().registerViewFactory("plugins.vincent/panorama", new FlutterPanoramaFactory(binding.getActivity(), messenger));}@Overridepublic void onDetachedFromActivityForConfigChanges() {
//    onDetachedFromActivity();}@Overridepublic void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
//    onAttachedToActivity(binding);}@Overridepublic void onDetachedFromActivity() {}
}

上面代码中使用了plugins.vincent/panorama这样一个字符串,这是组件的注册名称,在Flutter调用时需要用到,你可以使用任意格式的字符串,但是两端必须一致。

在Flutter工程中调用原生View

原生View的调用非常简单,在使用Android平台的view只需要创建AndroidView组件并告诉它组件的注册注册名称即可,可通过creationParams传递参数

return AndroidView(viewType: "plugins.vincent/panorama",creationParams: {"myContent": "通过参数传入的文本内容",},creationParamsCodec: const StandardMessageCodec(),onPlatformViewCreated: (int id) {// 注册MethodChannelMethodChannelPanoramaPlatform(id, callbacksHandler);},);

creationParams传入了一个map参数,并由原生组件接收,creationParamsCodec传入的是一个编码对象这是固定写法。

通过MethodChannel与原生组件通讯

  1. 让原始组件必须要MethodCallHandler接口,
  2. Flutter中创建MethodChannelPanoramaPlatform ,
  3. 在AndroidView创建时的onPlatformViewCreated方法中,去创建MethodChannelPanoramaPlatform 。

通过callbacksHandler回调函数,触发方法。

class MethodChannelPanoramaPlatform {final MethodChannel _channel;final PanoramaPlatformCallbacksHandler _callbacksHandler;MethodChannelPanoramaPlatform(int id, this._callbacksHandler) : assert(_callbacksHandler != null), _channel = MethodChannel('plugins.vincent/panorama_$id') {_channel.setMethodCallHandler(_onMethodCall);}Future _onMethodCall(MethodCall call) async {switch(call.method) {case "onImageLoaded":final int state = call.arguments;_callbacksHandler.onImageLoaded(state);return true;}return null;}
}

封装FlutterPanorama组件,实现跨平台

通过defaultTargetPlatform区分当前平台,然后调用不同的组件。

class FlutterPanorama extends StatelessWidget {final String dataSource;final DataSourceType dataSourceType;final String package;final ImageType imageType;final bool enableInfoButton;final bool enableFullButton;final bool enableStereoModeButton;final ImageLoadedCallback onImageLoaded;/// 自定义回调函数_PlatformCallbacksHandler _platformCallbacksHandler;/// 针对Flutter中Asset资源的构造器FlutterPanorama.assets(this.dataSource, {this.package,this.imageType: ImageType.MEDIA_MONOSCOPIC,this.enableInfoButton,this.enableFullButton,this.enableStereoModeButton,this.onImageLoaded,}) : dataSourceType = DataSourceType.asset, super();/// 针对网络资源的构造器FlutterPanorama.network(this.dataSource, {this.imageType: ImageType.MEDIA_MONOSCOPIC,this.enableInfoButton,this.enableFullButton,this.enableStereoModeButton,this.onImageLoaded,}) : dataSourceType = DataSourceType.network, package = null, super();/// 针对存储卡资源的构造器FlutterPanorama.file(this.dataSource, {this.imageType: ImageType.MEDIA_MONOSCOPIC,this.enableInfoButton,this.enableFullButton,this.enableStereoModeButton,this.onImageLoaded,}) :  dataSourceType = DataSourceType.file, package = null, super();static FlutterPanoramaPlatform _platform;static set platform(FlutterPanoramaPlatform platform) {_platform = platform;}/// 平台区分,返回不同平台的视图static FlutterPanoramaPlatform get platform {if (_platform == null) {switch (defaultTargetPlatform) {case TargetPlatform.android:_platform = AndroidPanoramaView();break;case TargetPlatform.iOS:_platform = IosPanoramaView();break;default:throw UnsupportedError("Trying to use the default panorama implementation for $defaultTargetPlatform but there isn't a default one");}}return _platform;}@overrideWidget build(BuildContext context) {_platformCallbacksHandler = _PlatformCallbacksHandler(this);return FlutterPanorama.platform.build(context,_toCreationParams(),_platformCallbacksHandler);}/// 转换参数Map _toCreationParams() {DataSource dataSourceDescription;switch (dataSourceType) {case DataSourceType.asset:dataSourceDescription = DataSource(sourceType: DataSourceType.asset,asset: dataSource,package: package,);break;case DataSourceType.network:dataSourceDescription = DataSource(sourceType: DataSourceType.network,uri: dataSource);break;case DataSourceType.file:dataSourceDescription = DataSource(sourceType: DataSourceType.file,uri: dataSource,);break;}Map creationParams = dataSourceDescription.toJson();creationParams["imageType"] = this.imageType.index;creationParams["enableInfoButton"] = this.enableInfoButton;creationParams["enableFullButton"] = this.enableFullButton;creationParams["enableStereoModeButton"] = this.enableStereoModeButton;return creationParams;}
}class _PlatformCallbacksHandler implements PanoramaPlatformCallbacksHandler {FlutterPanorama _widget;_PlatformCallbacksHandler(this._widget);@overridevoid onImageLoaded(int state) {_widget.onImageLoaded(state);}
}

使用方法

先在pubspec.yaml中引用

  flutter_panorama:git:url: https://github.com/lytian/flutter_panorama.git

然后就可以使用了

@override
Widgetbuild(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('Plugin example app'),),body: Center(
//          child: FlutterPanorama.assets("images/xishui_pano.jpg"),child: FlutterPanorama.network('https://storage.googleapis.com/vrview/examples/coral.jpg',imageType: ImageType.MEDIA_STEREO_TOP_BOTTOM,onImageLoaded: (state) {print("------------------------------- ${state == 1 ? '图片加载完成' : '图片加载失败'}");},),)),);


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部