类似优酷的网站开发,wordpress 纯净版下载地址,设计网站一般多少钱,企业标准网上备案网站前言
本文主要讲解 Flutter 和 Android原生之间#xff0c;页面相互跳转、传参#xff0c;
但其中用到了两端相互通信的知识#xff0c;非常建议先看完这篇 讲解通信的文章#xff1a;
Flutter 与 Android原生 相互通信#xff1a;BasicMessageChannel、MethodChannel、…前言
本文主要讲解 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?vp6cK_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按键 / 左滑屏幕 进行回退是没有问题的因为这种回退方式调用的是原生APIAndroid原生不光提供FlutterView渲染Flutter页面结果还会创建FlutterActivity和FlutterView进行绑定
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/methodprivate val METHOD_CHANNEL_HOME com.example.flutter_nav_android/home/methodprivate val NAV_FLUTTER_LOGIN_NOTICE navFlutterLoginNoticeprivate val POP_NOTICE popNoticeprivate val PERSONAL_POP_NOTICE personalPopNoticeoverride 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端 发来的 数据对象* resultAndroid端 给 Flutter端 执行回调的接口对象*/override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {val methodName: String call.methodwhen (methodName) { // 销毁 Flutter Login 页面POP_NOTICE - {val age call.argumentInt(age)getResult(age.toString())loginRouterManager.mEngine!!.navigationChannel.popRoute()}else - {result.notImplemented()}}}override fun onClick(v: View?) {when (v) {bind.toFlutter - { // 前往 Flutter Login 页面val map: MutableMapString, String mutableMapOfString, String()map[name] 老王loginMethodChannel.invokeMethod(NAV_FLUTTER_LOGIN_NOTICE,map)loginRouterManager.push()}bind.pop - { // 销毁 Android Personal 页面val map: MutableMapString, Int mutableMapOfString, 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/methodprivate val METHOD_CHANNEL_STUDENT com.example.flutter_nav_android/student/methodprivate val NAV_FLUTTER_BOOK_NOTICE navFlutterBookNoticeprivate val NAV_FLUTTER_STUDENT_NOTICE navFlutterStudentNoticeoverride 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: MutableMapString, String mutableMapOfString, String()map[title] BookbookChannel.invokeMethod(NAV_FLUTTER_BOOK_NOTICE,map)val studentEngine FlutterRouterManager.getEngineCacheInstance(student_engine)val studentChannel MethodChannel(studentEngine!!.dartExecutor,METHOD_CHANNEL_STUDENT)val map2: MutableMapString, String mutableMapOfString, String()map2[title] StudentstudentChannel.invokeMethod(NAV_FLUTTER_STUDENT_NOTICE,map2)}/*** 初始化页面*/private fun initView() {bookFragment FlutterFragment.withCachedEngine(book_engine).transparencyMode(TransparencyMode.transparent) // 背景透明避免切换页面出现闪烁.shouldAttachEngineToActivity(false) // 是否让Flutter控制Activitytrue可以 false不可以默认值 true.build()supportFragmentManager.beginTransaction().add(bind.bookFragment.id, bookFragment).commit()studentFragment FlutterFragment.withCachedEngine(student_engine).transparencyMode(TransparencyMode.transparent) // 背景透明避免切换页面出现闪烁.shouldAttachEngineToActivity(false) // 是否让Flutter控制Activitytrue可以 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: ArrayString?,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/methodprivate val NAV_ANDROID_PERSONAL_NOTICE navAndroidPersonalNoticeprivate val NAV_FLUTTER_HOME_NOTICE navFlutterHomeNoticeprivate val POP_NOTICE popNoticeprivate 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端 发来的 数据对象* resultAndroid端 给 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.argumentString(name))startActivity(intent)}POP_NOTICE - { // 销毁 Flutter Home 页面val age call.argumentInt(age)getResult(age.toString())homeRouterManager.mEngine!!.navigationChannel.popRoute()}else - {result.notImplemented()}}}override fun onClick(v: View?) {when (v) {bind.toFlutter - { // 前往 Flutter Home 页面val map: MutableMapString, String mutableMapOfString, 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 version1.0 encodingutf-8?
layout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autodata/dataandroidx.constraintlayout.widget.ConstraintLayoutandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentTextViewandroid:idid/app_barandroid:layout_widthmatch_parentandroid:layout_height60dpandroid:layout_marginBottom16dpandroid:backgroundcolor/cardview_shadow_start_colorandroid:gravitycenter_verticalandroid:paddingStart16dpandroid:textAndroid Personalandroid:textColorcolor/material_dynamic_primary60android:textSize26spandroid:textStyleboldapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintTop_toTopOfparent /TextViewandroid:idid/init_vandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:gravitycenter_verticalandroid:text接收初始化参数android:textColorcolor/material_dynamic_primary60android:textSize20spandroid:textStyleboldapp:layout_constraintBottom_toBottomOfparentapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintRight_toRightOfparentapp:layout_constraintTop_toTopOfparent /TextViewandroid:idid/result_vandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:text接收上一页返回参数android:textColorcolor/material_dynamic_primary60android:textSize20spandroid:textStyleboldapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintRight_toRightOfparentapp:layout_constraintTop_toBottomOfid/init_v /Buttonandroid:idid/to_flutterandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:text前往 Flutter Loginandroid:textSize20spandroid:textAllCapsfalseapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintRight_toRightOfparentapp:layout_constraintTop_toBottomOfid/result_v /Buttonandroid:idid/popandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop16dpandroid:text返回 上一页android:textSize20spandroid:textAllCapsfalseapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintRight_toRightOfparentapp:layout_constraintTop_toBottomOfid/to_flutter //androidx.constraintlayout.widget.ConstraintLayout/layout
activity_school.xml
?xml version1.0 encodingutf-8?
layout xmlns:androidhttp://schemas.android.com/apk/res/androiddata/dataLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:paddingTop20dpandroid:orientationverticalTextViewandroid:idid/app_barandroid:layout_widthmatch_parentandroid:layout_height60dpandroid:backgroundcolor/cardview_shadow_start_colorandroid:gravitycenter_verticalandroid:paddingStart16dpandroid:textAndroid Schoolandroid:textColorcolor/material_dynamic_primary60android:textSize26spandroid:textStylebold /FrameLayoutandroid:idid/book_fragmentandroid:layout_weight1android:layout_widthmatch_parentandroid:layout_height0dp /FrameLayoutandroid:idid/student_fragmentandroid:layout_weight1android:layout_widthmatch_parentandroid:layout_height0dp //LinearLayout
/layout
activity_main.xml
?xml version1.0 encodingutf-8?
layout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autodata/dataandroidx.constraintlayout.widget.ConstraintLayoutandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentTextViewandroid:idid/app_barandroid:layout_widthmatch_parentandroid:layout_height60dpandroid:layout_marginBottom16dpandroid:backgroundcolor/cardview_shadow_start_colorandroid:gravitycenter_verticalandroid:paddingStart16dpandroid:textAndroid Mainandroid:textColorcolor/material_dynamic_primary60android:textSize26spandroid:textStyleboldapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintTop_toTopOfparent /TextViewandroid:idid/result_vandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:gravitycenter_verticalandroid:text接收上一页返回参数android:textColorcolor/material_dynamic_primary60android:textSize20spandroid:textStyleboldapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintTop_toTopOfparentapp:layout_constraintRight_toRightOfparentapp:layout_constraintBottom_toBottomOfparent/Buttonandroid:idid/to_flutterandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:text前往 Flutter Homeandroid:textSize20spandroid:textAllCapsfalseapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintTop_toBottomOfid/result_vapp:layout_constraintRight_toRightOfparent /Buttonandroid:idid/to_flutter_fragmentandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:text前往 Flutter Fragmentandroid:textSize20spandroid:textAllCapsfalseapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintTop_toBottomOfid/to_flutterapp:layout_constraintRight_toRightOfparent //androidx.constraintlayout.widget.ConstraintLayout/layout
AndroidManifest.xml
manifest xmlns:androidhttp://schemas.android.com/apk/res/androidapplicationandroid:name${applicationName}android:iconmipmap/ic_launcherandroid:labelflutter_nav_androidandroid:themestyle/AppThemeactivityandroid:nameio.flutter.embedding.android.FlutterActivityandroid:configChangesorientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiModeandroid:hardwareAcceleratedtrueandroid:windowSoftInputModeadjustResize /activity android:name.ui.activity.SchoolActivity android:exportedtrue/activity android:name.ui.activity.PersonalActivity android:exportedtrue/activityandroid:name.ui.activity.MainActivityandroid:configChangesorientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiModeandroid:exportedtrueandroid:hardwareAcceleratedtrueandroid:launchModesingleTopandroid:windowSoftInputModeadjustResize!-- 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:nameio.flutter.embedding.android.NormalThemeandroid:resourcestyle/NormalTheme /intent-filteraction android:nameandroid.intent.action.MAIN /category android:nameandroid.intent.category.LAUNCHER //intent-filter/activity!-- Dont delete the meta-data below.This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --meta-dataandroid:nameflutterEmbeddingandroid:value2 //application!-- Required to query activities that can process text, see:https://developer.android.com/training/package-visibility?hlen 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. --queriesintentaction android:nameandroid.intent.action.PROCESS_TEXT /data android:mimeTypetext/plain //intent/queries
/manifestFlutter代码
book.dart
import package:flutter/material.dart;
import package:flutter/services.dart;class Book extends StatefulWidget {const Book({super.key});overrideStateBook createState() _BookState();
}class _BookState extends StateBook {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函数就会被触发Futuredynamic 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});overrideStateHome createState() _HomeState();
}class _HomeState extends StateHome {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函数就会被触发Futuredynamic 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 {MapString, int map {age: 12};homeMethodChannel.invokeMethod(POP_NOTICE, map);}}/// 前往 Android Personal 页面navAndroidPersonal() {MapString, 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});overrideStateLogin createState() _LoginState();
}class _LoginState extends StateLogin {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函数就会被触发Futuredynamic 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 {MapString, 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});overrideStateStudent createState() _StudentState();
}class _StudentState extends StateStudent {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函数就会被触发Futuredynamic 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方式 进行 页面跳转、传参下面直接使用引擎进行 页面跳转、传参但实用价值不高因为弊端太大
比如
只提供了ListString类型进行传参目标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 mutableListOfString()dartEntrypointArgs.add(张三)// 这种方式传参数会导致这个Flutter页面中的Widget默认参数全部失效// 这种只有 Dart主入口文件比如main.dart的 main函数才能接收到参数// void main(ListString args) {}// flutterEngine.dartExecutor.executeDartEntrypoint(// DartExecutor.DartEntrypoint.createDefault(),// dartEntrypointArgs// )// 这种可以指定页面接收但需要在主入口文件里先声明 // void showPersonal(ListString 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: MutableMapString, String mutableMapOfString, 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(ListString 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(ListString 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});overrideStatePersonal createState() _PersonalState();
}class _PersonalState extends StatePersonal {/// 在这个页面中使用的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相互跳转、传参