這個(gè)文章比較“膚淺”昭齐,但是其實(shí)網(wǎng)上對(duì)于Fragment切換這么膚淺的事情也甚少有文章說(shuō)的清楚,所以稍微介紹下矾柜。
BottomNavigationView
網(wǎng)上有好多關(guān)于BottomNavigationView的教程阱驾,講的挺詳細(xì)的就谜,本文這里沒(méi)有細(xì)講這個(gè)的意向,但是下面用到BottomNavigationView的監(jiān)聽(tīng)事件:
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里覆,要不切換的時(shí)候會(huì)沒(méi)有動(dòng)畫效果的丧荐。
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的容器喧枷,上面有提及過(guò)虹统,在這里使用,為顯示的fragment指定容器隧甚。
這種方式比較簡(jiǎn)單车荔,直接初始化fragment的list和寫好對(duì)應(yīng)的tag后,切換一次直接replace就好了戚扳。
fragment的切換方式二忧便,hide,show帽借,重點(diǎn)說(shuō)這個(gè)珠增。
因?yàn)閔ide,show的使用方式不當(dāng)?shù)脑捒嘲瑫?huì)導(dǎo)致很多bug蒂教。
比如說(shuō)重疊問(wèn)題,重疊問(wèn)題這個(gè)在android 23版本上被修復(fù)了辐董。
但是在使用23版本上有時(shí)候還是會(huì)遇到回收內(nèi)存后界面重疊的情況悴品,那就是你的打開(kāi)方式不對(duì)了。
看下面:
在onCreate里面調(diào)用的設(shè)置默認(rèn)界面简烘,比如說(shuō)三個(gè)fragment苔严,我讓第二個(gè)為默認(rèn),就設(shè)置1孤澎,這很簡(jiǎn)單届氢,沒(méi)有問(wèn)題。
//設(shè)置默認(rèn)
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進(jìn)去過(guò)了來(lái)判斷是add還是show,這個(gè)也很簡(jiǎn)單型将。
prePos 是記錄了當(dāng)前顯示的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;
}
好了,代碼都這么簡(jiǎn)單而且沒(méi)有問(wèn)題七兜,然后發(fā)生重疊了丸凭。這不是打臉嗎,而且翻過(guò)好多文章都說(shuō)23以上修復(fù)了bug...Android不是在耍我們吧。
思考下重疊原因惜犀,肯定是內(nèi)存回收機(jī)制的原因铛碑。
我們可以在android studio上通過(guò)一系列的騷操作來(lái)復(fù)現(xiàn)內(nèi)存回收的情況:
打開(kāi)Android Device Monitor
你可以看到你的應(yīng)用在你的手機(jī)(真機(jī)也是可以的)上運(yùn)行的線程,以包名顯示虽界,比如說(shuō)是com.test.fragment 汽烦。
你要模擬內(nèi)存回收,運(yùn)行應(yīng)用后按home鍵回到桌面莉御,然后在Android Device Monitor把com.test.fragment給stop了撇吞。
然后再按進(jìn)應(yīng)用,內(nèi)存回收又重啟進(jìn)入應(yīng)用的一波騷操作你就完成了颈将。在開(kāi)發(fā)還是挺有用的梢夯。
好了言疗,繼續(xù)分析晴圾,從生命周期說(shuō)起。
應(yīng)用內(nèi)存回收后會(huì)執(zhí)行onSaveInstanceState這個(gè)方法噪奄,而且全局變量的會(huì)被清空掉死姚,都被回收了,全局變量算什么勤篮,application都照樣null了都毒。
所以我們還是要在onSaveInstanceState保存下我們珍貴的prePos,位置信息碰缔。
因?yàn)锽ottomNavigationView比較靈活账劲,比如說(shuō)你滑到第二個(gè)界面,內(nèi)存被回收了重啟進(jìn)去金抡,切換狀態(tài)還是在第二個(gè)的狀態(tài)瀑焦,只是我們這里上面的fragment顯示重疊了。
這樣保存
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//保存上一個(gè)位置
outState.putInt(PRE,prePos);
}
然后在onCreate 中當(dāng)savedInstanceState梗肝!=null時(shí)重新賦值榛瓮。
這樣應(yīng)該沒(méi)問(wèn)題了吧,位置信息對(duì)了巫击,hide禀晓,show應(yīng)該就不會(huì)有毛病了吧。
然而并不是坝锰。
還是重疊粹懒,仔細(xì)觀察下。
該show的Fragment是顯示了顷级,但是該消失的沒(méi)有消失凫乖。。。
看下切換代碼拣凹,消失的是如何實(shí)現(xiàn)的
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)
恍然大悟森爽,內(nèi)存回收后,此from和彼from看上去一樣嚣镜,實(shí)際上爬迟,內(nèi)存上已經(jīng)不一樣了。
你hide錯(cuò)fragment了菊匿,hide了個(gè)新的fragment付呕,舊的還是show出來(lái)了。
解決
所以應(yīng)該這么做跌捆。
在onCreate 中
if(savedInstanceState==null){
//默認(rèn)為0
prePos = 0;
fragments = new ArrayList<>(3);
buildFragmentList();
}else{
//內(nèi)存被回收了徽职,fragments的list也被回收了,重新add進(jìn)去
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一個(gè)
fragments.add(homeFragment!=null?homeFragment:new HomeFragment());
fragments.add(foundragment!=null?foundragment:new BdFoundFragment ());
fragments.add(accountFragment!=null?accountFragment:new BdAccountFragment () );
}
通過(guò)findFragmentByTag來(lái)保證內(nèi)存回收前后的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
//切換的點(diǎn)擊事件
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){
//默認(rèn)為0
prePos = 0;
fragments = new ArrayList<>(3);
buildFragmentList();
}else{
//內(nèi)存被回收了抄瓦,fragments的list也被回收了潮瓶,重新add進(jìn)去
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一個(gè)
fragments.add(homeFragment!=null?homeFragment:new HomeFragment());
fragments.add(foundragment!=null?foundragment:new BdFoundFragment ());
fragments.add(accountFragment!=null?accountFragment:new BdAccountFragment () );
}
//設(shè)置默認(rèn)
setDefaultFragment(prePos);
}
//設(shè)置默認(rèn)
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);
//保存上一個(gè)位置
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;
}
}