Flutter 和 Android原生(Activity、Fragment)相互跳转、传参

前言

本文主要讲解 Flutter 和 Android原生之间,页面相互跳转、传参,

但其中用到了两端相互通信的知识,非常建议先看完这篇 讲解通信的文章

Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel_flutter eventchannel methodchannel basemessagechan-CSDN博客

当前案例 Flutter SDK版本:3.13.2

Flutter使用多个轻量型引擎,进行混合开发,是从 2.0 开始的,大大的减轻了内存压力;

轻量型引擎开发-官方介绍视频:https://www.youtube.com/watch?v=p6cK_0jp2ag

Flutter和原生端的关系

混合路由栈

如果是纯Flutter开发只会有一个Flutter引擎,如果是混合开发,原生端 需要为每个从原生端跳转的Flutter页面创建独立的引擎 (也可以单例,但单例写法需要处理一些问题,这个到 FlutterEngineGroup 章节会具体讲解);

比如:

1.0 从 Android_A页面  ==== 跳转到 ==== Flutter_A页面,Android端需要为Flutter_A页面,创建Flutter引擎;

1.1 紧接着,从 Flutter_A页面 ==== 跳转到 ==== 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按键 / 左滑屏幕 进行回退,是没有问题的,因为这种回退方式调用的是原生API,Android原生不光提供FlutterView渲染Flutter页面结果,还会创建FlutterActivityFlutterView进行绑定;

1.5 看到这,大家应该理解,为什么说Flutter只是UI框架了吧;

Android 和 Flutter 跳转

Android 跳转 Flutter

Flutter 跳转 Android

效果图

Android代码

FlutterRouterManager.kt

package com.example.flutter_nav_android.utilimport 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.DartExecutorclass FlutterRouterManager(val targetRoute: String,val mEngineId: String,val mContext: Activity
) {var mEngine: FlutterEngine? = nullinit {createCachedEngine()}companion object {/*** 获取缓存中的 Flutter引擎*/@JvmStaticfun getEngineCacheInstance(engineId: String): FlutterEngine? {return FlutterEngineCache.getInstance().get(engineId)}}/*** 1、创建Flutter引擎* 2、将初始命名路由,修改为目标页面路由* 3、缓存Flutter引擎*/fun createCachedEngine(): FlutterEngine {val flutterEngine = FlutterEngine(mContext) // 创建Flutter引擎// 将初始命名路由,修改为目标页面路由flutterEngine.navigationChannel.setInitialRoute(targetRoute)// 这一步,是在执行相关Dart文件入口的 main函数,将Flutter页面渲染出结果// 原生端获取结果,进行最终渲染上屏flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())// 将加载好的引擎,存储起来FlutterEngineCache.getInstance().put(mEngineId, flutterEngine)mEngine = flutterEnginereturn flutterEngine}/*** 根据引擎ID,前往指定的Flutter页面*/fun push() {// 创建新的引擎(了解即可)// mContext.startActivity(//    FlutterActivity//        .withNewEngine() // 创建引擎//        .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.activityimport 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.MethodChannelclass PersonalActivity : AppCompatActivity(), MethodChannel.MethodCallHandler, View.OnClickListener {private lateinit var bind: ActivityPersonalBindingprivate lateinit var homeFlutterEngine: FlutterEngineprivate lateinit var loginRouterManager: FlutterRouterManagerprivate lateinit var loginMethodChannel: MethodChannelprivate lateinit var homeMethodChannel: MethodChannelprivate 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)}/*** 监听来自 Flutter端 的消息通道** call: Android端 接收到 Flutter端 发来的 数据对象* result:Android端 给 Flutter端 执行回调的接口对象*/override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {val methodName: String = call.methodwhen (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"] = 18homeMethodChannel.invokeMethod(PERSONAL_POP_NOTICE,map)finish()}}}/*** 初始化页面*/private fun initView() {bind.toFlutter.setOnClickListener(this)bind.pop.setOnClickListener(this)var name = intent.getStringExtra("name")val title = "接收初始化参数:"val msg = title + nameval ss = SpannableString(msg)ss.setSpan(ForegroundColorSpan(Color.RED),title.length,msg.length,Spanned.SPAN_INCLUSIVE_EXCLUSIVE)bind.initV.text = ss}/*** 获取上一页的返回参数*/private fun getResult(age: String) {val title = "接收上一页返回参数:"val msg = title + ageval 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.activityimport 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.MethodChannelclass SchoolActivity : AppCompatActivity() {private lateinit var bind: ActivitySchoolBindingprivate lateinit var bookFragment: FlutterFragmentprivate lateinit var studentFragment: FlutterFragmentprivate 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) // 背景透明,避免切换页面,出现闪烁.shouldAttachEngineToActivity(false) // 是否让Flutter控制Activity,true:可以 false:不可以,默认值 true.build()supportFragmentManager.beginTransaction().add(bind.bookFragment.id, bookFragment).commit()studentFragment = FlutterFragment.withCachedEngine("student_engine").transparencyMode(TransparencyMode.transparent) // 背景透明,避免切换页面,出现闪烁.shouldAttachEngineToActivity(false) // 是否让Flutter控制Activity,true:可以 false:不可以,默认值 true.build()supportFragmentManager.beginTransaction().add(bind.studentFragment.id, studentFragment).commit()}// ================================ 这些是固定写法,Flutter需要这些回调 ================================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.activityimport 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.MethodChannelclass MainActivity : AppCompatActivity(), MethodChannel.MethodCallHandler, View.OnClickListener {private lateinit var bind: ActivityMainBindingprivate lateinit var homeMethodChannel: MethodChannelprivate 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: FlutterRouterManagerprivate lateinit var bookRouterManager: FlutterRouterManagerprivate lateinit var studentRouterManager: FlutterRouterManageroverride 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)}/*** 监听来自 Flutter端 的消息通道** call: Android端 接收到 Flutter端 发来的 数据对象* result:Android端 给 Flutter端 执行回调的接口对象*/override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {val methodName: String = call.methodwhen (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)}}}/*** 获取上一页的返回参数*/private fun getResult(age: String) {val title = "接收上一页返回参数:"val msg = title + ageval 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.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid: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" /><TextViewandroid:id="@+id/init_v"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center_vertical"android:text="接收初始化参数:"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" /><TextViewandroid:id="@+id/result_v"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="接收上一页返回参数:"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" /><Buttonandroid: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" /><Buttonandroid: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><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:paddingTop="20dp"android:orientation="vertical"><TextViewandroid: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" /><FrameLayoutandroid:id="@+id/book_fragment"android:layout_weight="1"android:layout_width="match_parent"android:layout_height="0dp" /><FrameLayoutandroid: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.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid: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" /><TextViewandroid:id="@+id/result_v"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center_vertical"android:text="接收上一页返回参数:"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"/><Buttonandroid: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" /><Buttonandroid: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"><applicationandroid:name="${applicationName}"android:icon="@mipmap/ic_launcher"android:label="flutter_nav_android"android:theme="@style/AppTheme"><activityandroid: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"/><activityandroid: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 asthe Android process has started. This theme is visible to the userwhile the Flutter UI initializes. After that, this theme continuesto determine the Window background behind the Flutter UI. --><meta-dataandroid: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-dataandroid:name="flutterEmbedding"android:value="2" /></application><!-- Required to query activities that can process text, see:https://developer.android.com/training/package-visibility?hl=en andhttps://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});@overrideState<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';@overridevoid initState() {super.initState();MethodChannel bookMethodChannel = const MethodChannel(METHOD_CHANNEL_BOOK);bookMethodChannel.setMethodCallHandler(methodHandler);}/// 监听来自 Android端 的消息通道/// Android端调用了函数,这个handler函数就会被触发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端具体实现函数',details: '具体描述'); // 返回给Android端}}}@overrideWidget 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});@overrideState<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';@overridevoid initState() {super.initState();homeMethodChannel = const MethodChannel(METHOD_CHANNEL_HOME);homeMethodChannel.setMethodCallHandler(methodHandler);}/// 监听来自 Android端 的消息通道/// Android端调用了函数,这个handler函数就会被触发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端具体实现函数',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);}@overrideWidget 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: '接收初始化参数:',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: '接收上一页返回参数:',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});@overrideState<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';@overridevoid initState() {super.initState();loginMethodChannel = MethodChannel(METHOD_CHANNEL_LOGIN);loginMethodChannel.setMethodCallHandler(methodHandler);}/// 监听来自 Android端 的消息通道/// Android端调用了函数,这个handler函数就会被触发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端具体实现函数', details: '具体描述'); // 返回给Android端}}}/// 销毁当前页面popPage() {if (Navigator.canPop(context)) { // 检查Flutter路由栈中,是否还有其他路由Navigator.pop(context);} else {Map<String, int> map = {'age': 28};loginMethodChannel.invokeMethod(POP_NOTICE, map);}}@overrideWidget 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: '接收初始化参数:',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});@overrideState<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';@overridevoid initState() {super.initState();MethodChannel bookMethodChannel = const MethodChannel(METHOD_CHANNEL_STUDENT);bookMethodChannel.setMethodCallHandler(methodHandler);}/// 监听来自 Android端 的消息通道/// Android端调用了函数,这个handler函数就会被触发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端具体实现函数',details: '具体描述'); // 返回给Android端}}}@overrideWidget 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});@overrideWidget 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',);}
}

踩坑

  • 一个路由坑,Flutter 加载 根命名路由标识,默认是 " / ",如果你在Flutter端使用了 " / " 作为根路由页面,有时候,从Android 跳转 Flutter页面时,它会将 根路由页面 先push进栈,再push你的目标页面进栈,会多出一个页面
  • 所有我直接不用 " / " 标识,而使用页面对应的路由标识字符串,来指定根路由。
// 原来带坑的写法
// 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方式 进行 页面跳转、传参,下面直接使用引擎进行 页面跳转、传参,但实用价值不高,因为弊端太大;

比如:

  • 只提供了List<String>类型,进行传参;
  • 目标Flutter页面内的widget,默认参数会全部失效;
  • Flutter的Widget默认参数,是由主文件内的 MaterialApp 组件提供的,一个Flutter应用,一般只会使用一个 MaterialApp,它代表返回一个App,如果将目标Flutter页面套上 MaterialApp ,可以解决这个默认参数问题,但会引发 路由问题
  • 不过也有适用场景:让每个Flutter页面完全独立,之间没有任何交互,比如跳转、数据共享等等,这样路由就不需要了,让每个Flutter页面都使用 MaterialApp,当作App和原生交互;

综上所述,大家将这种方式当作扩展知识就好了。

Android代码

class MainActivity : AppCompatActivity(), View.OnClickListener {... ... override fun onCreate(savedInstanceState: android.os.Bundle?) {super.onCreate(savedInstanceState)... ... val flutterEngine = FlutterEngine(this)// 定义参数val dartEntrypointArgs = mutableListOf<String>()dartEntrypointArgs.add("张三")// 这种方式传参数,会导致这个Flutter页面中的Widget默认参数,全部失效,// 这种只有 Dart主入口文件,比如main.dart的 main函数才能接收到参数// 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页面提供的 暴露函数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 代码解析、分配或调用
@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});@overrideWidget 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});@overrideState<Personal> createState() => _PersonalState();
}class _PersonalState extends State<Personal> {/// 在这个页面中,使用的Widget 默认参数全部失效@overrideWidget 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 已经处理了这个问题,自首个Flutter引擎起,后面多出的Flutter引擎,都是其子类,如果只剩下最后一个,那么这个Flutter引擎将和首个Flutter引擎性能特征相同;
  • FlutterEngineGroup官方文档:多个 Flutter 页面或视图 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter
  • 我个人体验上来说,不使用单例,性能也足够了,如果有很多混合的Flutter页面,那当我没说😐。
  • 没找到创建子类Flutter引擎的相关代码,是从注解中发现的

Debug 和 Release

我用真机做测试,发现在Debug模式下,第一次从 Android 跳转 Flutter 会出现黑屏现象,但 打完包 或在 Release模式 就看不出来了;

我们默认的运行模式就是 Debug模式,可以通过 修改IDE运行命令 --release,切换为 Release模式

源码地址

GitHub - LanSeLianMa/flutter_nav_android: Flutter 和 Android原生(Activity、Fragment)相互跳转、传参

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/251167.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

命名空间(namespace)及其应用技巧

C 命名空间及其应用技巧 文章目录 C 命名空间及其应用技巧引言代码案例一&#xff1a;不同命名空间的变量和自定义类型代码案例二&#xff1a;命名空间的嵌套和using的使用代码案例三&#xff1a;不连续的同名命名空间代码案例四&#xff1a;命名空间和局部、全局变量的优先级总…

使用Ettus USRP X440对雷达和EW系统进行原型验证

概览 无论是保障己方平台的生存能力&#xff0c;还是扰乱敌方频谱使用&#xff0c;以电磁(EM)频谱为主导都是任务成功的主要因素。电磁频谱操作(Electromagnetic Spectrum Operation, EMSO)需要使用战术系统来监测敌方的频谱活动、定位其发射器并帮助己方制定行动计划。软件无…

大数据 - Spark系列《二》- 关于Spark在Idea中的一些常用配置

上一篇&#xff1a; 大数据 - Spark系列《一》- 从Hadoop到Spark&#xff1a;大数据计算引擎的演进-CSDN博客 目录 1. &#x1f959;Idea中配置Live Templates来快速生成代码片段 2. &#x1f959;Idea中配置文件模板自定义初始代码 3.&#x1f959;设置spark-submit提交程…

【stm32】hal库学习笔记-FSMC连接TFT_LCD

【stm32】hal库学习笔记-FSMC连接TFT LCD 触摸屏结构与原理 LCD模块接口原理图 LCD 接口连接在 FSMC 总线上面&#xff0c;图中的 T_MISO/T_MOSI/T_PEN/T_SCK/T_CS 连接在 MCU 的 PB2/PF11/PB1/PB0/PC13 上&#xff0c;这些信号用来实现对液晶触摸屏的控制&#xff08;支持电阻…

AI 数字人从制作到变现

最近AI很火&#xff0c;无意中发现一个宝藏专栏《AI数字人从制作到变现》&#xff0c;原价599&#xff0c;现在推广阶段&#xff0c;只需要10元&#xff0c;专栏持续更新中&#xff0c;会有更多的知识后续分享。如有兴趣可以用微信扫描左侧海报二维码&#xff0c;下面我将介绍专…

第14章_视图

第14章_视图 1.常见的数据库对象 对象描述表(TABLE)表是存储数据的逻辑单元&#xff0c;以行和列的形式存在&#xff0c;列就是字段&#xff0c;行就是记录数据字典就是系统表&#xff0c;存放数据库相关信息的表。系统表的数据通常由数据库系统维护&#xff0c; 程序员通常不…

ES6-let

一、基本语法 ES6 中的 let 关键字用于声明变量&#xff0c;并且具有块级作用域。 - 语法&#xff1a;let 标识符;let 标识符初始值; - 规则&#xff1a;1.不能重复声明let不允许在相同作用域内重复声明同一个变量2.不存在变量提升在同一作用域内&#xff0c;必须先声明才能试…

【项目实战】谷粒学院项目回顾

本文作者&#xff1a; slience_me 谷粒学院 谷粒学院项目致力于打造一个B2C模式的职业技能在线教育系统平台&#xff0c;采用现阶段流行技术来实现&#xff0c;采用前后端分离编写。 GitHub 地址 项目学习资源 项目文档 slience_me的博客 接口文档 谷粒学院完整代码: https…

使用ChatGPT学习大象机器人六轴协作机械臂mechArm

引言 我是一名机器人方向的大学生&#xff0c;近期学校安排自主做一个机器人方面相关的项目。学校给我们提供了一个小型的六轴机械臂&#xff0c;mechArm 270M5Stack&#xff0c;我打算使用ChatGPT让它来辅助我学习如何使用这个机械臂并且做一个demo。 本篇文章将记录我是如何使…

模型单体化真的有那么重要吗?

模型单体化是三维建模绕不开的一关&#xff0c;日常想要实现模型单体化可以使用一些软件加以辅助。 比如【云端地球&#xff08;Das Earth&#xff09;】 这是一款集中于实景三维建模与展示、建模数据分析、个性化服务选择于一体的云平台&#xff1b;在线建模&#xff0c;具有…

MySQL进阶之锁(表级锁,元数据锁,意向锁)

表级锁 介绍 表级锁&#xff0c;每次操作锁住整张表。锁定粒度大&#xff0c;发生锁冲突的概率最高&#xff0c;并发度最低。应用在MyISAM、 InnoDB、BDB等存储引擎中。 对于表级锁&#xff0c;主要分为以下三类&#xff1a; 表锁 元数据锁&#xff08;meta data lock&…

微信小程序(二十九)交互提示-界面加载框和提示框

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.showLoading加载框示范 2.showToast提示框示范 源码&#xff1a; index.wxml <!-- 列表渲染基础写法&#xff0c;不明白的看上一篇 --> <view class"students"><view class"it…

浅谈WPF之UniformGrid和ItemsControl

在日常开发中&#xff0c;有些布局非常具有规律性&#xff0c;比如相同的列宽&#xff0c;行高&#xff0c;均匀的排列等&#xff0c;为了简化开发&#xff0c;WPF提供了UniformGrid布局和ItemsControl容器&#xff0c;本文以一个简单的小例子&#xff0c;简述&#xff0c;如何…

RabbitMQ控制台的基本使用

启动RabbitMQ后&#xff0c;浏览器 http://localhost:15672 打开RabbitMQ的控制台页面后&#xff0c;登录默认账户guest。 一. 添加队列 控制台选择队列&#xff0c;然后选择添加队列&#xff0c;队列类型默认经典类型&#xff0c;然后输入队列名称&#xff0c;最后添加队列。…

Kore.ai获10亿元融资,提供定制化类ChatGPT助手

1月31日&#xff0c;生成式AI和企业对话平台Kore.ai在官网宣布&#xff0c;获得1.5 亿美元&#xff08;约10.7亿元&#xff09;融资。本次由FTV Capital 领投&#xff0c;英伟达等跟投。 Kore.ai主要提供银行、医疗、零售、营销、人力资源等多种领域的&#xff0c;定制化类Cha…

Leetcode的AC指南 —— 栈与队列 :1047.删除字符串中的所有相邻重复项

摘要&#xff1a; **Leetcode的AC指南 —— 栈与队列 &#xff1a;1047.删除字符串中的所有相邻重复项 **。题目介绍&#xff1a;给出由小写字母组成的字符串 S&#xff0c;重复项删除操作会选择两个相邻且相同的字母&#xff0c;并删除它们。 在 S 上反复执行重复项删除操作&a…

RHCE DNS域名解析服务器

目录 1. 正向解析 1.1 安装必要软件 1.2 配置静态ip 1.3 DNS配置 1.4 测试 2. 反向解析 2.1 关闭安全软件&#xff0c;安装必要软件 2.2 配置静态ip 2.3 DNS配置 2.4 测试 1. 正向解析 1.1 安装必要软件 1.2 配置静态ip 服务器配置 nmcli c modify ens32 ipv4.method man…

【乳腺肿瘤诊断分类及预测】基于PNN概率神经网络

课题名称&#xff1a;基于PNN的乳腺肿瘤诊断分类及预测 版本日期&#xff1a;2023-06-15 运行方式: 直接运行PNN0501.m 文件即可 代码获取方式&#xff1a;私信博主或QQ&#xff1a;491052175 模型描述&#xff1a; 威斯康辛大学医学院经过多年的收集和整理&#xff0c;建…

mysql 锁知识汇总

目录 一、锁1.1 什么是锁&#xff1f;1.2 全局锁1.2.1 定义1.2.2 应用场景1.2.3 会出现的问题1.2.4 解决方法 1.3 表级锁1.3.1 表锁1.3.2 元数据锁&#xff08;MDL&#xff09;1.3.3 意向锁1.3.4 AUTO-INC锁 1.4 行级锁1.4.1 记录锁(Record Lock)1.4.2 间隙锁(Gap Lock)1.4.3 N…

国家组织考试并唯一认可的IT类资格证书:计算机技术与软件专业技术资格(水平)考试证书

目录 一、这么多IT类证书为什么只有软考证书权威 1.根据身份选择并考证 2.根据需要选择考试 3.要根据证书的出身选择考试 二、软考的考试内容 三、证书样张 计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff0c;简称“软考”&#xff0c;是人力资源…