前言
本文主要講解 Flutter 和 Android原生之間,頁面相互跳轉(zhuǎn)、傳參,
但其中用到了兩端相互通信的知識,非常建議先看完這篇 講解通信的文章:
Flutter 與 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel_flutter eventchannel methodchannel basemessagechan-CSDN博客
當前案例 Flutter SDK版本:3.13.2
Flutter使用多個輕量型引擎,進行混合開發(fā),是從?2.0?開始的,大大的減輕了內(nèi)存壓力;
輕量型引擎開發(fā)-官方介紹視頻:https://www.youtube.com/watch?v=p6cK_0jp2ag
Flutter和原生端的關(guān)系
混合路由棧
如果是純Flutter開發(fā),只會有一個Flutter引擎,如果是混合開發(fā),原生端 需要為每個從原生端跳轉(zhuǎn)的Flutter頁面創(chuàng)建獨立的引擎?(也可以單例,但單例寫法需要處理一些問題,這個到?FlutterEngineGroup 章節(jié)會具體講解);
比如:
1.0?從 Android_A頁面? ==== 跳轉(zhuǎn)到 ==== Flutter_A頁面,Android端需要為Flutter_A頁面,創(chuàng)建Flutter引擎;
1.1 緊接著,從 Flutter_A頁面?==== 跳轉(zhuǎn)到 ==== Flutter_B頁面,就不需要,它倆共用一個引擎;
1.2?每個Flutter引擎都有自己的路由棧,且這個路由棧只能管理Flutter頁面;
1.3 使用Flutter提供的?Navigator.pop(context);?方法,可以從 Flutter_B頁面 回退到 Flutter_A頁面,但無法從 Flutter_A頁面 回退到 Android_A,會黑屏,因為Flutter棧里面沒有Android頁面,可以使用?Navigator.canPop(context);?來檢查Flutter路由棧中,是否還有其他路由;
1.4 如果不使用?Navigator.pop(context);?回退方法,使用手機自帶的 Back按鍵 / 左滑屏幕 進行回退,是沒有問題的,因為這種回退方式調(diào)用的是原生API,Android原生不光提供FlutterView渲染Flutter頁面結(jié)果,還會創(chuàng)建FlutterActivity和FlutterView進行綁定;
1.5 看到這,大家應(yīng)該理解,為什么說Flutter只是UI框架了吧;
Android 和 Flutter 跳轉(zhuǎn)
Android 跳轉(zhuǎn) Flutter
Flutter 跳轉(zhuǎn) Android
效果圖
Android代碼
FlutterRouterManager.kt
package com.example.flutter_nav_android.util
import android.app.Activity
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.FlutterActivityLaunchConfigs
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.dart.DartExecutor
class FlutterRouterManager(
val targetRoute: String,
val mEngineId: String,
val mContext: Activity
) {
var mEngine: FlutterEngine? = null
init {
createCachedEngine()
}
companion object {
/**
* 獲取緩存中的 Flutter引擎
*/
@JvmStatic
fun getEngineCacheInstance(engineId: String): FlutterEngine? {
return FlutterEngineCache.getInstance().get(engineId)
}
}
/**
* 1、創(chuàng)建Flutter引擎
* 2、將初始命名路由,修改為目標頁面路由
* 3、緩存Flutter引擎
*/
fun createCachedEngine(): FlutterEngine {
val flutterEngine = FlutterEngine(mContext) // 創(chuàng)建Flutter引擎
// 將初始命名路由,修改為目標頁面路由
flutterEngine.navigationChannel.setInitialRoute(targetRoute)
// 這一步,是在執(zhí)行相關(guān)Dart文件入口的 main函數(shù),將Flutter頁面渲染出結(jié)果
// 原生端獲取結(jié)果,進行最終渲染上屏
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// 將加載好的引擎,存儲起來
FlutterEngineCache.getInstance().put(mEngineId, flutterEngine)
mEngine = flutterEngine
return flutterEngine
}
/**
* 根據(jù)引擎ID,前往指定的Flutter頁面
*/
fun push() {
// 創(chuàng)建新的引擎(了解即可)
// mContext.startActivity(
// FlutterActivity
// .withNewEngine() // 創(chuàng)建引擎
// .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) // 背景改為透明,不然切換頁面時,會閃爍黑色
// .build(mContext))
// 使用緩存好的引擎(推薦)
mContext.startActivity(
FlutterActivity
.withCachedEngine(mEngineId) // 獲取緩存好的引擎
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) // 背景改為透明,不然切換頁面時,會閃爍黑色
.build(mContext)
)
}
/**
* 銷毀當前Flutter引擎
*/
fun destroy() {
mEngine?.destroy()
}
}
PersonalActivity.kt
package com.example.flutter_nav_android.ui.activity
import android.graphics.Color
import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.flutter_nav_android.databinding.ActivityPersonalBinding
import com.example.flutter_nav_android.util.FlutterRouterManager
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class PersonalActivity : AppCompatActivity(), MethodChannel.MethodCallHandler, View.OnClickListener {
private lateinit var bind: ActivityPersonalBinding
private lateinit var homeFlutterEngine: FlutterEngine
private lateinit var loginRouterManager: FlutterRouterManager
private lateinit var loginMethodChannel: MethodChannel
private lateinit var homeMethodChannel: MethodChannel
private val METHOD_CHANNEL_LOGIN = "com.example.flutter_nav_android/login/method"
private val METHOD_CHANNEL_HOME = "com.example.flutter_nav_android/home/method"
private val NAV_FLUTTER_LOGIN_NOTICE = "navFlutterLoginNotice"
private val POP_NOTICE = "popNotice"
private val PERSONAL_POP_NOTICE = "personalPopNotice"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bind = ActivityPersonalBinding.inflate(layoutInflater)
setContentView(bind.root)
initView()
loginRouterManager = FlutterRouterManager("/login", "login_engine", this)
// 兩端建立通信
loginMethodChannel = MethodChannel(loginRouterManager.mEngine!!.dartExecutor, METHOD_CHANNEL_LOGIN)
loginMethodChannel.setMethodCallHandler(this)
// 獲取 Flutter Home頁面的引擎,并且建立通信
homeFlutterEngine = FlutterRouterManager.getEngineCacheInstance("home_engine")!!
homeMethodChannel = MethodChannel(homeFlutterEngine.dartExecutor,METHOD_CHANNEL_HOME)
}
/**
* 監(jiān)聽來自 Flutter端 的消息通道
*
* call: Android端 接收到 Flutter端 發(fā)來的 數(shù)據(jù)對象
* result:Android端 給 Flutter端 執(zhí)行回調(diào)的接口對象
*/
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
val methodName: String = call.method
when (methodName) { // 銷毀 Flutter Login 頁面
POP_NOTICE -> {
val age = call.argument<Int>("age")
getResult(age.toString())
loginRouterManager.mEngine!!.navigationChannel.popRoute()
}
else -> {
result.notImplemented()
}
}
}
override fun onClick(v: View?) {
when (v) {
bind.toFlutter -> { // 前往 Flutter Login 頁面
val map: MutableMap<String, String> = mutableMapOf<String, String>()
map["name"] = "老王"
loginMethodChannel.invokeMethod(NAV_FLUTTER_LOGIN_NOTICE,map)
loginRouterManager.push()
}
bind.pop -> { // 銷毀 Android Personal 頁面
val map: MutableMap<String, Int> = mutableMapOf<String, Int>()
map["age"] = 18
homeMethodChannel.invokeMethod(PERSONAL_POP_NOTICE,map)
finish()
}
}
}
/**
* 初始化頁面
*/
private fun initView() {
bind.toFlutter.setOnClickListener(this)
bind.pop.setOnClickListener(this)
var name = intent.getStringExtra("name")
val title = "接收初始化參數(shù):"
val msg = title + name
val ss = SpannableString(msg)
ss.setSpan(
ForegroundColorSpan(Color.RED),
title.length,
msg.length,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
bind.initV.text = ss
}
/**
* 獲取上一頁的返回參數(shù)
*/
private fun getResult(age: String) {
val title = "接收上一頁返回參數(shù):"
val msg = title + age
val ss = SpannableString(msg)
ss.setSpan(
ForegroundColorSpan(Color.RED),
title.length,
msg.length,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
bind.resultV.text = ss
}
override fun onDestroy() {
super.onDestroy()
loginRouterManager.destroy()
}
}
SchoolActivity.kt
package com.example.flutter_nav_android.ui.activity
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.flutter_nav_android.databinding.ActivitySchoolBinding
import com.example.flutter_nav_android.util.FlutterRouterManager
import io.flutter.embedding.android.FlutterFragment
import io.flutter.embedding.android.TransparencyMode
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
class SchoolActivity : AppCompatActivity() {
private lateinit var bind: ActivitySchoolBinding
private lateinit var bookFragment: FlutterFragment
private lateinit var studentFragment: FlutterFragment
private val METHOD_CHANNEL_BOOK = "com.example.flutter_nav_android/book/method"
private val METHOD_CHANNEL_STUDENT = "com.example.flutter_nav_android/student/method"
private val NAV_FLUTTER_BOOK_NOTICE = "navFlutterBookNotice"
private val NAV_FLUTTER_STUDENT_NOTICE = "navFlutterStudentNotice"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bind = ActivitySchoolBinding.inflate(layoutInflater)
setContentView(bind.root)
initView()
initChannel()
}
/**
* 建立通信
*/
private fun initChannel() {
val bookEngine = FlutterRouterManager.getEngineCacheInstance("book_engine")
val bookChannel = MethodChannel(bookEngine!!.dartExecutor,METHOD_CHANNEL_BOOK)
val map: MutableMap<String, String> = mutableMapOf<String, String>()
map["title"] = "Book"
bookChannel.invokeMethod(NAV_FLUTTER_BOOK_NOTICE,map)
val studentEngine = FlutterRouterManager.getEngineCacheInstance("student_engine")
val studentChannel = MethodChannel(studentEngine!!.dartExecutor,METHOD_CHANNEL_STUDENT)
val map2: MutableMap<String, String> = mutableMapOf<String, String>()
map2["title"] = "Student"
studentChannel.invokeMethod(NAV_FLUTTER_STUDENT_NOTICE,map2)
}
/**
* 初始化頁面
*/
private fun initView() {
bookFragment = FlutterFragment.withCachedEngine("book_engine")
.transparencyMode(TransparencyMode.transparent) // 背景透明,避免切換頁面,出現(xiàn)閃爍
.shouldAttachEngineToActivity(false) // 是否讓Flutter控制Activity,true:可以 false:不可以,默認值 true
.build()
supportFragmentManager
.beginTransaction()
.add(bind.bookFragment.id, bookFragment)
.commit()
studentFragment = FlutterFragment.withCachedEngine("student_engine")
.transparencyMode(TransparencyMode.transparent) // 背景透明,避免切換頁面,出現(xiàn)閃爍
.shouldAttachEngineToActivity(false) // 是否讓Flutter控制Activity,true:可以 false:不可以,默認值 true
.build()
supportFragmentManager
.beginTransaction()
.add(bind.studentFragment.id, studentFragment)
.commit()
}
// ================================ 這些是固定寫法,F(xiàn)lutter需要這些回調(diào) ================================
override fun onPostResume() {
super.onPostResume()
bookFragment.onPostResume()
studentFragment.onPostResume()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
bookFragment.onNewIntent(intent)
studentFragment.onNewIntent(intent)
}
override fun onBackPressed() {
bookFragment.onBackPressed()
studentFragment.onBackPressed()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
bookFragment.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
)
studentFragment.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
)
}
override fun onUserLeaveHint() {
bookFragment.onUserLeaveHint()
studentFragment.onUserLeaveHint()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
bookFragment.onTrimMemory(level)
studentFragment.onTrimMemory(level)
}
}
MainActivity.kt
package com.example.flutter_nav_android.ui.activity
import android.content.Intent
import android.graphics.Color
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.flutter_nav_android.databinding.ActivityMainBinding
import com.example.flutter_nav_android.util.FlutterRouterManager
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class MainActivity : AppCompatActivity(), MethodChannel.MethodCallHandler, View.OnClickListener {
private lateinit var bind: ActivityMainBinding
private lateinit var homeMethodChannel: MethodChannel
private val METHOD_CHANNEL_HOME = "com.example.flutter_nav_android/home/method"
private val NAV_ANDROID_PERSONAL_NOTICE = "navAndroidPersonalNotice"
private val NAV_FLUTTER_HOME_NOTICE = "navFlutterHomeNotice"
private val POP_NOTICE = "popNotice"
private lateinit var homeRouterManager: FlutterRouterManager
private lateinit var bookRouterManager: FlutterRouterManager
private lateinit var studentRouterManager: FlutterRouterManager
override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
bind = ActivityMainBinding.inflate(layoutInflater)
setContentView(bind.root)
bind.toFlutter.setOnClickListener(this)
bind.toFlutterFragment.setOnClickListener(this)
homeRouterManager = FlutterRouterManager("/home", "home_engine", this)
// 兩端建立通信
homeMethodChannel = MethodChannel(homeRouterManager.mEngine!!.dartExecutor,METHOD_CHANNEL_HOME)
homeMethodChannel.setMethodCallHandler(this)
// 這里Fragment案例的
bookRouterManager = FlutterRouterManager("/book", "book_engine", this)
studentRouterManager = FlutterRouterManager("/student", "student_engine", this)
}
/**
* 監(jiān)聽來自 Flutter端 的消息通道
*
* call: Android端 接收到 Flutter端 發(fā)來的 數(shù)據(jù)對象
* result:Android端 給 Flutter端 執(zhí)行回調(diào)的接口對象
*/
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
val methodName: String = call.method
when (methodName) {
NAV_ANDROID_PERSONAL_NOTICE -> { // Flutter Home 頁面 前往 Android Personal 頁面
val intent = Intent(this, PersonalActivity::class.java)
intent.putExtra("name",call.argument<String>("name"))
startActivity(intent)
}
POP_NOTICE -> { // 銷毀 Flutter Home 頁面
val age = call.argument<Int>("age")
getResult(age.toString())
homeRouterManager.mEngine!!.navigationChannel.popRoute()
}
else -> {
result.notImplemented()
}
}
}
override fun onClick(v: View?) {
when (v) {
bind.toFlutter -> { // 前往 Flutter Home 頁面
val map: MutableMap<String, String> = mutableMapOf<String, String>()
map["name"] = "張三"
homeMethodChannel.invokeMethod(NAV_FLUTTER_HOME_NOTICE,map)
homeRouterManager.push()
}
bind.toFlutterFragment -> {
val intent = Intent(this, SchoolActivity::class.java)
startActivity(intent)
}
}
}
/**
* 獲取上一頁的返回參數(shù)
*/
private fun getResult(age: String) {
val title = "接收上一頁返回參數(shù):"
val msg = title + age
val ss = SpannableString(msg)
ss.setSpan(
ForegroundColorSpan(Color.RED),
title.length,
msg.length,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
bind.resultV.text = ss
}
override fun onDestroy() {
super.onDestroy()
homeRouterManager.destroy()
bookRouterManager.destroy()
studentRouterManager.destroy()
}
}
activity_personal.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="16dp"
android:background="@color/cardview_shadow_start_color"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:text="Android Personal"
android:textColor="@color/material_dynamic_primary60"
android:textSize="26sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/init_v"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="接收初始化參數(shù):"
android:textColor="@color/material_dynamic_primary60"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/result_v"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="接收上一頁返回參數(shù):"
android:textColor="@color/material_dynamic_primary60"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/init_v" />
<Button
android:id="@+id/to_flutter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="前往 Flutter Login"
android:textSize="20sp"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/result_v" />
<Button
android:id="@+id/pop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="返回 上一頁"
android:textSize="20sp"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/to_flutter" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
activity_school.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/cardview_shadow_start_color"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:text="Android School"
android:textColor="@color/material_dynamic_primary60"
android:textSize="26sp"
android:textStyle="bold" />
<FrameLayout
android:id="@+id/book_fragment"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp" />
<FrameLayout
android:id="@+id/student_fragment"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp" />
</LinearLayout>
</layout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginBottom="16dp"
android:background="@color/cardview_shadow_start_color"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:text="Android Main"
android:textColor="@color/material_dynamic_primary60"
android:textSize="26sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/result_v"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="接收上一頁返回參數(shù):"
android:textColor="@color/material_dynamic_primary60"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<Button
android:id="@+id/to_flutter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="前往 Flutter Home"
android:textSize="20sp"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/result_v"
app:layout_constraintRight_toRightOf="parent" />
<Button
android:id="@+id/to_flutter_fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="前往 Flutter Fragment"
android:textSize="20sp"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/to_flutter"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="flutter_nav_android"
android:theme="@style/AppTheme">
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize" />
<activity android:name=".ui.activity.SchoolActivity" android:exported="true"/>
<activity android:name=".ui.activity.PersonalActivity" android:exported="true"/>
<activity
android:name=".ui.activity.MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>
Flutter代碼
book.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Book extends StatefulWidget {
const Book({super.key});
@override
State<Book> createState() => _BookState();
}
class _BookState extends State<Book> {
String title = '';
static const String METHOD_CHANNEL_BOOK = 'com.example.flutter_nav_android/book/method';
static const String NAV_FLUTTER_BOOK_NOTICE = 'navFlutterBookNotice';
@override
void initState() {
super.initState();
MethodChannel bookMethodChannel = const MethodChannel(METHOD_CHANNEL_BOOK);
bookMethodChannel.setMethodCallHandler(methodHandler);
}
/// 監(jiān)聽來自 Android端 的消息通道
/// Android端調(diào)用了函數(shù),這個handler函數(shù)就會被觸發(fā)
Future<dynamic> methodHandler(MethodCall call) async {
final String methodName = call.method;
switch (methodName) {
case NAV_FLUTTER_BOOK_NOTICE: // 進入當前頁面
{
title = call.arguments['title'];
setState(() {});
return 0;
}
default:
{
return PlatformException(
code: '-1',
message: '未找到Flutter端具體實現(xiàn)函數(shù)',
details: '具體描述'); // 返回給Android端
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amberAccent,
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
alignment: Alignment.center,
child: Text(
'Flutter $title',
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red,
fontSize: 20,
),
),
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
late MethodChannel homeMethodChannel;
String name = '';
String age = '';
static const String METHOD_CHANNEL_HOME = 'com.example.flutter_nav_android/home/method';
static const String NAV_ANDROID_PERSONAL_NOTICE = 'navAndroidPersonalNotice';
static const String NAV_FLUTTER_HOME_NOTICE = 'navFlutterHomeNotice';
static const String POP_NOTICE = 'popNotice';
static const String PERSONAL_POP_NOTICE = 'personalPopNotice';
@override
void initState() {
super.initState();
homeMethodChannel = const MethodChannel(METHOD_CHANNEL_HOME);
homeMethodChannel.setMethodCallHandler(methodHandler);
}
/// 監(jiān)聽來自 Android端 的消息通道
/// Android端調(diào)用了函數(shù),這個handler函數(shù)就會被觸發(fā)
Future<dynamic> methodHandler(MethodCall call) async {
final String methodName = call.method;
switch (methodName) {
case NAV_FLUTTER_HOME_NOTICE: // 進入當前頁面
{
name = call.arguments['name'];
setState(() {});
return 0;
}
case PERSONAL_POP_NOTICE: // Android Personal 頁面 銷毀了
{
age = '${call.arguments['age']}';
setState(() {});
return 0;
}
default:
{
return PlatformException(
code: '-1',
message: '未找到Flutter端具體實現(xiàn)函數(shù)',
details: '具體描述'); // 返回給Android端
}
}
}
/// 銷毀當前頁面
popPage() {
if (Navigator.canPop(context)) { // 檢查Flutter路由棧中,是否還有其他路由
Navigator.pop(context);
} else {
Map<String, int> map = {'age': 12};
homeMethodChannel.invokeMethod(POP_NOTICE, map);
}
}
/// 前往 Android Personal 頁面
navAndroidPersonal() {
Map<String, String> map = {'name': '李四'};
homeMethodChannel.invokeMethod(NAV_ANDROID_PERSONAL_NOTICE, map);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: const Text(
'Flutter Home',
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 26,
color: Colors.yellow),
)),
body: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: RichText(
text: TextSpan(
text: '接收初始化參數(shù):',
style: const TextStyle(color: Colors.black, fontSize: 20),
children: [
TextSpan(
text: name,
style: const TextStyle(color: Colors.red,fontWeight: FontWeight.bold),
)
])),
),
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: RichText(
text: TextSpan(
text: '接收上一頁返回參數(shù):',
style: const TextStyle(color: Colors.black, fontSize: 20),
children: [
TextSpan(
text: age,
style: const TextStyle(color: Colors.red,fontWeight: FontWeight.bold),
)
])),
),
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: ElevatedButton(
onPressed: navAndroidPersonal,
child: const Text(
'前往 Android Personal',
style: TextStyle(fontSize: 20),
),
),
),
ElevatedButton(
onPressed: popPage,
child: const Text(
'返回 上一頁',
style: TextStyle(fontSize: 20),
),
),
],
),
),
);
}
}
login.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Login extends StatefulWidget {
const Login({super.key});
@override
State<Login> createState() => _LoginState();
}
class _LoginState extends State<Login> {
late MethodChannel loginMethodChannel;
String name = '';
final String METHOD_CHANNEL_LOGIN = 'com.example.flutter_nav_android/login/method';
static const String NAV_FLUTTER_LOGIN_NOTICE = 'navFlutterLoginNotice';
final String POP_NOTICE = 'popNotice';
@override
void initState() {
super.initState();
loginMethodChannel = MethodChannel(METHOD_CHANNEL_LOGIN);
loginMethodChannel.setMethodCallHandler(methodHandler);
}
/// 監(jiān)聽來自 Android端 的消息通道
/// Android端調(diào)用了函數(shù),這個handler函數(shù)就會被觸發(fā)
Future<dynamic> methodHandler(MethodCall call) async {
final String methodName = call.method;
switch (methodName) {
case NAV_FLUTTER_LOGIN_NOTICE: // 進入當前頁面
{
name = call.arguments['name'];
setState(() {});
return 0;
}
default:
{
return PlatformException(code: '-1', message: '未找到Flutter端具體實現(xiàn)函數(shù)', details: '具體描述'); // 返回給Android端
}
}
}
/// 銷毀當前頁面
popPage() {
if (Navigator.canPop(context)) { // 檢查Flutter路由棧中,是否還有其他路由
Navigator.pop(context);
} else {
Map<String, int> map = {'age': 28};
loginMethodChannel.invokeMethod(POP_NOTICE, map);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: const Text(
'Flutter Login',
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 26,
color: Colors.yellow),
)),
body: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: RichText(
text: TextSpan(
text: '接收初始化參數(shù):',
style: const TextStyle(color: Colors.black, fontSize: 20),
children: [
TextSpan(
text: name,
style: const TextStyle(color: Colors.red,fontWeight: FontWeight.bold),
)
])),
),
ElevatedButton(
onPressed: popPage,
child: const Text(
'返回 上一頁',
style: TextStyle(fontSize: 20),
),
),
],
),
),
);
}
}
student.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Student extends StatefulWidget {
const Student({super.key});
@override
State<Student> createState() => _StudentState();
}
class _StudentState extends State<Student> {
String title = '';
static const String METHOD_CHANNEL_STUDENT = 'com.example.flutter_nav_android/student/method';
static const String NAV_FLUTTER_STUDENT_NOTICE = 'navFlutterStudentNotice';
@override
void initState() {
super.initState();
MethodChannel bookMethodChannel = const MethodChannel(METHOD_CHANNEL_STUDENT);
bookMethodChannel.setMethodCallHandler(methodHandler);
}
/// 監(jiān)聽來自 Android端 的消息通道
/// Android端調(diào)用了函數(shù),這個handler函數(shù)就會被觸發(fā)
Future<dynamic> methodHandler(MethodCall call) async {
final String methodName = call.method;
switch (methodName) {
case NAV_FLUTTER_STUDENT_NOTICE: // 進入當前頁面
{
title = call.arguments['title'];
setState(() {});
return 0;
}
default:
{
return PlatformException(
code: '-1',
message: '未找到Flutter端具體實現(xiàn)函數(shù)',
details: '具體描述'); // 返回給Android端
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.cyan,
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
alignment: Alignment.center,
child: Text(
'Flutter $title',
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20,
),
),
),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_nav_android/book.dart';
import 'package:flutter_nav_android/student.dart';
import 'home.dart';
import 'login.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
),
useMaterial3: true,
),
routes: {
'/home': (context) => const Home(),
'/login': (context) => const Login(),
'/book': (context) => const Book(),
'/student': (context) => const Student(),
},
initialRoute: '/home',
);
}
}
踩坑
- 一個路由坑,F(xiàn)lutter 加載 根命名路由標識,默認是 "?/ ",如果你在Flutter端使用了 "?/ " 作為根路由頁面,有時候,從Android 跳轉(zhuǎn) Flutter頁面時,它會將 根路由頁面 先push進棧,再push你的目標頁面進棧,會多出一個頁面;
- 所有我直接不用 "?/ "?標識,而使用頁面對應(yīng)的路由標識字符串,來指定根路由。
// 原來帶坑的寫法
// routes: {
// '/': (context) => const Home(),
// '/login': (context) => const Login(),
// '/personal': (context) => const Personal()
// },
// initialRoute: '/',
// 解決方式的寫法
routes: {
'/home': (context) => const Home(),
'/login': (context) => const Login(),
'/personal': (context) => const Personal()
},
initialRoute: '/home',
奇技淫巧
在案例中,我使用的是 命名路由 和 Channel方式 進行 頁面跳轉(zhuǎn)、傳參,下面直接使用引擎進行 頁面跳轉(zhuǎn)、傳參,但實用價值不高,因為弊端太大;
比如:
- 只提供了List<String>類型,進行傳參;
- 目標Flutter頁面內(nèi)的widget,默認參數(shù)會全部失效;
- Flutter的Widget默認參數(shù),是由主文件內(nèi)的 MaterialApp?組件提供的,一個Flutter應(yīng)用,一般只會使用一個?MaterialApp,它代表返回一個App,如果將目標Flutter頁面套上?MaterialApp?,可以解決這個默認參數(shù)問題,但會引發(fā) 路由問題;
- 不過也有適用場景:讓每個Flutter頁面完全獨立,之間沒有任何交互,比如跳轉(zhuǎn)、數(shù)據(jù)共享等等,這樣路由就不需要了,讓每個Flutter頁面都使用?MaterialApp,當作App和原生交互;
綜上所述,大家將這種方式當作擴展知識就好了。
Android代碼
class MainActivity : AppCompatActivity(), View.OnClickListener {
... ...
override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
... ...
val flutterEngine = FlutterEngine(this)
// 定義參數(shù)
val dartEntrypointArgs = mutableListOf<String>()
dartEntrypointArgs.add("張三")
// 這種方式傳參數(shù),會導致這個Flutter頁面中的Widget默認參數(shù),全部失效,
// 這種只有 Dart主入口文件,比如main.dart的 main函數(shù)才能接收到參數(shù)
// void main(List<String args>) {}
// flutterEngine.dartExecutor.executeDartEntrypoint(
// DartExecutor.DartEntrypoint.createDefault(),
// dartEntrypointArgs
// )
// 這種可以指定頁面接收,但需要在主入口文件里先聲明
// void showPersonal(List<String> args) {}
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"showPersonal"), // 找到目標Flutter頁面提供的 暴露函數(shù)
dartEntrypointArgs)
FlutterEngineCache.getInstance().put("personal_engine", flutterEngine)
}
override fun onClick(v: View?) {
... ...
val map: MutableMap<String, String> = mutableMapOf<String, String>()
map["name"] = "張三"
startActivity(
FlutterActivity
.withCachedEngine("personal_engine") // 獲取緩存好的引擎
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent) // 背景改為透明,不然切換頁面時,會閃爍黑色
.build(this))
}
}
Flutter代碼
main.dart(主文件)
import 'package:flutter/material.dart';
import 'package:flutter_nav_android/personal.dart';
void main(List<String> args) {
debugPrint('args:$args');
runApp(const MyApp());
}
/// 注解說明文檔:https://mrale.ph/dartvm/compiler/aot/entry_point_pragma.html
/// 注解:表明它可以在 AOT 模式下直接從本機或 VM 代碼解析、分配或調(diào)用
@pragma("vm:entry-point")
void showPersonal(List<String> args) { // 在主文件入口暴露出來
debugPrint('args:$args');
runApp(Personal(title: args.first));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
),
useMaterial3: true,
),
);
}
}
personal.dart(目標頁面)
import 'package:flutter/material.dart';
class Personal extends StatefulWidget {
final String? title;
const Personal({super.key,this.title});
@override
State<Personal> createState() => _PersonalState();
}
class _PersonalState extends State<Personal> {
/// 在這個頁面中,使用的Widget 默認參數(shù)全部失效
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
alignment: Alignment.center,
child: Directionality(
textDirection: TextDirection.ltr,
child: Text(
widget.title ?? '',
style: const TextStyle(
color: Colors.lightBlue,
fontSize: 30,
),
)),
);
}
}
FlutterEngineGroup
- 如果你接受以上缺點,可以使用 FlutterEngineGroup,這個東西就是換成了單例寫法,我們自己寫單例,需要處理一個問題,比如這個需求:同時獲取兩個Flutter引擎,這里就要通過判空新建一個Flutter引擎;
- 而?FlutterEngineGroup 已經(jīng)處理了這個問題,自首個Flutter引擎起,后面多出的Flutter引擎,都是其子類,如果只剩下最后一個,那么這個Flutter引擎將和首個Flutter引擎性能特征相同;
- FlutterEngineGroup官方文檔:多個 Flutter 頁面或視圖 - Flutter 中文文檔 - Flutter 中文開發(fā)者網(wǎng)站 - Flutter
- 從我個人體驗上來說,不使用單例,性能也足夠了,如果有很多混合的Flutter頁面,那當我沒說??。
- 我沒找到創(chuàng)建子類Flutter引擎的相關(guān)代碼,是從注解中發(fā)現(xiàn)的
Debug 和 Release
我用真機做測試,發(fā)現(xiàn)在Debug模式下,第一次從 Android 跳轉(zhuǎn) Flutter 會出現(xiàn)黑屏現(xiàn)象,但?打完包 或在 Release模式?就看不出來了;
我們默認的運行模式就是 Debug模式,可以通過 修改IDE運行命令 --release,切換為?Release模式;
文章來源:http://www.zghlxwxcb.cn/news/detail-835006.html
源碼地址
GitHub - LanSeLianMa/flutter_nav_android: Flutter 和 Android原生(Activity、Fragment)相互跳轉(zhuǎn)、傳參文章來源地址http://www.zghlxwxcb.cn/news/detail-835006.html
到了這里,關(guān)于Flutter 和 Android原生(Activity、Fragment)相互跳轉(zhuǎn)、傳參的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!