Flutter與原生混合開發實現過程

本篇博客不講原理,只講實現方法,從0到1實現 Flutter 和原生的混合開發。將會新建一個 Android 原生項目,取名叫 AndroidDemo,然後新建一個 FlutterModule 項目,取名爲 flutter_module,兩個項目都放在 flutter_boost 目錄下,最後實現兩個項目的混合。實現的功能包括:

  • 安卓原生打開 Flutter 頁面
  • Flutter 打開安卓原生頁面

實現步驟如下:

1 . 新建安卓原生項目 AndroidDemo
2 .打開原生項目,在 AndroidStudio 控制檯中輸入如下命令,用於新建 FlutterModule 項目

cd..	//由於新建的 FlutterModule 項目和 AndroidDemo 項目並列在同級目錄下,退出到主目錄下
flutter create -t module flutter_module	//創建 flutter_module 

控制檯的截圖如下:
在這裏插入圖片描述
最後 flutter_module 創建成功後,在目錄中可以看到如下並列的文件結構:
在這裏插入圖片描述
3 .打開 flutter_module 項目,在控制檯中執行如下指令

cd .android
./gradlew assembleDebug

4 .原生項目 AndroidDemo 的配置

在 app 目錄下的 build.gradle 中添加如下代碼:

compileOptions {
	sourceCompatibility 1.8
	targetCompatibility 1.8
}

並添加 Flutter 依賴:

implementation project(':flutter')

結構圖如下:
在這裏插入圖片描述
最後在 setting.gradle 中添加如下代碼,根據你的項目名稱和路徑自行調整:

setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        '/flutter_module/.android/include_flutter.groovy'
))

原生項目的配置環節到這裏就完成了。

5 .在 AndroidDemo 項目中新建活動,取名爲 FlutterActivity,並添加 MainActivity 代碼和 FlutterActivity 代碼

MainActivity 代碼如下:

package com.example.shinelon.androiddemo;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(MainActivity.this,FlutterActivity.class);
                Bundle bundle = new Bundle();
                bundle.putString("routeName", "first");
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });
    }
}

FlutterActivity 代碼如下:

package com.example.shinelon.androiddemo;

import android.app.NativeActivity;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.FrameLayout;

import io.flutter.facade.Flutter;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.view.FlutterView;

public class FlutterActivity extends AppCompatActivity {

    public static final String CHANNEL_NAME = "com.flutterbus/demo";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 獲取由上一個頁面傳過來的routeName
        String routeName = "";
        Intent intent = getIntent();
        if (intent != null && intent.getExtras() != null) {
            routeName = intent.getExtras().getString("routeName");
        }

        // 根據指定routeName創建FlutterView用來展示對應dart中的Widget
        FlutterView flutterView = Flutter.createView(this, this.getLifecycle(), routeName);

        // 創建Platform Channel用來和Flutter層進行交互
        new MethodChannel(flutterView, CHANNEL_NAME).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                methodCall(methodCall, result);
            }
        });
        setContentView(flutterView);
    }

    /**
     * 處理dart層傳來的方法調用
     */
    private void methodCall(MethodCall call, MethodChannel.Result result) {
        if (call.method.equals("gotoNativePage")) {
            startActivity(new Intent(this, SecondActivity.class));
            result.success(true);
        } else {
            result.notImplemented();
        }
    }

}

FlutterActivity 對應的佈局是一個 FrameLayout ,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FlutterActivity">

    <FrameLayout
        android:id="@+id/flutter_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </FrameLayout>

</android.support.constraint.ConstraintLayout>

6 . flutter_module 項目中 main.dart 代碼入下

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

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'first':
      return MyApp();
    case 'second':
      return MyApp();
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  static const platform = const MethodChannel("com.flutterbus/demo");

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  //調用原生方法
  Future<Null> gotoNativePage() async {
    bool result;
    try {
      //參數爲方法名稱
      result = await platform.invokeMethod("gotoNativePage");
    } on PlatformException catch (e) {
      print(e.message);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            //從flutter進入原生界面
            RaisedButton(
              onPressed: gotoNativePage,
              child: Text("打開原生界面"),
            ),
            //進入flutter界面
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }
}

dart 代碼實現了 flutter 打開原生頁面的功能,對應於原生的 SecondActivity,自己新建一個活動就可以了。

到這裏就已經實現了 Flutter 和原生的混合了,下面是實現頁面跳轉的方法。

安卓原生打開 Flutter 頁面的方法:

Intent intent = new Intent();
intent.setClass(MainActivity.this,FlutterActivity.class);
Bundle bundle = new Bundle();
bundle.putString("routeName", "first");
intent.putExtras(bundle);
startActivity(intent);

Flutter 打開原生頁面的方法,也就是調用原生的方法,然後在原生的方法中打開活動:

dart 代碼調用原生方法

static const platform = const MethodChannel("com.flutterbus/demo");
//函數
Future<Null> gotoNativePage() async {
  bool result;
   try {
     //參數爲方法名稱
     result = await platform.invokeMethod("gotoNativePage");
   } on PlatformException catch (e) {
     print(e.message);
   }
 }

原生方法實現頁面轉跳:

/**
* 處理dart層傳來的方法調用
*/
private void methodCall(MethodCall call, MethodChannel.Result result) {
   if (call.method.equals("gotoNativePage")) {
   	   //進入 SecondActivity
       startActivity(new Intent(this, SecondActivity.class));
       result.success(true);
   } else {
       result.notImplemented();
   }
}

最後編譯安裝 AndroidDemo 項目,看一下實現結果:

打開 App 的第一個界面:
在這裏插入圖片描述
點擊 進入FLUTTER界面 按鈕後進入如下界面,也就是 Flutter 界面:
在這裏插入圖片描述
點擊 打開原生界面 後進入如下界面,也就是我們的 SecondActivity:
在這裏插入圖片描述
這種方法實現混合開發效果不是很好,在原生打開 Flutter 頁面時會出現白屏現象,需要等待一段時間,體驗不是很好。如有問題可以留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章