Flutter 专题58 图解 Flutter 嵌入原生 AndroidView 小尝试 #yyds干货盘点#

Posted 阿策小和尚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 专题58 图解 Flutter 嵌入原生 AndroidView 小尝试 #yyds干货盘点#相关的知识,希望对你有一定的参考价值。

      小菜前段时间学习了一下 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 端
  8. 自定义 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() 
    
  9. 创建 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()));
    
    
  10. 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
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Flutter** 与 **Android** 交互一般借助 **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) 

![5803.png](https://s2.51cto.com/images/20220208/1644287945925662.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

#### 4. gestureRecognizers
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;针对不同的 **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;


![5804.png](https://s2.51cto.com/images/20220208/1644287964274467.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

#### 5. hitTestBehavior
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;小菜尝试了数据绑定和手势操作,但重要的一点是数据透传,小菜在 **Flutter / Android** 两端添加了 **Toast** 进行测试;
##### a. opaque
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;使用 **PlatformViewHitTestBehavior.opaque** 方式,两端均可监听处理,小菜理解,若有叠加 **AndroidView** 则不会透传到下一层;注意 **PlatformView** 只可在 **AndroidView** 范围内展示;
##### b. translucent
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;使用 **PlatformViewHitTestBehavior.translucent** 方式,两端均可监听处理,小菜理解,若有叠加 **AndroidView** 则可以透传到下一层;
![5805.jpeg](https://s2.51cto.com/images/20220208/1644287973256626.jpeg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

##### c. transparent
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;使用 **PlatformViewHitTestBehavior.transparent** 方式,两端均不会透传展示;
![5806.jpeg](https://s2.51cto.com/images/20220208/1644287981499660.jpeg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;小菜在测试时,**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. 测试过程中热重载无效,每次均需重新编译;
***
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;小菜对两端的交互理解还不够深入,尤其是专有名词的理解还不到位,如有问题请多多指导!

>  来源:阿策小和尚

以上是关于Flutter 专题58 图解 Flutter 嵌入原生 AndroidView 小尝试 #yyds干货盘点#的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 专题49 图解 Flutter 与 Android 原生交互 #yyds干货盘点#

Flutter 专题63 图解 Flutter 集成极光 JPush 小结 #yyds干货盘点#

Flutter 专题54 图解基本生命周期 #yyds干货盘点#

Flutter 专题01 图解 Windows 环境下安装配置环境

Flutter 专题02 图解 Mac 环境下安装配置环境 #yyds干货盘点#

Android:Flutter 专题03 图解第一个程序 Hello World