Android 實現單Activity多Fragment頁面項目

前言

  以往項目都是多Activity實現,總體實現很方便。但是現在流行使用單Activity+多Fragment的形式實現項目,例如比較流行的Navigation基本可以滿足,但是有時候會存在從一個頁面跳轉到另一個頁面並且回傳數據的問題,Navigation組件的話內部實現原理是使用replace()方式添加Fragment,這樣會導致兩個問題,第一個是每次切換Fragment都會重建,第二是無法做到數據的回傳,網上也有一些針對此問題的解決方案。本文並不打算使用Navigation實現,而是通過使用Results API的方式。

Fragment通信

  今年 Google 推出了 Fragment Result API 和 Activity Results API,用來取代之前的 Activity 和 Fragment 之間通信方式的不足。Fragment Result API 主要介紹 Fragment 間通信的新方式,是在 Fragment 1.3.0-alpha04 新增加的 API ,而現在最新版本已經到 fragment-1.3.0-beta01 應該很快就能應用在項目裏面了。本文就是通過Result API實現Fragment之間的通信。

接受數據

在需要接受數據的Fragment裏面註冊setFragmentResultListener()監聽

FragmentManager.setFragmentResultListener(
    requestKey,
    lifecycleOwner,
    FragmentResultListener { requestKey: String, result: Bundle ->
        // Handle result
    })

參數解釋:
requestKey :請求跳轉到另一個fragment的字符串key
lifecycleOwner:生命週期觀察者
FragmentResultListener :fragment結果回到監聽

發送數據

當前fragment想把數據傳遞給上一個頁面,則需要通過設置setFragmentResult()方法設置數據,

parentFragmentManager.setFragmentResult(
    requestKey, // Same request key FragmentA used to register its listener
    bundleOf(key to value) // The data to be passed to FragmentA
)

參數解釋:
requestKey :請求跳轉到另一個fragment的字符串key
Bundle:用於傳遞迴調結果的bundle

Fragment攜帶數據跳轉

  如果此次Fragment是做攜帶數據的跳轉則必須攜帶requestKey,requestKey與設置回調數據時候使用同一個requestKey,因爲想把回調封裝進跳轉的參數中,因此resultCallback充當Fragment回到結果的監聽回調。

 /**
     * fragment中添加fragment(replace方式)
     * @param target Fragment 目標fragment
     * @param requestKey String 請求key
     * @param args Bundle 攜帶參數
     * @param resultCallback FragmentResultListener 跳轉結果回調
     */
    fun startForResult(target: Fragment, requestKey: String, args: Bundle? = null, resultCallback: FragmentResultListener) {
        val targetArgs: Bundle = args ?: Bundle()
        targetArgs.putString(REQUEST_KEY, requestKey)
        target.arguments = targetArgs
        manager.beginTransaction()
            .setReorderingAllowed(true)
            .add(R.id.fragment_root, target)
            .addToBackStack(tag ?: target.javaClass.simpleName)
            .commit()
        target.setFragmentResultListener(requestKey) { it, bundle ->
            resultCallback.onFragmentResult(it, bundle)
        }
    }

BaseFragment封裝

  關於ViewBinding和ViewModel的封裝在前一篇文章已經講了。對於ViewModel的封裝當初公司有人提議是每個Fragment必須去實現一個ViewModel,如果沒有則使用通用的EmptyViewModel代替,這樣考慮是爲了不讓代碼中出現太多?空校驗,這一塊看自己決定。整個BaseFragment代碼如下:

abstract class BaseFragment<ViewModelLazy : ViewModel, Binding : ViewBinding> : Fragment(), View.OnClickListener {

    protected lateinit var TAG: String
    protected lateinit var ctx: AppCompatActivity
    protected lateinit var binding: Binding
    protected lateinit var manager: FragmentManager
    protected var requestKey: String? = null
    protected var viewModel: ViewModelLazy? = null

    companion object {
        const val REQUEST_KEY = "requestKey"
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        this.ctx = context as AppCompatActivity
        TAG = this::class.java.simpleName
        manager = ctx.supportFragmentManager
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        Log.e(TAG, "fragment_onCreateView")
        viewModel = bindViewModel()
        arguments?.let { bindBundle(it) }
        binding = bindLayout()
        initView()
        initData()
        bindObserver()
        bindListener()
        return binding.root
    }

    /**
     * 綁定ViewModel
     * @return ViewModelLazy
     */
    open fun bindViewModel(): ViewModelLazy? = null

    /**
     * 綁定intent
     * @param bundle Bundle
     */
    open fun bindBundle(bundle: Bundle) {
        requestKey = bundle.getString(REQUEST_KEY)
    }

    /**
     * 綁定佈局
     * @return View
     */
    abstract fun bindLayout(): Binding

    /**
     * 初始化view
     */
    open fun initView() {

    }

    /**
     * 初始化數據
     */
    open fun initData() {

    }

    /**
     * 重新加載
     */
    open fun onReload() {

    }

    /**
     * 綁定observer
     */
    open fun bindObserver() {

    }

    /**
     * 綁定監聽
     */
    open fun bindListener() {

    }

    /**
     * 監聽回調
     * @param v View
     */
    override fun onClick(v: View?) {

    }

    /**
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +++++++++++++++++++++++++++++++++++++++++++++++++fragment回傳數據++++++++++++++++++++++++++++++++
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     */
    /**
     * 設置回傳fragment數據
     * @param args Bundle
     */
    fun onBackResult(args: Bundle) {
        requestKey?.let {
            setFragmentResult(it, args)
            manager.popBackStack()
        }
    }

    interface FragmentResultListener {
        /**
         * Callback used to handle results passed between fragments.
         *
         * @param requestKey key used to store the result
         * @param result result passed to the callback
         */
        fun onFragmentResult(requestKey: String, result: Bundle)
    }


    /**
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +++++++++++++++++++++++++++++++++++++++++++++++++頁面跳轉++++++++++++++++++++++++++++++++++++++++
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     */
    /**
     * fragment中添加fragment(add方式)
     * @param target Fragment
     * @param args Bundle
     */
    fun start(target: Fragment, args: Bundle? = null) {
        args?.let {
            target.arguments = it
        }
        manager.beginTransaction()
            .setReorderingAllowed(true)
            .add(R.id.fragment_root, target)
            .addToBackStack(target.javaClass.simpleName)
            .commit()
    }

    /**
     * fragment中添加fragment(add方式)
     * @param target Fragment
     * @param args Bundle
     */
    fun start(target: String, args: Bundle? = null) {
        val navigation = ARouter.getInstance().build(target).navigation() as BaseFragment<*, *>
        args?.let {
            navigation.arguments = it
        }
        manager.beginTransaction()
            .setReorderingAllowed(true)
            .add(R.id.fragment_root, navigation)
            .addToBackStack(target.javaClass.simpleName)
            .commit()
    }

    /**
     * fragment中添加fragment(replace方式)
     * @param target Fragment
     * @param args Bundle
     */
    fun replace(target: Fragment, args: Bundle? = null) {
        args?.let {
            target.arguments = it
        }
        manager.beginTransaction()
            .setReorderingAllowed(true)
            .replace(R.id.fragment_root, target)
            .addToBackStack(target.javaClass.simpleName)
            .commit()
    }

    /**
     * fragment中添加fragment(replace方式)
     * @param target Fragment
     * @param args Bundle
     */
    fun startRoot(target: Fragment, args: Bundle? = null) {
        args?.let {
            target.arguments = it
        }
        clearFragmentStack()
        manager.beginTransaction()
            .setReorderingAllowed(true)
            .replace(R.id.fragment_root, target)
            .addToBackStack(target.javaClass.simpleName)
            .commit()
    }

    /**
     * fragment中添加fragment(replace方式)
     * @param target Fragment 目標fragment
     * @param requestKey String 請求key
     * @param args Bundle 攜帶參數
     * @param resultCallback FragmentResultListener 跳轉結果回調
     */
    fun startForResult(target: Fragment, requestKey: String, args: Bundle? = null, resultCallback: FragmentResultListener) {
        val targetArgs: Bundle = args ?: Bundle()
        targetArgs.putString(REQUEST_KEY, requestKey)
        target.arguments = targetArgs
        manager.beginTransaction()
            .setReorderingAllowed(true)
            .add(R.id.fragment_root, target)
            .addToBackStack(tag ?: target.javaClass.simpleName)
            .commit()
        target.setFragmentResultListener(requestKey) { it, bundle ->
            resultCallback.onFragmentResult(it, bundle)
        }
    }

    /**
     * 清空當前棧所有fragment
     */
    private fun clearFragmentStack() {
        val count: Int = manager.backStackEntryCount
        if (count > 0) {
            val entry: FragmentManager.BackStackEntry =
                manager.getBackStackEntryAt(0)
            manager.popBackStack(
                entry.name,
                FragmentManager.POP_BACK_STACK_INCLUSIVE
            )
        }
    }

    /**
     * 退出當前的fragment
     */
    fun onBackPressed() {
        manager.popBackStack()
    }

    /**
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     * +++++++++++++++++++++++++++++++++++++++++++++++++生命週期++++++++++++++++++++++++++++++++++++++++
     * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     */
    override fun onResume() {
        super.onResume()
        Log.d(TAG, "fragment_onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "fragment_onPause")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "fragment_onStart")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "fragment_onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "fragment_onDestroy")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d(TAG, "fragment_onDestroyView")
    }

    override fun onDetach() {
        super.onDetach()
        Log.d(TAG, "fragment_onDetach")
    }
}

總結

  如上封裝的其實也是不夠完善的,但是總體可以實現單Activity+多Fragment模式的項目,並且不需要引用第三方框架,當然上面也封裝了關於使用Arouter路由的方式實現組件化的Fragment通信方式。通過上面的方式寫了demo,具體實現可參考demo。
Needle: 單Activity+多Fragment模式創建項目 (gitee.com)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章