BottomNavigation+fragment实现,解决因内存不足系统回收Activity,或者屏幕旋转导致Activiy重建导致fragment重叠的问题

1.基于BottomNavigation+fragment通过show/hide的方式实现首页面
2.出现fragment重叠的原因可点击这里查看
3.基于源码分析出现问题的原因看这里
具体原因总结如下:

因内存不足导致Activity被系统回收,或者屏幕旋转导致Activity重建,Activity会保存fragment的实例,但是不会保存fragment show/hide的状态,当Activity重建时,重新走onCreate方法,会重新创建fragment的实例,此时重新创建的fragment实例,和Activity保存的fragment的实例是不一样的,当我们重新创建的fragment的实例又提交的事务中去,就会出现fragment重叠的问题。
解决思路:Activity重建时,会保存fragment的实例,我们在onCreate方法中判断savedInstanceState不为空时,取出Activity保存的实例,重新关联到我们保存的fragment的实例的引用。

具体解决步骤:
1.首先在onCreate方法中创建我们需要添加到BottomNavigationView中的fragment实例,并将实例放入HashMap中进行存储

    private var cacheFragments: HashMap<String, Fragment> = HashMap<String, Fragment>()


            mainFragment = MainFragment.getInstance()
            secondFragment = SecondFragment.getInstance()
            threeFragment = ThreeFragment.getInstance()
            Log.d(this.javaClass.simpleName, MainFragment::class.qualifiedName!!)
            Log.d(this.javaClass.simpleName, SecondFragment::class.qualifiedName!!)
            Log.d(this.javaClass.simpleName, ThreeFragment::class.qualifiedName!!)

            cacheFragments.put(MainFragment::class.qualifiedName!!, mainFragment!!)
            cacheFragments.put(SecondFragment::class.qualifiedName!!, secondFragment!!)
            cacheFragments.put(ThreeFragment::class.qualifiedName!!, threeFragment!!)

2.然后将我们创建的fragment与BottomNavigationView关联起来,当点击对应的底部tab时,show/hide对应的fragment

        bottomNavigationView.setOnNavigationItemSelectedListener {
            Log.d(this.javaClass.name, "setOnNavigationItemSelectedListener")
            when (it.itemId) {
                R.id.main_menu -> {
                    showFragment = cacheFragments.get(MainFragment::class.qualifiedName!!)
                }
                R.id.second_menu -> {
                    showFragment = cacheFragments.get(SecondFragment::class.qualifiedName!!)
                }
                R.id.three_menu -> {
                    showFragment = cacheFragments.get(ThreeFragment::class.qualifiedName!!)
                }
            }
            //2.记录当前选中的条目id
            currentSelect = it.itemId
            //3.切换fragment的方法
            FragmentUtil.switchFragment(this, showFragment!!)
            return@setOnNavigationItemSelectedListener true
        }

3.切换对应的fragment


        private var currentShowFragment: Fragment? = null

        /**
         *切换fragment,以show/hide的方式
         * @param activity activity实例
         * @param needShowFragment 需要显示的fragment实例
         */
        fun switchFragment(activity: AppCompatActivity, needShowFragment: Fragment) {
            val beginTransaction = activity.supportFragmentManager.beginTransaction()
            //判断当前fragment是否为空,如果为空,添加需要显示的fragment并提交事务
            if (currentShowFragment == null) {
                beginTransaction.add(
                    R.id.fragment_container_view,
                    needShowFragment,
                    needShowFragment::class.qualifiedName!!
                ).commit()
            } else {
                //判断当前需要显示fragment是否已经被添加,如果被添加判断是否隐藏,如果隐藏,让其显示,并隐藏当前显示的fragment,并提交事务
                if (needShowFragment.isAdded) {
                    if (needShowFragment.isHidden) {
                        beginTransaction.show(needShowFragment).hide(currentShowFragment!!).commit()
                    }
                } else {
                    //如果当前fragment不为null,隐藏当前fragment,并显示需要显示的fragment,并提交事务
                    beginTransaction.hide(currentShowFragment!!)
                        .add(
                            R.id.fragment_container_view,
                            needShowFragment,
                            needShowFragment::class.qualifiedName!!
                        ).commit()
                }
            }
            //保存当前显示的fragment
            currentShowFragment = needShowFragment
        }

4.此时已经能正常完成fragment的切换,但是在Activity重建后,会出现重叠问题,解决该问题的思路如上所述,在savedInstanceState不为空时,找出Activity保存的实例,并重建引用,如果没有就重新创建fragment

 if (savedInstanceState != null) {

            Log.d(this.javaClass.simpleName, "savedInstanceState not null")

//            getFragmentInstance(MainFragment::class.java)
//            getFragmentInstance(SecondFragment::class.java)
//            getFragmentInstance(ThreeFragment::class.java)
            val findMainFragmentByTag =
                supportFragmentManager.findFragmentByTag(MainFragment::class.qualifiedName)
            if (findMainFragmentByTag == null) {
                mainFragment = MainFragment.getInstance()
            } else {
                mainFragment = findMainFragmentByTag as MainFragment
            }
            cacheFragments.put(MainFragment::class.java.name, mainFragment!!)

            val findSecondFragmentByTag =
                supportFragmentManager.findFragmentByTag(SecondFragment::class.qualifiedName)
            if (findSecondFragmentByTag == null) {
                secondFragment = SecondFragment.getInstance()
            } else {
                secondFragment = findSecondFragmentByTag as SecondFragment
            }
            cacheFragments.put(SecondFragment::class.java.name, secondFragment!!)

            val findThreeFragmentByTag =
                supportFragmentManager.findFragmentByTag(ThreeFragment::class.qualifiedName)
            if (findThreeFragmentByTag == null) {
                threeFragment = ThreeFragment.getInstance()
            } else {
                threeFragment = findThreeFragmentByTag as ThreeFragment
            }
            cacheFragments.put(ThreeFragment::class.java.name, threeFragment!!)


            val fragments = supportFragmentManager.fragments
            for (fragment in fragments) {
                Log.d(this.javaClass.simpleName, fragment::class.qualifiedName!!)
                //5.判断该fragment是否被添加,如果没有被添加判断是否隐藏,如果没有隐藏,则隐藏
                //目的是隐藏所有可见的fragment,重新管理显示隐藏状态
                if (fragment.isAdded) {
                    supportFragmentManager.beginTransaction().hide(fragment).commit()
                }
            }
            //6.获取activity被回收时,自己保存的选中item的id
            val currentSelectItemId = savedInstanceState.getInt("currentItemId")
            currentSelectItemId?.let {
                bottomNavigationView.selectedItemId = it
            }
        } 

5.上述代码逻辑为,先从Activity中获取其保存的fragment实例,如果没有就重新创建,然后获取所有的fragment判断是否已经被添加到Activity中,如果已经被添加,就全部隐藏,然后从savedInstanceState中获取Activity异常销毁时保存的选择tab的id,并重新设置到BottomNavigationView中

以上就是解决fragment重叠的全部流程

完成代码如下

class BnActivity : AppCompatActivity() {


    private var mainFragment: MainFragment? = null
    private var secondFragment: SecondFragment? = null
    private var threeFragment: ThreeFragment? = null

    private var showFragment: Fragment? = null

    private var cacheFragments: HashMap<String, Fragment> = HashMap<String, Fragment>()

    private var currentSelect: Int? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_bn)
        val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation_view)
        //1.先设置监听防止设置默认选中item时不走该方法
        bottomNavigationView.setOnNavigationItemSelectedListener {
            Log.d(this.javaClass.name, "setOnNavigationItemSelectedListener")
            when (it.itemId) {
                R.id.main_menu -> {
                    showFragment = cacheFragments.get(MainFragment::class.qualifiedName!!)
                }
                R.id.second_menu -> {
                    showFragment = cacheFragments.get(SecondFragment::class.qualifiedName!!)
                }
                R.id.three_menu -> {
                    showFragment = cacheFragments.get(ThreeFragment::class.qualifiedName!!)
                }
            }
            //2.记录当前选中的条目id
            currentSelect = it.itemId
            //3.切换fragment的方法
            FragmentUtil.switchFragment(this, showFragment!!)
            return@setOnNavigationItemSelectedListener true
        }
        //4.判断savedInstanceState是否为空,不为空则取出在Activity被系统回收时,保存的fragment的实例,并保存在自己的HashMap中
        if (savedInstanceState != null) {

            Log.d(this.javaClass.simpleName, "savedInstanceState not null")
			//从fragmamager中获取Activity保存的fragment实例,如果为空,就重新创建
            val findMainFragmentByTag =
                supportFragmentManager.findFragmentByTag(MainFragment::class.qualifiedName)
            if (findMainFragmentByTag == null) {
                mainFragment = MainFragment.getInstance()
            } else {
                mainFragment = findMainFragmentByTag as MainFragment
            }
            cacheFragments.put(MainFragment::class.java.name, mainFragment!!)
			//第二个fragment
            val findSecondFragmentByTag =
                supportFragmentManager.findFragmentByTag(SecondFragment::class.qualifiedName)
            if (findSecondFragmentByTag == null) {
                secondFragment = SecondFragment.getInstance()
            } else {
                secondFragment = findSecondFragmentByTag as SecondFragment
            }
            cacheFragments.put(SecondFragment::class.java.name, secondFragment!!)
			//第三个fragment
            val findThreeFragmentByTag =
                supportFragmentManager.findFragmentByTag(ThreeFragment::class.qualifiedName)
            if (findThreeFragmentByTag == null) {
                threeFragment = ThreeFragment.getInstance()
            } else {
                threeFragment = findThreeFragmentByTag as ThreeFragment
            }
            cacheFragments.put(ThreeFragment::class.java.name, threeFragment!!)

			//获取所有的fragment
            val fragments = supportFragmentManager.fragments
   
            for (fragment in fragments) {
                Log.d(this.javaClass.simpleName, fragment::class.qualifiedName!!)
                //5.判断该fragment是否被添加,如果没有被添加判断是否隐藏,如果没有隐藏,则隐藏
                //目的是隐藏所有可见的fragment,重新管理显示隐藏状态
                if (fragment.isAdded) {
                    supportFragmentManager.beginTransaction().hide(fragment).commit()
                }
            }
            //6.获取activity被回收时,自己保存的选中item的id
            val currentSelectItemId = savedInstanceState.getInt("currentItemId")
            currentSelectItemId?.let {
                bottomNavigationView.selectedItemId = it
            }
        } else {
            //7.如果是第一次创建,创建出所有的fragment,并添加到自己的hashMap中
            Log.d(this.javaClass.simpleName, "savedInstanceState is null")
            mainFragment = MainFragment.getInstance()
            secondFragment = SecondFragment.getInstance()
            threeFragment = ThreeFragment.getInstance()
            Log.d(this.javaClass.simpleName, MainFragment::class.qualifiedName!!)
            Log.d(this.javaClass.simpleName, SecondFragment::class.qualifiedName!!)
            Log.d(this.javaClass.simpleName, ThreeFragment::class.qualifiedName!!)

            cacheFragments.put(MainFragment::class.qualifiedName!!, mainFragment!!);
            cacheFragments.put(SecondFragment::class.qualifiedName!!, secondFragment!!);
            cacheFragments.put(ThreeFragment::class.qualifiedName!!, threeFragment!!);
            //8.设置默认选中的item
            bottomNavigationView.selectedItemId = R.id.main_menu
        }

    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        //9.保存activity被回收时,当前选中的itemid
        currentSelect?.let { outState.putInt("currentItemId", it) }
    }
}

这里给出我写的Demo
如果对你有所帮助,请点个小星星,感谢

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值