We know that there are two main ways to integrate Flutter with native Android. One is to create a flutter module, and then rely on it as a native module; the other is to package the flutter module into aar, and then rely on the aar package in the native project, which is officially recommended. aar way to access.
How to access Flutter as aar in native Android project, you can refer to the introduction of my previous article: Native Android project access to Flutter aar . What I want to share with you today is the use of FlutterFragment.
1. Android native engineering
In Android native development, there are usually three ways to implement bottom Tab navigation, namely:
- RadioGroup + ViewPager + Fragment: Ability to preload adjacent Fragments
- FragmentTabHost + Fragment: Load the selected Fragment
- BottomNavigationView: has a selected animation effect
Here, we use BottomNavigationView to implement bottom tab navigation. First, we create a new Android native project, and then create three new Fragments. The activity_main.xml layout code is as follows:
<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>
The code introduces a bottom_nav_menu.xml layout, the code is as follows:
<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>
Among them, the commonly used properties of BottomNavigationView are as follows:
- app:iteamBackground: refers to the background color of the bottom navigation bar, the default is the color of the theme
- app:menu: refers to the bottom menu (text and pictures are written in this, it is recommended to use vector graphics)
- app:itemTextColor: refers to the color of the navigation bar text
- app:itemIconTint: refers to the color of the image in the navigation bar
Finally, implement Tab switching in MainActivity.java, the code is as follows:
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
}
}
}
2. Introducing Flutter Module
First, create a Flutter Module project. There are two ways to create a Flutter Module, one is to use Android Studio to generate it, and the other is to use the command line directly. Use the command line to create a flutter module as follows:
flutter create -t module flutter_module
Then, enter the flutter_module and execute the flutter build aar command to generate the aar package. If there is no error, the /flutter_module/.android/Flutter/build/outputs
directory, as shown in the following figure.
Next, we copy the generated aar package to the libs of the Android project, and then open app/build.grade to add local dependencies.
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'
}
Then declare it as a local dependency in the outer build.gradle, the code is as follows:
buildscript {
repositories {
...
maven {
url "http://download.flutter.io" //flutter依赖
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
}
}
3. Use Flutter Module
By default, Android provides FlutterActivity, Fragment and FlutterView views. In this example, we are talking about the use of Fragment.
First, we create a FlutterEngineGroup object. FlutterEngineGroup can be used to manage multiple FlutterEngine objects, and multiple FlutterEngines can share resources in order to reduce the resource occupation of FlutterEngine. The code of MyApplication is as follows:
class MyApplication : Application() {
lateinit var engineGroup: FlutterEngineGroup
override fun onCreate() {
super.onCreate()
// 创建FlutterEngineGroup对象
engineGroup = FlutterEngineGroup(this)
}
}
Next, create a FlutterEngineManager cache management class, and create a static method flutterEngine in FlutterEngineManager to cache 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!!
}
}
In the above code, we will first get the cached FlutterEngine from it, if not, create a new FlutterEngine, and then cache it.
Next, we will bind FlutterEngine and FlutterFragment. If no routing is provided by default, the routing homepage of the flutter module will be opened. If you want to specify the home page of the flutter module, you can use the setInitialRoute() method.
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)
}
}
We use the cached FlutterEngine here to save resources, because when the Fragment of the Bottom Navigation Activity is switched back and forth, the Fragment will be re-created and destroyed, which consumes more resources.
If we enter the second-level page and return, we also need to hide the BottomNavigationView in activity_main.xml. The code involved is as follows.
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
}
}
}
If we want to interact with Flutter, we can use MethodChannel, and then use setMethodCallHandler to call back Android data to Flutter. The code is as follows.
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)
}
}
Then in Flutter, there is an invokeMethod method to inject.
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;
}
}
At present, native mobile APP can integrate multiple Flutter Modules in the application, which facilitates the modular development of multiple services. In addition to FlutterActivity and Fragment, it is a little more complicated to use FlutterView in Android. A FlutterView needs to be bound to the life cycle, and developers need to manage the FlutterView life cycle by themselves.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。