Flutter中嵌套Android布局

效果

通常Flutter與Android頁面交互是各自獨占整個手機屏幕咳胃,但有些情況下無法滿足需求植康,比如:Flutter頁面中嵌套Android地圖旷太,F(xiàn)lutter中嵌套相機預(yù)覽圖等,這些都需要在手機屏幕中既有Flutter頁面也有Android頁面销睁,有些時候Flutter中沒有提供相關(guān)插件或者插件不滿足需求供璧,這時候就需要開發(fā)者自定義插件,開發(fā)者可以參考本文中的方法去進行自定義冻记。本文具體demo效果如下:

開發(fā)

  1. 首先創(chuàng)建flutter項目睡毒,在項目中定義好flutter需要展示布局:
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Expanded(
            child: Center(
              child: Text(
                'Android按鈕點擊了 $_counter 次',
                style: const TextStyle(fontSize: 17.0)),
            ),
          ),
          Container(
            padding: const EdgeInsets.only(bottom: 15.0, left: 5.0),
            child: Row(
              children: <Widget>[
                Image.asset('assets/flutter-mark-square-64.png', scale: 1.5),
                const Text('Flutter', style: TextStyle(fontSize: 30.0)),
              ],
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _sendFlutterIncrement,
        child: const Icon(Icons.add),
      ),
    );
  }
復(fù)制代碼

上述代碼所呈現(xiàn)的布局就是效果圖中Flutter那一部分(上半部分),設(shè)置FloatingActionButton是為了呈現(xiàn)Flutter與Android的交互過程冗栗, "Android按鈕點擊了 $_counter 次"呈現(xiàn)的是交互結(jié)果演顾,Image.asset()則顯示的是Flutter的logo(標記這是flutter中的布局)。之所以這樣做是因為Flutter與Android頁面相互嵌套通產(chǎn)伴隨著數(shù)據(jù)交互隅居。

  1. 創(chuàng)建Android中的布局:
  <io.flutter.embedding.android.FlutterView
    android:id="@+id/flutter_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    />

  <androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:background="@color/grey"
    >

    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:orientation="vertical"
      >

      <TextView
        android:id="@+id/button_tap"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/button_tap"
        ...
        />

      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/android"
        ...
        />

    </LinearLayout>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
      android:id="@+id/button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
       ...
      />

  </androidx.coordinatorlayout.widget.CoordinatorLayout>
復(fù)制代碼

io.flutter.embedding.android.FlutterView就是需要展示的flutter布局也就是第一步中編寫的布局钠至,剩下的部分和第一步的邏輯是一樣的,有用來展示交互結(jié)果的TextView(@+id/button_tap)胎源,標記Android頁面的TextView(@string/android)棉钧,用來交互的按鈕FloatingActionButton。

  1. 定義通信渠道
  • Flutter:
  static const String _channel = 'increment';
  static const String _pong = 'pong';
  static const String _emptyMessage = '';
  static const BasicMessageChannel<String> platform =
      BasicMessageChannel<String>(_channel, StringCodec());
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    platform.setMessageHandler(_handlePlatformIncrement);
  }

 Future<String> _handlePlatformIncrement(String message) async {
    setState(() {
      _counter++;
    });
    return _emptyMessage;
  }

  void _sendFlutterIncrement() {
    platform.send(_pong);
  }
復(fù)制代碼

代碼中通信渠道使用的是BasicMessageChannel涕蚤,沒有用MethodChannel和EventChannel是因為BasicMessageChannel可以隨時隨地進行任何通信宪卿,而另外兩種則各自有各自的局限性,這里就不做解釋了万栅,稍后會有文章專門介紹這三種通信渠道佑钾。_channel 是通信渠道的名稱,這個是唯一且固定的烦粒,一旦定義好休溶,Android端也要使用相同的名稱,否則兩者無法對接,導(dǎo)致通信失敗邮偎。_pong是Flutter向Android傳遞的消息內(nèi)容管跺,Android每次接收的內(nèi)容為"pong",相應(yīng)的Flutter按鈕點擊次數(shù)就+1禾进,消息內(nèi)容和類型用戶都可以自定義豁跑,只要定義好BasicMessageChannel的泛型和消息編碼機制(文中使用的是StringCodec)即可。如果消息的內(nèi)容比較多泻云,開發(fā)者可以使用Json進行消息傳遞艇拍。_counter是flutter接收Android按鈕的點擊次數(shù),Android按鈕每點擊一次_counter就+1宠纯。相關(guān)變量或常量定義完后卸夕,開發(fā)者需要在initState()中進行消息接收處理,因為BasicMessageChannel是雙向通信婆瓜,platform.setMessageHandler(_handlePlatformIncrement)就是對接收到的消息進行處理快集,_handlePlatformIncrement方法說明了消息的類型是String,消息是異步廉白,_counter+1后調(diào)用setState()刷新布局后相應(yīng)展示的Android按鈕點擊次數(shù)就+1了个初。platform.send(_pong)就是Flutter按鈕點擊完后調(diào)用這個方法,然后BasicMessageChannel將消息發(fā)送到Android猴蹂。

  • Android:
    private static FlutterEngine flutterEngine;
    private FlutterView flutterView;
    private int counter;
    private static final String CHANNEL = "increment";
    private static final String EMPTY_MESSAGE = "";
    private static final String PING = "ping";
    private BasicMessageChannel<String> messageChannel;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            if (flutterEngine == null) {
            flutterEngine = new FlutterEngine(this, args);
            flutterEngine.getDartExecutor().executeDartEntrypoint(
                DartEntrypoint.createDefault()
            );
        }
        ...
        flutterView = findViewById(R.id.flutter_view);
        flutterView.attachToFlutterEngine(flutterEngine);

        messageChannel = new BasicMessageChannel<>(flutterEngine.getDartExecutor(), CHANNEL, StringCodec.INSTANCE);
        messageChannel.
            setMessageHandler(new MessageHandler<String>() {
                @Override
                public void onMessage(String s, Reply<String> reply) {
                    onFlutterIncrement();
                    reply.reply(EMPTY_MESSAGE);
                }
            });

        FloatingActionButton fab = findViewById(R.id.button);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sendAndroidIncrement();
            }
        });
        ...
復(fù)制代碼

CHANNEL 是通信渠道的名稱也是渠道的標識符院溺,一定要和flutter統(tǒng)一,否則無法通信磅轻。BasicMessageChannel是通信渠道珍逸,如果使用了和flutter端不一樣的,也是無法通信的聋溜。EMPTY_MESSAGE是Android端收到Flutter消息后給Flutter的回復(fù)谆膳,表示讓Flutter知道Android收到消息了。Android端收到消息后在setMessageHandler中進行消息處理:將flutter點擊按鈕次數(shù)+1( onFlutterIncrement())勤婚,同時回復(fù)確認收到的消息(reply.reply(EMPTY_MESSAGE))摹量。FloatingActionButton發(fā)生點擊事件后調(diào)用sendAndroidIncrement方法就會向Flutter發(fā)送消息說自己點擊了一次按鈕:

    private void sendAndroidIncrement() {
        messageChannel.send(PING);
    }
復(fù)制代碼

PING就是Android向Flutter發(fā)送的消息內(nèi)容,F(xiàn)lutter收到消息后就對Android點擊按鈕次數(shù)+1馒胆。如果傳遞的消息比較多缨称,還需要對具體的消息進行判斷來確認需要做哪些處理,本文中只傳遞一種內(nèi)容的消息祝迂,所以對消息的參數(shù)和方法沒有做判斷睦尽。代碼中flutterView即是需要展示的Flutter布局,flutterEngine則是flutter引擎(說法不統(tǒng)一)型雳, flutterView.attachToFlutterEngine(flutterEngine)則是為flutterView注入生命和能量当凡,否則flutterView就是空空沒有生命和內(nèi)容的控件山害。flutterEngine和AppCompatActivity的生命周期是綁定在一起:

  @Override
    protected void onResume() {
        super.onResume();
        flutterEngine.getLifecycleChannel().appIsResumed();
    }

    @Override
    protected void onPause() {
        super.onPause();
        flutterEngine.getLifecycleChannel().appIsInactive();
    }

    @Override
    protected void onStop() {
        super.onStop();
        flutterEngine.getLifecycleChannel().appIsPaused();
    }

    @Override
    protected void onDestroy() {
        flutterView.detachFromFlutterEngine();
        super.onDestroy();
    }
復(fù)制代碼

Android中一旦出現(xiàn)了對生命周期的綁定,就是說只要按要求來沿量,就不會出現(xiàn)亂七八糟的問題浪慌,即使有問題也不是它的問題。

總結(jié)

  • Flutter與Android的交互在不斷迭代中已經(jīng)變得比較完善朴则,最新Flutter版本中已經(jīng)比Flutter早期的版中簡單很多权纤。
  • 如果能避免Flutter與Android的相互嵌套就盡量避免,因為兩者的嵌套很耗能乌妒,可能出現(xiàn)卡頓汹想、死機、高耗電等問題撤蚊。

作者:VanGogh
鏈接:https://juejin.im/post/6885918668018941965

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末古掏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子侦啸,更是在濱河造成了極大的恐慌槽唾,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匹中,死亡現(xiàn)場離奇詭異夏漱,居然都是意外死亡,警方通過查閱死者的電腦和手機顶捷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屎篱,“玉大人服赎,你說我怎么就攤上這事〗徊ィ” “怎么了重虑?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秦士。 經(jīng)常有香客問我缺厉,道長,這世上最難降的妖魔是什么隧土? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任提针,我火速辦了婚禮,結(jié)果婚禮上曹傀,老公的妹妹穿的比我還像新娘辐脖。我一直安慰自己,他們只是感情好皆愉,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布嗜价。 她就那樣靜靜地躺著艇抠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪久锥。 梳的紋絲不亂的頭發(fā)上家淤,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音瑟由,去河邊找鬼媒鼓。 笑死,一個胖子當著我的面吹牛错妖,可吹牛的內(nèi)容都是我干的绿鸣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼暂氯,長吁一口氣:“原來是場噩夢啊……” “哼潮模!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起痴施,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤擎厢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辣吃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體动遭,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年神得,在試婚紗的時候發(fā)現(xiàn)自己被綠了厘惦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡哩簿,死狀恐怖宵蕉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情节榜,我是刑警寧澤羡玛,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站宗苍,受9級特大地震影響稼稿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜讳窟,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一让歼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挪钓,春花似錦是越、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浦徊。三九已至,卻和暖如春天梧,著一層夾襖步出監(jiān)牢的瞬間盔性,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工呢岗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冕香,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓后豫,卻偏偏與公主長得像悉尾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挫酿,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353