前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Flutter 专题】58 图解 Flutter 嵌入原生 AndroidView 小尝试

【Flutter 专题】58 图解 Flutter 嵌入原生 AndroidView 小尝试

作者头像
阿策小和尚
发布2019-08-28 15:43:01
2.2K1
发布2019-08-28 15:43:01
举报
文章被收录于专栏:阿策小和尚

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

源码分析

代码语言:javascript
复制
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 交互接口;
代码语言:javascript
复制
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
代码语言:javascript
复制
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 中注册该组件;
代码语言:javascript
复制
NLayoutFactory.registerWith(this);
b. Flutter 端

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

代码语言:javascript
复制
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 为编解码器类型;

代码语言:javascript
复制
// 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 方式
代码语言:javascript
复制
// 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 方式
代码语言:javascript
复制
// 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 方式
代码语言:javascript
复制
// 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 手势集合;

代码语言:javascript
复制
// 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. 测试过程中热重载无效,每次均需重新编译;

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 阿策小和尚 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源码分析
  • 基本用法
    • 1. viewType
      • a. Android 端
      • b. Flutter 端
      • c. 相关小结
    • 2. creationParams / creationParamsCodec
      • 3. onPlatformViewCreated
        • MethodChannel 方式
        • BasicMessageChannel 方式
        • EventChannel 方式
      • 4. gestureRecognizers
        • 5. hitTestBehavior
          • a. opaque
          • b. translucent
          • c. transparent
      • 小结
      相关产品与服务
      GPU 云服务器
      GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于生成式AI,自动驾驶,深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档