所有源碼已上傳githubphp
這部分的核心是須要你在Flutter程序的入口添加路由,Android會從Flutter設置的路由裏找須要的頁面做爲View嵌入到Activity裏,若是Flutter程序的入口同時配置了routes和home,則優先使用routes裏配置的路由,當經過routes和home都沒有找到路由的時候,纔會從onUnknownRoute尋找。android
window.defaultRouteName
就是Activity裏傳遞的路由名稱git
Activity跳轉到Flutter其實就是在Activity裏嵌入FlutterView而後再跳轉到這個包含FlutterView的Activity,分別爲下面4種方式github
方法3和方法4是最簡單的一種顯示FlutterView的方式,不須要本身建立FlutterView,由於內部已經幫你建立好了。也不須要建立包含FlutterView的Fragment。而且可讓Flutter與FlutterActivity有交互功能。可是直接跳轉FlutterFragmentActivity或FlutterActivity會有短暫的黑屏,優化方式在下面的代碼裏我也寫了,和其餘頁面的方式優化方式是同樣的,都是獲取到FlutterView而後調用一下enableTransparentBackground()
方法。方法3和方法4的惟一區別就是方法4繼承的是v4包的FragmentActivity,而方法3繼承的是普通的Activity。json
我寫了點內容,可是仍是直接發完整代碼看起來更方便更容易理解,因此就不寫多餘的文字了,直接把完整代碼放出來app
package com.liuhc.myapplication
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.startActivity
/** * 描述:首頁 * 做者:liuhc * 建立日期:2019-09-05 on 11:21 AM */
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//addContentView方式添加FlutterView
page1.setOnClickListener {
startActivity<Page1Activity>()
}
//普通Fragment方式添加FlutterView
page2.setOnClickListener {
startActivity<Page2Activity>()
}
//使用FlutterFragmentActivity
page3.setOnClickListener {
startActivity<PageFlutterFragmentActivity>()
}
//使用FlutterActivity
page4.setOnClickListener {
startActivity<PageFlutterActivity>()
}
//addContentView方式添加FlutterView並傳遞參數
page1Param.setOnClickListener {
startActivity<Page1ParamActivity>()
}
//解決debug模式下黑屏的另外一種方式
noBlack.setOnClickListener {
startActivity<DebugNoBlackActivity>()
}
}
}
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity">
<Button android:id="@+id/page1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="addContentView方式添加FlutterView" android:textAllCaps="false" />
<Button android:id="@+id/page2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="普通Fragment方式添加FlutterView" android:textAllCaps="false" />
<Button android:id="@+id/page3" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="使用FlutterFragmentActivity" android:textAllCaps="false" />
<Button android:id="@+id/page4" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="使用FlutterActivity" android:textAllCaps="false" />
<Button android:id="@+id/page1Param" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="addContentView方式添加FlutterView並傳遞參數" android:textAllCaps="false" />
<Button android:id="@+id/noBlack" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="解決debug模式下黑屏的另外一種方式" android:textAllCaps="false" />
</LinearLayout>
複製代碼
package com.liuhc.myapplication
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.ViewGroup
import android.widget.FrameLayout
import io.flutter.facade.Flutter
/** * 描述:addContentView方式添加FlutterView * 做者:liuhc * 建立日期:2019-09-05 on 11:21 AM */
class Page1Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//下面這個setContentView添加不添加的都不要緊
//若是添加了,在debug模式下FlutterView顯示有點慢因此會先顯示activity_page1再顯示FlutterView
//而release模式下速度會很快,看不到activity_page1,會直接顯示FlutterView
setContentView(R.layout.activity_page1)
//Activity嵌入FlutterView的方式1
val flutterView = Flutter.createView(
this,
lifecycle,
"page1"
)
//這句話的做用是在debug模式下去除啓動Flutter頁面的時候的黑屏
flutterView.enableTransparentBackground()
val flutterLayoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
flutterLayoutParams.leftMargin = 0
//將下面的值設置爲100就能夠在FlutterView加載完之後仍然看到TextView
flutterLayoutParams.topMargin = 0
//由於flutterLayoutParams是佔滿全屏,因此調用addContentView會將flutterView放到R.layout.activity_page1上層
//效果看起來就是flutterView替換了R.layout.activity_page1
addContentView(flutterView, flutterLayoutParams)
//可是若是上面直接使用setContentView就看不到過渡效果了,也就是看不到activity_page1也就看不到TextView顯示的文字了
}
}
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".Page1Activity">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="FlutterView正在加載中..." />
</RelativeLayout>
複製代碼
package com.liuhc.myapplication
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.flutter.facade.Flutter
/** * 描述:普通Fragment方式添加FlutterView * 做者:liuhc * 建立日期:2019-09-05 on 11:21 AM */
class Page2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_page2)
//Activity嵌入Flutter View的方式2
val tx = supportFragmentManager.beginTransaction()
//下面2種方式均可以,只不過方式2代碼裏調用了flutterView.enableTransparentBackground(),因此在debug模式下不會閃爍黑屏
//方式1
tx.replace(R.id.frameLayout, Flutter.createFragment("page2"))
//方式2
tx.replace(R.id.frameLayout, Page2Fragment())
tx.commit()
}
class Page2Fragment : Fragment() {
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? {
val flutterView = Flutter.createView(activity!!, lifecycle, "page2")
//這句話的做用是在debug模式下去除啓動Flutter頁面的時候的黑屏
flutterView.enableTransparentBackground()
return flutterView
}
}
}
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Page2Activity">
<FrameLayout android:id="@+id/frameLayout" android:layout_width="match_parent" android:layout_height="match_parent" />
</LinearLayout>
複製代碼
package com.liuhc.myapplication
import android.os.Bundle
import android.util.Log
import io.flutter.app.FlutterFragmentActivity
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.FlutterMain
import org.json.JSONObject
/** * 描述: * 做者:liuhc * 建立日期:2019-09-04 on 21:07 */
class PageFlutterFragmentActivity : FlutterFragmentActivity() {
private lateinit var methodChannel: MethodChannel
override fun onCreate(savedInstanceState: Bundle?) {
//強烈建議放到Application裏初始化,初始化一次便可,放這裏只是舉個例子
FlutterMain.startInitialization(this)
//intent的參數設置必須在super.onCreate以前,由於super.onCreate裏會取這些參數
intent.action = "android.intent.action.RUN"
val channelName = "channelName_PageFlutterFragmentActivity"
val androidMethod = "methodName_PageFlutterFragmentActivity"
val jsonObject = JSONObject()
jsonObject.put("path", "InvokeMethodPage")
jsonObject.put("title", "PageFlutterFragmentActivity")
jsonObject.put("channelName", channelName)
jsonObject.put("androidMethod", androidMethod)
intent.putExtra("route", jsonObject.toString())
super.onCreate(savedInstanceState)
//調用super.onCreate(savedInstanceState)以後flutterView纔有值,
//因此若是須要註冊插件,則應該放到super.onCreate(savedInstanceState)代碼以後才能夠
flutterView.enableTransparentBackground()
//若是不須要平臺交互的話,只須要上面的代碼並在最後加上super.onCreate就能夠了
//這裏this.registrarFor方法實際調用的是FlutterFragmentActivity裏的delegate的registrarFor方法,
//而delegate的registrarFor方法實際調用的是FlutterActivityDelegate裏的flutterView.getPluginRegistry().registrarFor方法,
//而FlutterActivityDelegate裏的flutterView在調用了這裏的super.onCreate(savedInstanceState)纔有值,
//因此若是須要註冊插件,則應該放到super.onCreate(savedInstanceState)代碼以後才能夠
val key = "PageFlutterFragmentActivity"
if (this.hasPlugin(key)) return
val registrar = this.registrarFor(key)
methodChannel = MethodChannel(
registrar.messenger(),
channelName
)
methodChannel.setMethodCallHandler { methodCall, result ->
if (methodCall.method == androidMethod) {
Log.e("Android", "接收到了Flutter傳遞的參數:${methodCall.arguments}")
result.success("$androidMethod ok")
Log.e("Android", "主動調用Flutter的methodInvokeMethodPageState方法")
methodChannel.invokeMethod("methodInvokeMethodPageState", "Android發送給Flutter的參數")
}
}
}
}
複製代碼
package com.liuhc.myapplication
import android.os.Bundle
import android.util.Log
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.MethodChannel
import io.flutter.view.FlutterMain
import org.json.JSONObject
/** * 描述: * 做者:liuhc * 建立日期:2019-09-04 on 23:30 */
class PageFlutterActivity : FlutterActivity() {
private lateinit var methodChannel: MethodChannel
override fun onCreate(savedInstanceState: Bundle?) {
//強烈建議放到Application裏初始化,初始化一次便可,放這裏只是舉個例子
FlutterMain.startInitialization(this)
//intent的參數設置必須在super.onCreate以前,由於super.onCreate裏會取這些參數
intent.action = "android.intent.action.RUN"
val channelName = "channelName_PageFlutterActivity"
val androidMethod = "methodName_PageFlutterActivity"
val jsonObject = JSONObject()
jsonObject.put("path", "InvokeMethodPage")
jsonObject.put("title", "PageFlutterActivity")
jsonObject.put("channelName", channelName)
jsonObject.put("androidMethod", androidMethod)
intent.putExtra("route", jsonObject.toString())
super.onCreate(savedInstanceState)
//調用super.onCreate(savedInstanceState)以後flutterView纔有值,
//因此若是須要註冊插件,則應該放到super.onCreate(savedInstanceState)代碼以後才能夠
flutterView.enableTransparentBackground()
//若是不須要平臺交互的話,只須要上面的代碼並在最後加上super.onCreate就能夠了
//這裏this.registrarFor方法實際調用的是FlutterFragmentActivity裏的delegate的registrarFor方法,
//而delegate的registrarFor方法實際調用的是FlutterActivityDelegate裏的flutterView.getPluginRegistry().registrarFor方法,
//而FlutterActivityDelegate裏的flutterView在調用了這裏的super.onCreate(savedInstanceState)纔有值,
//因此若是須要註冊插件,則應該放到super.onCreate(savedInstanceState)代碼以後才能夠
val key = "PageFlutterActivity"
if (this.hasPlugin(key)) return
val registrar = this.registrarFor(key)
methodChannel = MethodChannel(
registrar.messenger(),
channelName
)
methodChannel.setMethodCallHandler { methodCall, result ->
if (methodCall.method == androidMethod) {
Log.e("Android", "接收到了Flutter傳遞的參數:${methodCall.arguments}")
result.success("$androidMethod ok")
Log.e("Android", "主動調用Flutter的methodInvokeMethodPageState方法")
methodChannel.invokeMethod("methodInvokeMethodPageState", "Android發送給Flutter的參數")
}
}
}
}
複製代碼
Page1ParamActivity.ktless
package com.liuhc.myapplication
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.ViewGroup
import android.widget.FrameLayout
import io.flutter.facade.Flutter
import org.json.JSONObject
/** * 描述:演示在跳轉包含FlutterView的頁面時如何同時傳遞參數 * 做者:liuhc * 建立日期:2019-09-03 */
class Page1ParamActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//下面這個setContentView添加不添加的都不要緊
//若是添加了,在debug模式下FlutterView顯示有點慢因此會先顯示activity_page1再顯示FlutterView
//而release模式下速度會很快,看不到activity_page1,會直接顯示FlutterView
setContentView(R.layout.activity_page1)
//Activity嵌入FlutterView的方式1
val jsonObject = JSONObject()
jsonObject.put("path", "page1Param")
jsonObject.put("param", "我是參數")
val flutterView = Flutter.createView(
this,
lifecycle,
jsonObject.toString()
)
//這句話的做用是在debug模式下去除啓動Flutter頁面的時候的黑屏
flutterView.enableTransparentBackground()
val flutterLayoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
flutterLayoutParams.leftMargin = 0
//將下面的值設置爲100就能夠在FlutterView加載完之後仍然看到TextView
flutterLayoutParams.topMargin = 0
//由於flutterLayoutParams是佔滿全屏,因此調用addContentView會將flutterView放到R.layout.activity_page1上層
//效果看起來就是flutterView替換了R.layout.activity_page1
addContentView(flutterView, flutterLayoutParams)
//可是若是上面直接使用setContentView就看不到過渡效果了,也就是看不到activity_page1也就看不到TextView顯示的文字了
}
}
複製代碼
package com.liuhc.myapplication
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import io.flutter.facade.Flutter
import org.jetbrains.anko.find
/** * 描述:在debug模式下,直接跳轉到使用FlutterView的Activity的時候會有黑屏, * 若是想處理這種狀況能夠直接調用flutterView.enableTransparentBackground(),參考Page1Activity * 這裏提供另外一種解決方式 * * 可是沒什麼必要,由於release模式下是沒有這種問題的 * * 這裏要注意隱藏顯示window.decorView是不能夠的,必須隱藏顯示R.layout.xxx裏將要包含FlutterView的ViewGroup * * 做者:liuhc * 建立日期:2019-09-03 */
class DebugNoBlackActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//先調用setContentView設置一個layout
setContentView(R.layout.activity_debug_no_black)
//Activity嵌入FlutterView的方式1
val flutterView = Flutter.createView(
this,
lifecycle,
"page1"
)
val flutterLayoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
flutterLayoutParams.leftMargin = 0
flutterLayoutParams.topMargin = 0
//1.取出setContentView裏將要添加FlutterView的ViewGroup
val flutterViewViewGroup = find<ViewGroup>(R.id.flutterViewViewGroup)
val root = find<ViewGroup>(R.id.root)
val tipTextView = find<View>(R.id.tipTextView)
//2.添加FlutterView
flutterViewViewGroup.addView(flutterView, flutterLayoutParams)
//3.在xml或者調用代碼隱藏包含FlutterView的ViewGroup,由於我在xml隱藏了因此下面的方法就不須要調用了
//flutterViewViewGroup.visibility = View.INVISIBLE
//4.當FlutterView繪製了第一幀之後再顯示,就解決了debug模式下的黑屏問題
flutterView.addFirstFrameListener {
//刪除過渡TextView
root.removeView(tipTextView)
flutterViewViewGroup.visibility = View.VISIBLE
}
}
}
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".Page1Activity">
<FrameLayout android:id="@+id/flutterViewViewGroup" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="invisible" />
<TextView android:id="@+id/tipTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="FlutterView正在加載中..." />
</RelativeLayout>
複製代碼
若是你直接運行release的版本,跳轉到包含FlutterView的頁面是沒問題的,可是大部分時候咱們運行的是debug版本,這時候你會發現跳轉到包含FlutterView的頁面的時候屏幕會黑屏閃爍一下,實際上是不用處理的,由於debug和release模式的編譯方式不同,release是沒有這個問題的,可是本着刨根問底的態度,咱們能夠用DebugNoBlackActivity.kt的方式來解決async
import 'dart:convert';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'invoke_method_page.dart';
import 'my_home_page.dart';
void main() => runApp(MyApp());
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,
),
//方式1
home: _getWidgetByRoute(window.defaultRouteName),
//方式2 若是route相同,優先匹配routes而不是home
routes: <String, WidgetBuilder>{
"page1": (context) => MyHomePage(
title: "匹配到了page1",
message: "經過routes變量",
),
"page2": (context) => MyHomePage(
title: "匹配到了page2",
message: "經過routes變量",
),
"page3": (context) => MyHomePage(
title: "匹配到了page3",
message: "經過routes變量",
),
},
//當經過routes和home的返回值都爲null的話,纔會從onUnknownRoute尋找
onUnknownRoute: (RouteSettings settings) {
return new PageRouteBuilder(pageBuilder: (BuildContext context, _, __) {
//這裏爲返回的Widget
return MyHomePage(
title: "沒有匹配到",
message: "經過onUnknownRoute變量",
);
});
},
);
}
}
//若是要接收平臺層發送的參數,除了使用Channel之外(這種方式不是正常的方式,強烈不推薦),就只能經過window.defaultRouteName了,
//由於routes的route只能提早定義好,沒法動態判斷
Widget _getWidgetByRoute(String jsonStr) {
print("json=$jsonStr");
String _route;
Map<String, dynamic> jsonMap;
try {
jsonMap = json.decode(jsonStr);
_route = jsonMap["path"];
} catch (e) {
print(e);
_route = jsonStr;
}
switch (_route) {
//接收到了匹配的規則,跳轉到flutter指定頁面
case 'page1':
return MyHomePage(
title: "匹配到了page1",
message: "經過home變量",
);
case 'page1Param':
return MyHomePage(
title: "匹配到了page1Param",
message: jsonMap["param"],
);
case "InvokeMethodPage":
return InvokeMethodPage(
title: jsonMap["title"],
channelName: jsonMap["channelName"],
androidMethod: jsonMap["androidMethod"],
);
default:
return MyHomePage(
title: "沒有匹配到",
message: "經過home變量",
);
}
}
複製代碼
invoke_method_page.dartide
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class InvokeMethodPage extends StatefulWidget {
final String _title;
final String _channelName;
final String _androidMethod;
InvokeMethodPage({
@required String title,
@required String channelName,
@required String androidMethod,
}) : this._title = title,
this._channelName = channelName,
this._androidMethod = androidMethod;
@override
_InvokeMethodPageState createState() => _InvokeMethodPageState();
}
class _InvokeMethodPageState extends State<InvokeMethodPage> {
MethodChannel _methodChannel;
@override
void initState() {
_methodChannel = MethodChannel(widget._channelName);
_methodChannel.setMethodCallHandler((methodCall) {
Future future = Future(() {
if (methodCall.method == "methodInvokeMethodPageState") {
print("接收到了Android的主動調用,參數爲:${methodCall.arguments}");
}
});
return future;
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget._title),
),
body: RaisedButton(
onPressed: () async {
print("主動調用Android的方法${widget._androidMethod}");
var result = await _methodChannel.invokeMethod(
widget._androidMethod,
"我是來自Flutter的參數",
);
print("接收到了調用Android方法的返回值:$result");
},
child: Text("我是Flutter控件,點擊發送參數到Android"),
),
);
}
}
複製代碼
my_home_page.dartpost
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title, this.message}) : super(key: key);
final String title;
final String message;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
widget.message,
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
));
}
}
複製代碼
到此,本章結束,這章介紹瞭如何在Android裏嵌入FlutterView並跳轉,還介紹瞭如何傳遞參數到FlutterView,但願對你們有幫助。
運行Android項目,點擊各個頁面裏的+號,數字會遞增,說明刷新頁面正常
歡迎加入Flutter開發羣457664582,點擊加入,你們一塊兒學習討論