乐趣区

关于flutter:Flutter混合开发之FlutterFragment使用

咱们晓得,原生 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 生命周期。

退出移动版