“This is the 30th day of my participation in the August Gwen Challenge. For more information: August Gwen Challenge” juejin.cn/post/698796…

Some time ago, I learned about the interaction between Flutter and native Android. Android is the main project, and Flutter acts as Module for interaction. Today’s tip is to try the interaction mode of Flutter embedded in Native View. Android uses AndroidView and iOS uses UiKitView; Xiao CAI only learned the basic usage of AndroidView;

Source code analysis

const AndroidView({
    Key key,
    @required this.viewType,
    this.onPlatformViewCreated,
    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
    this.layoutDirection,
    this.gestureRecognizers,
    this.creationParams,
    this.creationParamsCodec,
})
Copy the code
  1. ViewType -> Unique identifier when interacting with Android native, common form is package name + custom name;
  2. OnPlatformViewCreated -> Callback after creating a view;
  3. Opaque > always > transparent;
  4. LayoutDirection -> Embedded view text direction;
  5. GestureRecognizers -> Set of gestures that can be transferred into your view;
  6. CreationParams -> passes arguments to the view, usually PlatformViewFactory;
  7. CreationParamsCodec -> Codec type;

Basic usage

1. viewType

A. the Android side
  1. Custom PlatformView, according to the requirements to achieve Channel interaction interface;
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() {}
}
Copy the code
  1. Create PlatformViewFactory to generate 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())); }}Copy the code
  1. Register the component in MainActivity;
NLayoutFactory.registerWith(this);
Copy the code
B. Flutter

Create AndroidView and set the same viewType as native;

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)]);Copy the code

C. Relevant summaries
  1. The side dish compares the height of the two containers. When the Container size is larger than the size of the native View corresponding to AndroidView, it will be displayed completely. On the contrary, if it is less than, the native View corresponding to AndroidView will be clipped;
  2. The background color of the two containers is not shown. It is understood that AndroidView is filled with containers, but the display effect in AndroidView is related to the size of the original View.
  3. The unfilled part of the AndroidView will show a white or black background color, depending on the Android theme version or device;

2. creationParams / creationParamsCodec

CreationParams is usually paired with creationParamsCodec. CreationParams is the default pass parameter and creationParamsCodec is the codec type.

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), the 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() {} }Copy the code

3. onPlatformViewCreated

Generally, bridge interaction between Flutter and Android is carried out by means of MethodChannel/BasicMessageChannel/EventChannel. Side dishes to customize TextView to try; PlatformViewFactory is basically the same, just replace the initialized and registered N… TextView can; The custom of N… TextView needs to implement its own Channel mode;

MethodChannel way
// 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 (" I'm a native TextView from Android "); mTextView.setBackgroundColor(Color.rgb(155, 205, 155)); mTextView.setGravity(Gravity.CENTER); MTextView. SetTextSize (16.0 f); 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 ", "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); }}Copy the code
BasicMessageChannel way
// 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.0 f); 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"); }}}Copy the code
The EventChannel way
// 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.0 f); 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) {} }Copy the code

4. gestureRecognizers

Textviews don’t have a set of gestures in them. Click in your app by default, but add gestureRecognizers in ListView if you need sliding gestures or long clicks.

// 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; }}Copy the code

5. hitTestBehavior

Xiao Ai tried data binding and gesture operation, but the most important point is data transparent transmission. Xiao Ai added Toast to Flutter/Android for testing.

a. opaque

Using PlatformViewHitTestBehavior opaque way, both ends can monitor processing, understand the appetizers if AndroidView is not through to the next layer; Note that PlatformView can only be displayed within AndroidView;

b. translucent

Use PlatformViewHitTestBehavior. Translucent, both ends can monitor processing, understand the appetizers if superposition AndroidView can through to the next layer;

c. transparent

Using PlatformViewHitTestBehavior. Transparent way, both ends not passthrough display;

NMethodListView set the height of the Container to 500.0, but the actual remaining screen height is only 300.0, because transparent does not pass through. So the Flutter outer ListView slides, NMethodListView doesn’t slide; In Opaque/opaque/always, the NMethodListView always slides, while the outer ListView of the Flutter always does not slide, and therefore cannot be displayed at a height of 200.0.

summary

  1. Android API > 20 when using AndroidView;
  2. AndroidView requires a bounded parent class;
  3. The official website clearly reminds that AndroidView method costs a lot, because it is GPU -> CPU -> GPU has obvious performance defects, try to avoid using;
  4. Hot overloading is invalid during the test, and recompilation is required each time.

Xiao CAI’s understanding of the interaction between the two sides is not deep enough, especially the understanding of proper nouns is not in place, if there is any problem, please give more guidance!

Source: Little Monk A Ce