4

Flutter混合开发之FlutterFragment使用

 2 years ago
source link: https://segmentfault.com/a/1190000041248904
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

我们知道,原生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生命周期。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK