您现在的位置是:首页 > 文章详情

Flutter 58: 图解 Flutter 嵌入原生 AndroidView 小尝试

日期:2019-08-25点击:375

      小菜前段时间学习了一下 Flutter 与原生 Android 之间的交互;是以 Android 为主工程,Flutter 作为 Module 方式进行交互;今天小菜尝试一下 Flutter 中嵌入 Native View 的交互方式;Android 端采用 AndroidView iOS 端采用 UiKitView;小菜仅学习了 AndroidView 的基本用法;

源码分析

const AndroidView({ Key key, @required this.viewType, this.onPlatformViewCreated, this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, this.layoutDirection, this.gestureRecognizers, this.creationParams, this.creationParamsCodec, })
  1. viewType ->Android 原生交互时唯一标识符,常见形式是包名+自定义名;
  2. onPlatformViewCreated -> 创建视图后的回调;
  3. hitTestBehavior -> 渗透点击事件,接收范围 opaque > translucent > transparent
  4. layoutDirection -> 嵌入视图文本方向;
  5. gestureRecognizers -> 可以传递到视图的手势集合;
  6. creationParams -> 向视图传递参数,常为 PlatformViewFactory
  7. creationParamsCodec -> 编解码器类型;

基本用法

1. viewType

a. Android 端
  1. 自定义 PlatformView,可根据需求实现 Channel 交互接口;
public class NLayout implements PlatformView { private LinearLayout mLinearLayout; private BinaryMessenger messenger; NLayout(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) { this.messenger = messenger; LinearLayout mLinearLayout = new LinearLayout(context); mLinearLayout.setBackgroundColor(Color.rgb(100, 200, 100)); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(900, 900); mLinearLayout.setLayoutParams(lp); this.mLinearLayout = mLinearLayout; } @Override public View getView() { return mLinearLayout; } @Override public void dispose() {} }
  1. 创建 PlatformViewFactory 用于生成 PlatformView
public class NLayoutFactory extends PlatformViewFactory { private final BinaryMessenger messenger; public NLayoutFactory(BinaryMessenger messenger) { super(StandardMessageCodec.INSTANCE); this.messenger = messenger; } @Override public PlatformView create(Context context, int i, Object o) { Map<String, Object> params = (Map<String, Object>) o; return new NLayout(context, messenger, i, params); } public static void registerWith(PluginRegistry registry) { final String key = "NLayout"; if (registry.hasPlugin(key)) return; PluginRegistry.Registrar registrar = registry.registrarFor(key); registrar.platformViewRegistry().registerViewFactory("com.ace.ace_demo01/method_layout", new NLayoutFactory(registrar.messenger())); } }
  1. MainActivity 中注册该组件;
NLayoutFactory.registerWith(this);
b. Flutter 端

      创建 AndroidView 并设置与原生相同的 viewType

return ListView(children: <Widget>[ Container(child: AndroidView(viewType: "com.ace.ace_demo01/method_layout"), color: Colors.pinkAccent, height: 400.0), Container(child: AndroidView(viewType: "com.ace.ace_demo01/method_layout"), color: Colors.greenAccent, height: 200.0) ]);

c. 相关小结
  1. 小菜对比两个 Container 高度,Container 尺寸大于 AndroidView 对应的原生 View 尺寸时,完全展示;相反小于时则会裁剪 AndroidView 对应的原生 View
  2. 两个 Container 背景色均未展示,小菜理解是 AndroidView 是填充满 Container 的,只是 AndroidView 中展示效果跟原生 View 尺寸相关;
  3. AndroidView 中未填充满的部分会展示白色或黑色背景色,与 Android 主题版本设备 相关;

2. creationParams / creationParamsCodec

      creationParamscreationParamsCodec 一般成对使用,creationParams 为默认传递参数,creationParamsCodec 为编解码器类型;

// Flutter 端 默认传递不同尺寸参数 return ListView(children: <Widget>[ Container(child: AndroidView(viewType: "com.ace.ace_demo01/method_layout0", creationParamsCodec: const StandardMessageCodec(), creationParams: {'method_layout_size': 150}), color: Colors.pinkAccent,height: 400.0), Container(child: AndroidView(viewType: "com.ace.ace_demo01/method_layout0", creationParamsCodec: const StandardMessageCodec(), creationParams: {'method_layout_size': 450}), color: Colors.greenAccent,height: 200.0) ]); // Android NLayout public class NLayout implements PlatformView { private LinearLayout mLinearLayout; private BinaryMessenger messenger; private int size = 0; NLayout(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) { this.messenger = messenger; LinearLayout mLinearLayout = new LinearLayout(context); mLinearLayout.setBackgroundColor(Color.rgb(100, 200, 100)); if (params != null && params.containsKey("method_layout_size")) { size = Integer.parseInt(params.get("method_layout_size").toString()); } else { size = 900; } LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(size,size); mLinearLayout.setLayoutParams(lp); this.mLinearLayout = mLinearLayout; } @Override public View getView() { return mLinearLayout; } @Override public void dispose() {} }

3. onPlatformViewCreated

      FlutterAndroid 交互一般借助 MethodChannel / BasicMessageChannel / EventChannel 三种方式进行桥接交互;小菜以自定义 TextView 进行尝试;PlatformViewFactory 基本一致,只是更换初始化和注册的 N...TextView 即可;自定义 N...TextView 需实现各自的 Channel 方式;

MethodChannel 方式
// Flutter 端 return Container(height: 80.0, child: AndroidView( onPlatformViewCreated: (id) async { MethodChannel _channel = const MethodChannel('ace_method_text_view'); _channel..invokeMethod('method_set_text', 'Method_Channel')..setMethodCallHandler((call) { if (call.method == 'method_click') { _toast('Method Text FlutterToast!', context); } }); }, viewType: "com.ace.ace_demo01/method_text_view", creationParamsCodec: const StandardMessageCodec(), creationParams: {'method_text_str': 'Method Channel Params!!'})); // Android NMethodTextView public class NMethodTextView implements PlatformView, MethodChannel.MethodCallHandler { private TextView mTextView; private MethodChannel methodChannel; private BinaryMessenger messenger; NMethodTextView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) { this.messenger = messenger; TextView mTextView = new TextView(context); mTextView.setText("我是来自Android的原生TextView"); mTextView.setBackgroundColor(Color.rgb(155, 205, 155)); mTextView.setGravity(Gravity.CENTER); mTextView.setTextSize(16.0f); if (params != null && params.containsKey("method_text_str")) { mTextView.setText(params.get("method_text_str").toString()); } this.mTextView = mTextView; methodChannel = new MethodChannel(messenger, "ace_method_text_view"); methodChannel.setMethodCallHandler(this); mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { methodChannel.invokeMethod("method_click", "点击!"); Toast.makeText(context, "Method Click NativeToast!", Toast.LENGTH_SHORT).show(); } }); } @Override public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { if (methodCall != null && methodCall.method.toString().equals("method_set_text")) { mTextView.setText(methodCall.arguments.toString()); result.success("method_set_text_success"); } } @Override public View getView() { return mTextView; } @Override public void dispose() { methodChannel.setMethodCallHandler(null); } }
BasicMessageChannel 方式
// Flutter 端 return Container(height: 80.0, child: AndroidView( hitTestBehavior: PlatformViewHitTestBehavior.translucent, onPlatformViewCreated: (id) async { BasicMessageChannel _channel = const BasicMessageChannel('ace_basic_text_view', StringCodec()); _channel..send("Basic_Channel")..setMessageHandler((message) { if (message == 'basic_text_click') { _toast('Basic Text FlutterToast!', context); } print('===${message.toString()}=='); }); }, viewType: "com.ace.ace_demo01/basic_text_view", creationParamsCodec: const StandardMessageCodec(), creationParams: {'basic_text_str': 'Basic Channel Params!!'})); // Android NBasicTextView public class NBasicTextView implements PlatformView, BasicMessageChannel.MessageHandler { private TextView mTextView; private BasicMessageChannel basicMessageChannel; private BinaryMessenger messenger; NBasicTextView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) { this.messenger = messenger; TextView mTextView = new TextView(context); mTextView.setTextColor(Color.rgb(155, 155, 205)); mTextView.setBackgroundColor(Color.rgb(155, 105, 155)); mTextView.setGravity(Gravity.CENTER); mTextView.setTextSize(18.0f); if (params != null && params.containsKey("basic_text_str")) { mTextView.setText(params.get("basic_text_str").toString()); } this.mTextView = mTextView; basicMessageChannel = new BasicMessageChannel(messenger, "ace_basic_text_view", StringCodec.INSTANCE); basicMessageChannel.setMessageHandler(this); mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { basicMessageChannel.send("basic_text_click"); Toast.makeText(context, "Basic Click NativeToast!", Toast.LENGTH_SHORT).show(); } }); } @Override public View getView() { return mTextView; } @Override public void dispose() { basicMessageChannel.setMessageHandler(null); } @Override public void onMessage(Object o, BasicMessageChannel.Reply reply) { if (o != null){ mTextView.setText(o.toString()); basicMessageChannel.send("basic_set_text_success"); } } }
EventChannel 方式
// Flutter 端 return Container(height: 80.0, child: AndroidView( hitTestBehavior: PlatformViewHitTestBehavior.opaque, onPlatformViewCreated: (id) async { EventChannel _channel = const EventChannel('ace_event_text_view'); _channel.receiveBroadcastStream('Event_Channel').listen((message) { if (message == 'event_text_click') { _toast('Event Text FlutterToast!', context); } }); }, viewType: "com.ace.ace_demo01/event_text_view", creationParamsCodec: const StandardMessageCodec(), creationParams: {'event_text_str': 'Event Channel Params!!'})); // Android EventChannel public class NEventTextView implements PlatformView, EventChannel.StreamHandler { private TextView mTextView; private EventChannel eventChannel; private BinaryMessenger messenger; NEventTextView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) { this.messenger = messenger; TextView mTextView = new TextView(context); mTextView.setTextColor(Color.rgb(250, 105, 25)); mTextView.setBackgroundColor(Color.rgb(15, 200, 155)); mTextView.setGravity(Gravity.CENTER); mTextView.setPadding(10, 10, 10, 10); mTextView.setTextSize(20.0f); if (params != null && params.containsKey("event_text_str")) { mTextView.setText(params.get("event_text_str").toString()); } this.mTextView = mTextView; eventChannel = new EventChannel(messenger, "ace_event_text_view"); eventChannel.setStreamHandler(this); mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context, "Event Click NativeToast!", Toast.LENGTH_SHORT).show(); } }); } @Override public View getView() { return mTextView; } @Override public void dispose() { eventChannel.setStreamHandler(null); } @Override public void onListen(Object o, EventChannel.EventSink eventSink) { if (o != null) { mTextView.setText(o.toString()); eventSink.success("event_set_text_success"); } } @Override public void onCancel(Object o) {} }

4. gestureRecognizers

      针对不同的 View 需要的手势有所不同,上述 TextView 没有设置手势集合,默认支持点击,但对于 ListView 之类的需要滑动手势或长按点击的话则需要添加 gestureRecognizers 手势集合;

// Flutter 端 return Container(height: 480.0, child: GestureDetector( child: AndroidView( gestureRecognizers: Set()..add(Factory<VerticalDragGestureRecognizer>(() => VerticalDragGestureRecognizer())) ..add(Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer())), hitTestBehavior: PlatformViewHitTestBehavior.opaque, onPlatformViewCreated: (id) async { MethodChannel _channel = const MethodChannel('ace_method_list_view'); _channel..invokeMethod('method_set_list', 15)..setMethodCallHandler((call) { if (call.method == 'method_item_click') { _toast('List FlutterToast! position -> ${call.arguments}', context); } else if (call.method == 'method_item_long_click') { _toast('List FlutterToast! -> ${call.arguments}', context); } }); }, viewType: "com.ace.ace_demo01/method_list_view", creationParamsCodec: const StandardMessageCodec(), creationParams: {'method_list_size': 10}))); // Android NMethodListView public class NMethodListView implements PlatformView, MethodChannel.MethodCallHandler, ListView.OnItemClickListener, ListView.OnItemLongClickListener { private Context context; private ListView mListView; private MethodChannel methodChannel; private List<Map<String, String>> list = new ArrayList<>(); private SimpleAdapter simpleAdapter = null; private int listSize = 0; NMethodListView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params) { this.context = context; ListView mListView = new ListView(context); if (params != null && params.containsKey("method_list_size")) { listSize = Integer.parseInt(params.get("method_list_size").toString()); } if (list != null) { list.clear(); } for (int i = 0; i < listSize; i++) { Map<String, String> map = new HashMap<>(); map.put("id", "current item = " + (i + 1)); list.add(map); } simpleAdapter = new SimpleAdapter(context, list, R.layout.list_item, new String[] { "id" }, new int[] { R.id.item_tv }); mListView.setAdapter(simpleAdapter); mListView.setOnItemClickListener(this); mListView.setOnItemLongClickListener(this); this.mListView = mListView; methodChannel = new MethodChannel(messenger, "ace_method_list_view"); methodChannel.setMethodCallHandler(this); } @Override public View getView() { return mListView; } @Override public void dispose() { methodChannel.setMethodCallHandler(null); } @Override public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { if (methodCall != null && methodCall.method.toString().equals("method_set_list")) { if (list != null) { list.clear(); } for (int i = 0; i < Integer.parseInt(methodCall.arguments.toString()); i++) { Map<String, String> map = new HashMap<>(); map.put("id", "current item = " + (i + 1)); list.add(map); } simpleAdapter.notifyDataSetChanged(); result.success("method_set_list_success"); } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { methodChannel.invokeMethod("method_item_click", position); Toast.makeText(context, "ListView.onItemClick NativeToast! position -> " + position, Toast.LENGTH_SHORT).show(); } @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { methodChannel.invokeMethod("method_item_long_click", list.get(position).get("id")); Toast.makeText(context, "ListView.onItemLongClick NativeToast! " + list.get(position).get("id"), Toast.LENGTH_SHORT).show(); return true; } }

5. hitTestBehavior

      小菜尝试了数据绑定和手势操作,但重要的一点是数据透传,小菜在 Flutter / Android 两端添加了 Toast 进行测试;

a. opaque

      使用 PlatformViewHitTestBehavior.opaque 方式,两端均可监听处理,小菜理解,若有叠加 AndroidView 则不会透传到下一层;注意 PlatformView 只可在 AndroidView 范围内展示;

b. translucent

      使用 PlatformViewHitTestBehavior.translucent 方式,两端均可监听处理,小菜理解,若有叠加 AndroidView 则可以透传到下一层;

c. transparent

      使用 PlatformViewHitTestBehavior.transparent 方式,两端均不会透传展示;

      小菜在测试时,NMethodListView 设置高度超过剩余空间高度,例 Container 高度设置 500.0 可实际屏幕剩余高度只有 300.0,因 transparent 不会透传,所以 Flutter 外层 ListView 可以滑动,NMethodListView 不会滑动;使用 opaque / translucent 方式,NMethodListView 可以滑动,Flutter 外层 ListView 不能滑动,故有 200.0 高度展示不出来;

小结

  1. 使用 AndroidView 时,Android API > 20
  2. 使用 AndroidView 时均需要有界父类;
  3. 官网明确提醒,AndroidView 方式代价较大,由于是 GPU -> CPU -> GPU 有明显的性能缺陷,尽量避免使用;
  4. 测试过程中热重载无效,每次均需重新编译;

      小菜对两端的交互理解还不够深入,尤其是专有名词的理解还不到位,如有问题请多多指导!

来源:阿策小和尚

原文链接:https://yq.aliyun.com/articles/715915
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章