Flutter、Android混合開發--Flutter Plugin篇

前言:作為一名大前端開發者,我的技術棧其實一直都很混亂,從Flutter到小程序再到Vue,我都做過不少項目。原因可能是我一直以來都想做大前端的念想,加上以往公司業務方向,讓我對所有前端技術來著不拒。
但是總有一門技術需要鉆精,于是我果斷選擇了Flutter,這半年來也一直在做Flutter的項目;但想要成為Flutter高級開發者,不會原生豈能說的過去,而我的前端剛好也是大學選修Android而入門的;所以我決定開始學習并記錄Flutter與Android混合開發系列文章。與君共勉~

一、Flutter與Android混合開發的類別

先做下總結,整個系列包括:Flutter package(Flutter純Dart庫)、Flutter Plugin(Flutter原生插件庫)、Android混入Flutter模塊。
以下再做細分:

  • package是純dart代碼的庫,供Flutter引用;

  • Plugin是原生插件庫,是一種特殊的package,Plugin涉及這幾個方面:Flutter與原生間相互的數據通信、
    Flutter顯示Android 視圖、
    Flutter跳轉到Android Activity、Android Fragment。

  • Android混入Flutter模塊:通過引入Flutter Module,分別在Android端的Activity和Fragment中顯示Flutter視圖。

二、Flutter Plugin開發詳解

Plugin即原生插件,開發者需要編寫android、iOS雙端的代碼,然后由Flutter層直接調用。由于筆者只學Android,所以iOS端的功能我能省都會省了。另外,在編寫Flutter插件時,官方已經啟用Flutter Engine來管理插件,因此寫法與網上的舊資料也會有所不同。

這篇文章我將實現:Flutter顯示Android view!
下一篇文章再記錄:1. Flutter與Android的通信(包括MethodChannel、BasicMessageChannel、EventChannel)2. Flutter跳轉到原生Activity、Fragment;

1、 Flutter Plugin的創建

若是使用Android studio開發工具的開發者,可以在菜單欄中直接創建一個plugin項目,根據提示一步步選擇即可;
創建Plugin

當然我建議你用命令行創建項目(其中-a指定android的開發語言,-i指定ios的開發語言):

flutter create --org com.example --template=plugin --platforms=android,ios -a java -i swift hello

創建好后的目錄結構如圖所示:
Plugin目錄結構

2. Flutter顯示Android View

  1. 打開android studio模塊,以便更快捷的編寫代碼;
  2. 可以看到有兩個android module,其中我們要編寫的是插件的模塊;
    module目錄結構
  3. 我們需要創建一個原生視圖布局,在包名下面創建一個新的視圖,這里我只是簡單構建了一個Text控件,這部分屬于原生的能力,筆者也不太熟悉。
package com.karl.wei.my_camera;

import android.content.Context;
import android.view.View;
import android.widget.TextView;

import java.util.Map;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.platform.PlatformView;

public class MyInfoView implements PlatformView {

    private final TextView name;


    public MyInfoView(Context context, BinaryMessenger messenger, int id, Map<String, Object> params){
        TextView textView = new TextView(context);
        textView.setText("正在加載...");

        this.name = textView;

        if(params!=null&&params.containsKey("content")){
            String t = (String)params.get("content");
            textView.setText(t);
        }

    }
    @Override
    public View getView() {
        return name;
    }

    @Override
    public void dispose() {

    }
}
  1. 之后需要構建一個視圖工廠的類,提供給Engine進行視圖的注冊,其中create生命周期需要retrun對應的視圖文件。
import android.content.Context;

import com.karl.wei.my_camera.MyInfoView;

import java.util.Map;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;

public class MyViewFactory extends PlatformViewFactory {

    private final BinaryMessenger messenger;

    public MyViewFactory(BinaryMessenger messenger) {
        super(StandardMessageCodec.INSTANCE);
        this.messenger = messenger;
    }

    @Override
    @SuppressWarnings("unchecked")
    public PlatformView create(Context context, int id, Object o) {
        Map<String, Object> params = (Map<String, Object>) o;
        return new MyInfoView(context, messenger, id, params);
    }
}

  1. 然后需要在插件的類中,注冊我們要返回的視圖,注冊時需要傳入唯一標識和視圖工廠,每一個標識對應一個視圖工廠。
  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    // 需要注冊的視圖的唯一標識
    final String key = "karl_info";
    // 創建MethodChannel通道,my_camera與yaml的name是需要對應的
    channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "my_camera");
    channel.setMethodCallHandler(this);
    // 注冊原生view,通過注冊視圖工廠(ViewFactory),需要傳入唯一標識和ViewFactory類
    flutterPluginBinding.getPlatformViewRegistry().registerViewFactory(key, new MyViewFactory(flutterPluginBinding.getBinaryMessenger()));
  }

下面是3、4、5步的文件路徑截圖
  1. 到這里,原生視圖算是簡單編寫完成;接下來還需要Flutter將我們再原生的視圖給暴露出去,讓純Flutter項目可以簡單的接入我們的視圖。回到plugin目錄下,我們新建一個dart文件,通過Flutter提供的AndroidView返回我們的原生布局,AndroidView接收一個viewType參數,這個參數就是試圖的唯一標識,跟第5點相對應。
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class InfoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    String viewType = 'karl_info'; // 唯一標識符
    var creationParams = {
      'type': 'info_view',
    }; // 視圖創建參數,可以被插件用來傳遞構造函數參數到嵌入式Android視圖
    // 視圖創建完畢的回調
    PlatformViewCreatedCallback callback = (id) {
      print(id);
    };
    // 判斷設備類型,也可用:defaultTargetPlatform == TargetPlatform.android
    if (Platform.isAndroid) {
      return AndroidView(
        viewType: viewType,
        onPlatformViewCreated: callback,
      );
    } else if (Platform.isIOS) {
      return UiKitView(
        viewType: viewType,
        onPlatformViewCreated: callback,
      );
    } else {
      return Text('Platform is not yet supported by this plugin');
    }
  }
}

  1. 現在,Flutter端如何使用我們的視圖?如果由于某些問題,你的插件只能本地引用的時候,首先在yaml配置文件中引入插件,然后在項目的android中主文件注冊插件。
dependencies:
  flutter:
    sdk: flutter

  my_camera:
    path: ../
package io.flutter.plugins;

import androidx.annotation.Keep;
import androidx.annotation.NonNull;

import io.flutter.embedding.engine.FlutterEngine;

/**
 * Generated file. Do not edit.
 * This file is generated by the Flutter tool based on the
 * plugins that support the Android platform.
 */
@Keep
public final class GeneratedPluginRegistrant {
  public static void registerWith(@NonNull FlutterEngine flutterEngine) {
    flutterEngine.getPlugins().add(new com.karl.wei.my_camera.MyCameraPlugin());
  }
}

如果你開發過flutter項目,到這里你應該會疑惑,以前項目引入那么多的開源插件,都不需要手動注冊插件呀,怎么到這里就需要了?其實,當我們執行flutter pub get時,編譯器就自動為我們加入相應插件的注冊了,不信你看看看這個文件,嘿嘿!

  1. 然后在你的項目中,需要的地方引用即可
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Container(
          child: Center(
            child: InfoWidget(),
          ),
        ),
      ),
    );
  }

三、總結

梳理下Flutter Plugin顯示Android View的邏輯:

① 創建完插件后,在原生層新建一個文件繼承自PlatformView,編寫視圖內容;
② PlatformViewFactory創建一個視圖工廠繼承自PlatformViewFactory,其中create生命周期返回對應的視圖文件;
③ 在插件主文件中注冊視圖,并定義好唯一標識;
④ 在Flutter層使用AndroidView通過傳入③中定義好的標識,通過視圖工廠取出視圖;
⑤ Flutter使用者調用即可。

另外,原生視圖同樣可以傳遞參數,跟view的層層傳遞是一起完成的。如AndroidView中是傳遞creationParams,PlatformViewFactory在create中接收并傳遞給真正的PlatformView,這里不做拓展。

四、寫在最后

其實Flutter顯示原生View筆者認為沒有太大意義,因為Flutter本身就是一個非常完美的UI框架,既然選擇使用Flutter,其實用到原生視圖的地方就比較少了。重點將在后續章節:Flutter與原生通信、Flutter調整Activity等功能的學習。

這篇文章僅僅是系列的開端,筆者也是邊學邊記錄,希望能幫到大家,一起學習一起努力。
希望大家多多指導我!

后會有期

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容