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
如果对你有所帮助,请点个小星星,感谢