这个文章比较“肤浅”,但是其实网上对于Fragment切换这么肤浅的事情也甚少有文章说的清楚,所以稍微介绍下。
BottomNavigationView
网上有好多关于BottomNavigationView的教程,讲的挺详细的,本文这里没有细讲这个的意向,但是下面用到BottomNavigationView的监听事件:
bottomNavi.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.home:
switchFragment(0);
return true;
case R.id.found:
switchFragment(1);
return true;
case R.id.account:
switchFragment(2);
return true;
}
return false;
}
});
注意在case里面要return true,要不切换的时候会没有动画效果的。
switchFragment 是我切换显示fragment的方法。
初始化fragment的列表
private static final String TAG_HOME = "home";
private static final String TAG_FOUND = "found";
private static final String TAG_ACCOUNT = "account"
private static final String[] TAGS = {"home","found","account"};
private void buildFragmentList() {
BdHomeFragment homeFragment = new BdHomeFragment();
BdFoundFragment foundragment = new BdFoundFragment();
BdAccountFragment accountFragment = new BdAccountFragment();
fragments.add(homeFragment);
fragments.add(foundragment);
fragments.add(accountFragment);
}
fragment的切换方式一:replace
private void switchFragment(int pos, String tag) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentholder, fragments.get(pos), tag)
.commit();
}
R.id.fragmentholder是fragment的容器,上面有提及过,在这里使用,为显示的fragment指定容器。
这种方式比较简单,直接初始化fragment的list和写好对应的tag后,切换一次直接replace就好了。
fragment的切换方式二,hide,show,重点说这个。
因为hide,show的使用方式不当的话,会导致很多bug。
比如说重叠问题,重叠问题这个在android 23版本上被修复了。
但是在使用23版本上有时候还是会遇到回收内存后界面重叠的情况,那就是你的打开方式不对了。
看下面:
在onCreate里面调用的设置默认界面,比如说三个fragment,我让第二个为默认,就设置1,这很简单,没有问题。
//设置默认
prePos = 0
setDefaultFragment(prePos );
private void setDefaultFragment(int pos){
Fragment now = fragments.get(pos);
if(!now.isAdded()){
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragmentholder,fragments.get(prePos),TAGS[pos])
.commit();
}else{
getSupportFragmentManager()
.beginTransaction()
.show(now)
.commit();
}
}
buildFragmentList 跟上面切换的一样。
switchFragment: 在判断to是否add进去过了来判断是add还是show,这个也很简单。
prePos 是记录了当前显示的fragment在list中的位置。
为了
private void switchFragment(int pos) {
//Toast.makeText(this,prePos+" -> "+pos,Toast.LENGTH_LONG).show();
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
Fragment from = fragments.get(prePos);
Fragment to = fragments.get(pos);
if(!to.isAdded()){
transaction.hide(from)
.add(R.id.fragmentholder,fragments.get(pos),TAGS[pos])
.commit();
}else{
transaction.hide(from)
.show(to)
.commit();
}
prePos = pos;
}
好了,代码都这么简单而且没有问题,然后发生重叠了。这不是打脸吗,而且翻过好多文章都说23以上修复了bug...Android不是在耍我们吧。
思考下重叠原因,肯定是内存回收机制的原因。
我们可以在android studio上通过一系列的骚操作来复现内存回收的情况:
打开Android Device Monitor
你可以看到你的应用在你的手机(真机也是可以的)上运行的线程,以包名显示,比如说是com.test.fragment 。
你要模拟内存回收,运行应用后按home键回到桌面,然后在Android Device Monitor把com.test.fragment给stop了。
然后再按进应用,内存回收又重启进入应用的一波骚操作你就完成了。在开发还是挺有用的。
好了,继续分析,从生命周期说起。
应用内存回收后会执行onSaveInstanceState这个方法,而且全局变量的会被清空掉,都被回收了,全局变量算什么,application都照样null了。
所以我们还是要在onSaveInstanceState保存下我们珍贵的prePos,位置信息。
因为BottomNavigationView比较灵活,比如说你滑到第二个界面,内存被回收了重启进去,切换状态还是在第二个的状态,只是我们这里上面的fragment显示重叠了。
这样保存
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//保存上一个位置
outState.putInt(PRE,prePos);
}
然后在onCreate 中当savedInstanceState!=null时重新赋值。
这样应该没问题了吧,位置信息对了,hide,show应该就不会有毛病了吧。
然而并不是。
还是重叠,仔细观察下。
该show的Fragment是显示了,但是该消失的没有消失。。。
看下切换代码,消失的是如何实现的
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
Fragment from = fragments.get(prePos);
Fragment to = fragments.get(pos);
if(!to.isAdded()){
transaction.hide(from)
.add(R.id.fragmentholder,fragments.get(pos),TAGS[pos])
.commit();
}else{
transaction.hide(from)
.show(to)
.commit();
}
prePos = pos;
Fragment from = fragments.get(prePos);
transaction.hide(from)
恍然大悟,内存回收后,此from和彼from看上去一样,实际上,内存上已经不一样了。
你hide错fragment了,hide了个新的fragment,旧的还是show出来了。
解决
所以应该这么做。
在onCreate 中
if(savedInstanceState==null){
//默认为0
prePos = 0;
fragments = new ArrayList<>(3);
buildFragmentList();
}else{
//内存被回收了,fragments的list也被回收了,重新add进去
prePos = savedInstanceState.getInt(PRE);
fragments = new ArrayList<>(3);
BdHomeFragment homeFragment = (BdHomeFragment) getSupportFragmentManager().findFragmentByTag(TAGS[0]);
BdFoundFragment foundragment = (BdFoundFragment) getSupportFragmentManager().findFragmentByTag(TAGS[1]);
BdAccountFragment accountFragment = (BdAccountFragment) getSupportFragmentManager().findFragmentByTag(TAGS[2]);
//加上判断fragment是否为空,为空要new一个
fragments.add(homeFragment!=null?homeFragment:new HomeFragment());
fragments.add(foundragment!=null?foundragment:new BdFoundFragment ());
fragments.add(accountFragment!=null?accountFragment:new BdAccountFragment () );
}
通过findFragmentByTag来保证内存回收前后的fragment是一样的就ok了。
上面懒得看,直接看hide show代码的
public class BdMainActivity extends BaseActivity {
@BindView(R.id.bottom_navi)
BottomNavigationView bottomNavi;
private ArrayList<Fragment> fragments ;
private static final String TAG_HOME = "home";
private static final String TAG_FOUND = "found";
private static final String TAG_ACCOUNT = "account";
private static final String[] TAGS = {"home","found","account"};
private int prePos;
private String PRE = "PREPOS";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bd_main);
ButterKnife.bind(this); //初始化所有fragment
//切换的点击事件
bottomNavi.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.home:
switchFragment(0);
return true;
case R.id.found:
switchFragment(1);
return true;
case R.id.account:
switchFragment(2);
return true;
}
return false;
}
});
if(savedInstanceState==null){
//默认为0
prePos = 0;
fragments = new ArrayList<>(3);
buildFragmentList();
}else{
//内存被回收了,fragments的list也被回收了,重新add进去
prePos = savedInstanceState.getInt(PRE);
fragments = new ArrayList<>(3);
BdHomeFragment homeFragment = (BdHomeFragment) getSupportFragmentManager().findFragmentByTag(TAGS[0]);
BdFoundFragment foundragment = (BdFoundFragment) getSupportFragmentManager().findFragmentByTag(TAGS[1]);
BdAccountFragment accountFragment = (BdAccountFragment) getSupportFragmentManager().findFragmentByTag(TAGS[2]);
//加上判断fragment是否为空,为空要new一个
fragments.add(homeFragment!=null?homeFragment:new HomeFragment());
fragments.add(foundragment!=null?foundragment:new BdFoundFragment ());
fragments.add(accountFragment!=null?accountFragment:new BdAccountFragment () );
}
//设置默认
setDefaultFragment(prePos);
}
//设置默认
private void setDefaultFragment(int pos){
Fragment now = fragments.get(pos);
if(!now.isAdded()){
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragmentholder,fragments.get(prePos),TAGS[pos])
.commit();
}else{
getSupportFragmentManager()
.beginTransaction()
.show(now)
.commit();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//保存上一个位置
outState.putInt(PRE,prePos);
}
private void buildFragmentList() {
BdHomeFragment homeFragment = new BdHomeFragment();
BdFoundFragment foundragment = new BdFoundFragment();
BdAccountFragment accountFragment = new BdAccountFragment();
fragments.add(homeFragment);
fragments.add(foundragment);
fragments.add(accountFragment);
}
private void switchFragment(int pos) {
//Toast.makeText(this,prePos+" -> "+pos,Toast.LENGTH_LONG).show();
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
Fragment from = fragments.get(prePos);
Fragment to = fragments.get(pos);
if(!to.isAdded()){
transaction.hide(from)
.add(R.id.fragmentholder,fragments.get(pos),TAGS[pos])
.commit();
}else{
transaction.hide(from)
.show(to)
.commit();
}
prePos = pos;
}
}