咱们晓得,原生Android集成Flutter次要有两种形式,一种是创立flutter module,而后以原生module那样依赖;另一种形式是将flutter module打包成aar,而后在原生工程中依赖aar包,官网举荐aar的形式接入。

如何在原生Android工程中以aar的形式接入Flutter,大家能够参考我之前文章的介绍:原生Android工程接入Flutter aar。明天想给大家分享的是FlutterFragment的应用。

一、Android原生工程

在Android原生开发中,实现底部Tab导航通常有3种形式,别离是:

  • RadioGroup + ViewPager + Fragment:可能预加载相邻的Fragment
  • FragmentTabHost + Fragment:加载选中的Fragment
  • BottomNavigationView:有选中动画成果

此处,咱们应用BottomNavigationView来实现底部Tab导航。首先,咱们新建一个Android原生工程,而后再新建三个 Fragment 。activity_main.xml布局代码如下:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingTop="?attr/actionBarSize">    <FrameLayout        android:id="@+id/fl_container"        android:layout_width="match_parent"        android:layout_height="0dp"        app:layout_constraintBottom_toTopOf="@id/bottom_navigation"        app:layout_constraintLeft_toLeftOf="parent"        app:layout_constraintTop_toTopOf="parent" />    <com.google.android.material.bottomnavigation.BottomNavigationView        android:id="@+id/bottom_navigation"        android:layout_width="0dp"        android:layout_height="wrap_content"        android:layout_marginStart="0dp"        android:layout_marginEnd="0dp"        android:background="?android:attr/windowBackground"        app:itemTextColor="@color/tab_text_color"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintLeft_toLeftOf="parent"        app:layout_constraintRight_toRightOf="parent"        app:menu="@menu/bottom_nav_menu" /></androidx.constraintlayout.widget.ConstraintLayout>

代码中引入了一个bottom_nav_menu.xml布局,代码如下:

<menu xmlns:android="http://schemas.android.com/apk/res/android">    <item        android:id="@+id/nav_home"        android:icon="@drawable/tab_home"        android:title="@string/tab_home" />    <item        android:id="@+id/nav_car"        android:icon="@drawable/tab_car"        android:title="@string/tab_car" />    <item        android:id="@+id/nav_me"        android:icon="@drawable/tab_mine"        android:title="@string/tab_me" /></menu>

其中,BottomNavigationView罕用的属性如下:

  • app:iteamBackground:指的是底部导航栏的背景色彩,默认是主题的色彩
  • app:menu:指的是底部菜单(文字和图片都写在这个外面,举荐图片应用矢量图)
  • app:itemTextColor:指的是导航栏文字的色彩
  • app:itemIconTint:指的是导航栏中图片的色彩

最初,在MainActivity.java中实现Tab的切换,代码如下:

class MainActivity : AppCompatActivity() {    private var fragments = mutableListOf<Fragment>()    private var lastfragment = 0    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)        initFragment()        initNavigationSelectedListener()    }    private fun initFragment() {        val homeFragment = HomeFragment()        val carFragment = CarFragment()        val mineFragment = MineFragment()        fragments.add(homeFragment)        fragments.add(carFragment)        fragments.add(mineFragment)        supportFragmentManager.beginTransaction()            .replace(R.id.fl_container, homeFragment)            .show(homeFragment)            .commit()    }    private fun switchFragment(index: Int) {        if (lastfragment != index) {            val transaction = supportFragmentManager.beginTransaction()            //暗藏上个Fragment            transaction.hide(fragments[lastfragment])            if (!fragments[index].isAdded) {                transaction.add(R.id.fl_container, fragments[index])            }            transaction.show(fragments[index]).commitAllowingStateLoss()            lastfragment = index        }    }    private fun initNavigationSelectedListener() {        findViewById<BottomNavigationView>(R.id.bottom_navigation).setOnNavigationItemSelectedListener { item ->            when (item.itemId) {                R.id.nav_home -> {                    switchFragment(0)                    return@setOnNavigationItemSelectedListener true                }                R.id.nav_car -> {                    switchFragment(1)                    return@setOnNavigationItemSelectedListener true                }                R.id.nav_me -> {                    switchFragment(2)                    return@setOnNavigationItemSelectedListener true                }            }            false        }    }}

二、引入Flutter Module

首先,创立一个Flutter Module工程。创立Flutter Module有两种形式,一种是应用Android Studio进行生成,另一种是间接应用命令行。应用命令行创立flutter module的如下:

flutter create -t module flutter_module

而后,进入到flutter_module,执行flutter build aar命令生成aar包,如果没有任何出错,会在/flutter_module/.android/Flutter/build/outputs目录下生成对应的aar包,如下图。


接下来,咱们把生成的aar包拷贝到Android工程的libs中,而后关上app/build.grade增加本地依赖。

repositories {    flatDir {        dirs 'libs'    }}dependencies {    ...    //增加本地依赖    implementation fileTree(dir: 'libs', include: ['*.jar'])    implementation(name: 'flutter_relaese-1.0', ext: 'aar')    implementation 'io.flutter:flutter_embedding_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'    implementation 'io.flutter:armeabi_v7a_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'    implementation 'io.flutter:arm64_v8a_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'    implementation 'io.flutter:x86_64_debug:1.0.0-f0826da7ef2d301eb8f4ead91aaf026aa2b52881'}

而后在外层的build.gradle中申明为本地依赖,代码如下:

buildscript {repositories {   ...     maven {        url "http://download.flutter.io"        //flutter依赖    }  }  dependencies {    classpath 'com.android.tools.build:gradle:4.0.0'  }}

三、应用Flutter Module

默认状况下,Android提供了FlutterActivity、Fragment和FlutterView视图,本例子咱们讲的是Fragment的应用。

首先,咱们创立一个 FlutterEngineGroup 对象,FlutterEngineGroup 能够用来治理多个 FlutterEngine 对象,而多个 FlutterEngine 是能够共享资源的,目标是缩小 FlutterEngine 的资源占用,MyApplication的代码如下:

class MyApplication : Application() {    lateinit var engineGroup: FlutterEngineGroup    override fun onCreate() {        super.onCreate()        // 创立FlutterEngineGroup对象        engineGroup = FlutterEngineGroup(this)    }}

接着,创立一个 FlutterEngineManager 缓存治理类,在 FlutterEngineManager 中创立一个静态方法 flutterEngine,用来缓存FlutterEngine。

object FlutterEngineManager {    fun flutterEngine(context: Context, engineId: String, entryPoint: String): FlutterEngine {        // 1. 从缓存中获取FlutterEngine        var engine = FlutterEngineCache.getInstance().get(engineId)        if (engine == null) {            // 如果缓存中没有FlutterEngine            // 1. 新建FlutterEngine,执行的入口函数是entryPoint            val app = context.applicationContext as MyApplication            val dartEntrypoint = DartExecutor.DartEntrypoint(                FlutterInjector.instance().flutterLoader().findAppBundlePath(), entryPoint            )            engine = app.engineGroup.createAndRunEngine(context, dartEntrypoint)            // 2. 存入缓存            FlutterEngineCache.getInstance().put(engineId, engine)        }        return engine!!    }    }

在下面的代码中,咱们会先从中获取缓存的 FlutterEngine ,如果没有则新建一个 FlutterEngine ,而后再缓存起来。

接下来,咱们将 FlutterEngine 和 FlutterFragment 进行绑定,如果默认没有提供路由,那么关上的是flutter module的路由首页。如果要指定flutter module的首页,能够应用setInitialRoute()办法。

class HomeFragment : Fragment() {    // 1. FlutterEngine对象    private lateinit var engine: FlutterEngine    private var engineId="home_fra"    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        // 2. 通过FBFlutterEngineManager获取FlutterEngine对象        engine = FlutterEngineManager.flutterEngine(requireActivity(), engineId, "main")        // 3. 用FlutterEngine对象构建出一个FlutterFragment        val flutterFragment = FlutterFragment.withCachedEngine(engineId).build<FlutterFragment>()        // 4. 显示FlutterFragment        parentFragmentManager.beginTransaction().replace(R.id.home_fl, flutterFragment).commit()    }    override fun onCreateView(        inflater: LayoutInflater,        container: ViewGroup?,        savedInstanceState: Bundle?    ): View? {        return inflater.inflate(R.layout.fragment_home, container, false)    }}

咱们这里应用缓存的 FlutterEngine 更能节俭资源,因为 Bottom Navigation Activity 的 Fragment 来回切换的时候, Fragment 是会从新新建和销毁,比拟耗费资源。

如果咱们在进入将二级页面时候,返回的时候,还须要将 activity_main.xml 中的 BottomNavigationView 暗藏,波及的代码如下。

class MainActivity : AppCompatActivity() {   ...//省略其余代码    fun switchBottomView(show: Boolean) {        val navView: BottomNavigationView = findViewById(R.id.nav_view)        if (show) {            navView.visibility = View.VISIBLE        } else {            navView.visibility = View.GONE        }    }}

如果要和Flutter进行数据交互,那么咱们能够应用MethodChannel,而后应用setMethodCallHandler即可将Android数据回调给Fluter,代码如下。

class HomeFragment : Fragment() {    // 1. FlutterEngine对象    private lateinit var engine: FlutterEngine    private var engineId="home_fra"    private lateinit var channel: MethodChannel    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        initEngine()        initChannel()    }    private fun initEngine() {        // 2. 通过FBFlutterEngineManager获取FlutterEngine对象        engine = FlutterEngineManager.flutterEngine(requireActivity(), engineId, "main")        // 3. 用FlutterEngine对象构建出一个FlutterFragment        val flutterFragment = FlutterFragment.withCachedEngine(engineId).build<FlutterFragment>()        // 4. 显示FlutterFragment        parentFragmentManager.beginTransaction().replace(R.id.home_fl, flutterFragment).commit()    }    private fun initChannel() {        channel = MethodChannel(engine.dartExecutor.binaryMessenger, "tab_switch")        channel.setMethodCallHandler { call, result ->            when (call.method) {                "showTab" -> {                    val activity = requireActivity() as MainActivity                    activity.switchBottomView(true)                    result.success(null)                }                "hideTab" -> {                    val activity = requireActivity() as MainActivity                    activity.switchBottomView(false)                    result.success(null)                }                else -> {                    result.notImplemented()                }            }        }    }    override fun onCreateView(        inflater: LayoutInflater,        container: ViewGroup?,        savedInstanceState: Bundle?    ): View? {        return inflater.inflate(R.layout.fragment_home, container, false)    }}

接着在Flutter外面是有invokeMethod办法注入即可。

class PluginManager {  static const MethodChannel _channel = MethodChannel('tab_switch');  static Future<String> showTab(Map params) async {    String resultStr = await _channel.invokeMethod('showTab', params);    return resultStr;  }}

目前原生挪动APP能够在利用集成多个 Flutter Module ,这样就不便咱们进行多业务的模块化开发了。除了FlutterActivity、Fragment,在Android中能够应用FlutterView 会略微简单点,应应用个 FlutterView 须要绑定生命周期,须要开发者本人去治理FlutterView生命周期。