以前寫多個fragment切換是經常使用這種方法切換fragment:
/**
* 使用replace切換頁面
* 顯示fragment
*/
private void showFragment(Fragment fg){
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, fg);
transaction.commit();
}
replace():該方法只是在上一個Fragment不再需要時採用的簡便方法,弊端就是如果需要重複使用該fragment時,需要每次都要重新加載一次。比如我在第一個fragment輸入信息後,切換第二個fragment後再切換回去,就會造成數據丟失
而且,如果每切換一次就實例化一次的話,FragmentManager管理下的棧也會爆滿,最終會導致手機卡頓,這很明顯不是正確的Fragment使用姿勢,這時,我們就需要使用show()、hide()、add()了,正確的切換方式是add(),切換時hide(),add()另一個Fragment;再次切換時,只需hide()當前,show()另一個就行了,代碼修改如下:
/**
* 使用show() hide()切換頁面
* 顯示fragment
*/
private void showFragment(Fragment fg){
FragmentTransaction transaction = fragmentManager.beginTransaction();
//如果之前沒有添加過
if(!fg.isAdded()){
transaction
.hide(currentFragment)
.add(R.id.content,fg);
}else{
transaction
.hide(currentFragment)
.show(fg);
}
//全局變量,記錄當前顯示的fragment
currentFragment = fg;
transaction.commit();
}
即使切換到別的fragment,再切換回來,數據還依然存在,這就避免了Fragment切換時佈局重新實例化。
安卓app有一種特殊情況,就是 app運行在後臺的時候,系統資源緊張的時候導致把app的資源全部回收(殺死app的進程),這時把app再從後臺返回到前臺時,app會重啓。這種情況下文簡稱爲:“內存重啓”。(屏幕旋轉等配置變化也會造成當前Activity重啓,本質與“內存重啓”類似)在系統要把app回收之前,系統會把Activity的狀態保存下來,Activity的FragmentManager負責把Activity中的Fragment保存起來。在“內存重啓”後,Activity的恢復是從棧頂逐步恢復,Fragment會在宿主Activity的onCreate方法調用後緊接着恢復
當我們不退出軟件,只是後臺掛着去幹別的事,當系統內存不足回收我們這個app時,再切換回來,app的這幾個Fragment界面會重疊。
三個fragment全部疊在了一起,而且點擊上面菜單也不能消除重疊。顯然,這並不是我們想要的,沒事,繼續解決問題,使用findFragmentByTag:即在add()或者replace()時綁定一個tag,一般我們是用fragment的類名作爲tag,然後在發生“內存重啓”時,通過findFragmentByTag找到對應的Fragment,並hide()需要隱藏的fragment。,修改如下:
/**
* 使用show() hide()切換頁面
* 顯示fragment
*/
private void showFragment(Fragment fg){
FragmentTransaction transaction = fragmentManager.beginTransaction();
//如果之前沒有添加過
if(!fg.isAdded()){
transaction
.hide(currentFragment)
.add(R.id.content,fg,fg.getClass().getName()); //第三個參數爲當前的fragment綁定一個tag,tag爲當前綁定fragment的類名
}else{
transaction
.hide(currentFragment)
.show(fg);
}
currentFragment = fg;
transaction.commit();
}
別急,還沒完,在當前Activity的onCreate()
方法裏面添加一下代碼:if (savedInstanceState != null) { // “內存重啓”時調用
//從fragmentManager裏面找到fragment
fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName());
fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName());
fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName());
//解決重疊問題show裏面可以指定恢復的頁面
fragmentManager.beginTransaction()
.show(fgOne)
.hide(fgTwo)
.hide(fgThree)
.commit();
//把當前顯示的fragment記錄下來
currentFragment = fgOne;
}else{ //正常啓動時調用
fgOne = new OneFragment();
fgTwo = new TwoFragment();
fgThree = new ThreeFragment();
showFragment(fgOne);
}
OK,當app後臺時遇到“內存重啓”的情況下,再返回我們的app,就會恢復到
show(fgOne)
頁面,而且還不會造成重疊問題! 很顯然,這樣結束是不道德的,因爲有人會問了,如果想記錄當前退出的狀態以至於下次恢復時直接顯示之前的fragment頁面怎麼辦,恩,對於這個問題,我們可以在activity的onSaveInstanceState()
方法中記錄一下“內存重啓”之前的Fragment的頁面,然後在oncreate()
中取出來,根據保存的頁面來顯示到指定的fragment,代碼如下:
@Override
protected void onSaveInstanceState(Bundle outState) {
//“內存重啓”時保存當前的fragment名字
outState.putString(STATE_FRAGMENT_SHOW,currentFragment.getClass().getName());
super.onSaveInstanceState(outState);
}
然後在oncreate()
方法中添加(修改上面的那個代碼)if (savedInstanceState != null) { // “內存重啓”時調用
//獲取“內存重啓”時保存的fragment名字
String saveName = savedInstanceState.getString(STATE_FRAGMENT_SHOW);
//從fragmentManager裏面找到fragment
fgOne = (OneFragment) fragmentManager.findFragmentByTag(OneFragment.class.getName());
fgTwo = (TwoFragment) fragmentManager.findFragmentByTag(TwoFragment.class.getName());
fgThree = (ThreeFragment) fragmentManager.findFragmentByTag(ThreeFragment.class.getName());
//如果爲空就默認操作
if(TextUtils.isEmpty(saveName)){
//解決重疊問題
fragmentManager.beginTransaction()
.show(fgOne)
.hide(fgTwo)
.hide(fgThree)
.commit();
//把當前顯示的fragment記錄下來
currentFragment = fgOne;
}else{
if(saveName.equals(fgOne.getClass().getName())){ //如果推出之前是OneFragment
//解決重疊問題
fragmentManager.beginTransaction()
.show(fgOne)
.hide(fgTwo)
.hide(fgThree)
.commit();
//把當前顯示的fragment記錄下來
currentFragment = fgOne;
}else if(saveName.equals(fgTwo.getClass().getName())){ //如果推出之前是TwoFragment
//解決重疊問題
fragmentManager.beginTransaction()
.show(fgTwo)
.hide(fgOne)
.hide(fgThree)
.commit();
//把當前顯示的fragment記錄下來
currentFragment = fgTwo;
}else{ //如果推出之前是ThreeFragment
//解決重疊問題
fragmentManager.beginTransaction()
.show(fgThree)
.hide(fgTwo)
.hide(fgOne)
.commit();
//把當前顯示的fragment記錄下來
currentFragment = fgThree;
}
}
}else{ //正常啓動時調用
fgOne = new OneFragment();
fgTwo = new TwoFragment();
fgThree = new ThreeFragment();
showFragment(fgOne);
}
OK,這樣就可以了,我們通過保存當前顯示的fragment的類名,當我們在第二個fragment頁面時後臺,等到“內存重啓”後返回該app時,就根據之前保存的類名來判斷加載指定的fragment,而且,重疊的問題也解決了!當然,有網友問了,如果fragment比較多,那麼多if多麻煩,是啊,上面的代碼主要就是讓大家理解一下思路,具體開發不建議那麼寫,下面,就說一下上面的代碼該如何精簡吧:
創建一個List用來存所有的fragment,給fragment設置tag可以使用當前的索引index,然後在onSaveInstanceState
中保存當前的索引,在恢復時通過索引來找到fragment
//當前顯示的fragment
private static final String CURRENT_FRAGMENT = "STATE_FRAGMENT_SHOW";
private TextView tvone;
private TextView tvtwo;
private TextView tvthree;
private FragmentManager fragmentManager;
private Fragment currentFragment = new Fragment();
private List<Fragment> fragments = new ArrayList<>();
private int currentIndex = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.tvthree = (TextView) findViewById(R.id.tv_three);
this.tvtwo = (TextView) findViewById(R.id.tv_two);
this.tvone = (TextView) findViewById(R.id.tv_one);
fragmentManager = getSupportFragmentManager();
tvthree.setOnClickListener(this);
tvtwo.setOnClickListener(this);
tvone.setOnClickListener(this);
if (savedInstanceState != null) { // “內存重啓”時調用
//獲取“內存重啓”時保存的索引下標
currentIndex = savedInstanceState.getInt(CURRENT_FRAGMENT,0);
//注意,添加順序要跟下面添加的順序一樣!!!!
fragments.removeAll(fragments);
fragments.add(fragmentManager.findFragmentByTag(0+""));
fragments.add(fragmentManager.findFragmentByTag(1+""));
fragments.add(fragmentManager.findFragmentByTag(2+""));
//恢復fragment頁面
restoreFragment();
}else{ //正常啓動時調用
fragments.add(new OneFragment());
fragments.add(new TwoFragment());
fragments.add(new ThreeFragment());
showFragment();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
//“內存重啓”時保存當前的fragment名字
outState.putInt(CURRENT_FRAGMENT,currentIndex);
super.onSaveInstanceState(outState);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_one:
currentIndex = 0;
break;
case R.id.tv_two:
currentIndex = 1;
break;
case R.id.tv_three:
currentIndex = 2;
break;
}
showFragment();
}
/**
* 使用show() hide()切換頁面
* 顯示fragment
*/
private void showFragment(){
FragmentTransaction transaction = fragmentManager.beginTransaction();
//如果之前沒有添加過
if(!fragments.get(currentIndex).isAdded()){
transaction
.hide(currentFragment)
.add(R.id.content,fragments.get(currentIndex),""+currentIndex); //第三個參數爲添加當前的fragment時綁定一個tag
}else{
transaction
.hide(currentFragment)
.show(fragments.get(currentIndex));
}
currentFragment = fragments.get(currentIndex);
transaction.commit();
}
/**
* 恢復fragment
*/
private void restoreFragment(){
FragmentTransaction mBeginTreansaction = fragmentManager.beginTransaction();
for (int i = 0; i < fragments.size(); i++) {
if(i == currentIndex){
mBeginTreansaction.show(fragments.get(i));
}else{
mBeginTreansaction.hide(fragments.get(i));
}
}
mBeginTreansaction.commit();
//把當前顯示的fragment記錄下來
currentFragment = fragments.get(currentIndex);
}