最近專案進入了無休止的修bug階段,很多問題也著實讓我頭疼了一陣子,其中就包括對單Activity頁面中多Fragment的管理。可能是我對Fragment瞭解太少了,遇到了很多問題,所以這篇文章著重於講述我遇到了怎樣的問題,以及我的解決方法。希望對有遇到相同問題的人提供一點幫助。
5月23日修改,在我寫完這篇文章的5天后,修改了主頁的佈局,將大部分內容都放置到了ViewStub中進行一個延時載入的操作。結果發現下面的onSaveInstanceState中儲存Fragment的方法失效了,每次銷燬後回來Fragment的資料還在,但頁面變成空了。經過我一天的不斷嘗試,最後終於發現··在ViewStub中,不會出現Fragment重疊的問題- -,屬實被自己給坑了。。
-
Fragment點選穿透
我目前專案的首頁是一個MainActivity包含5個Fragment,通過hide&show來進行tab切換。在剛開始就遇到了一個很噁心的問題:當前Fragment頁,點選能跳轉到其他Fragment頁的內容。具體來說就是不應該被點選的位置,出現了其它Fragment頁面對應位置的點選事件。這個問題不是100%的復現的,而且有些機型不會出現,有些又很頻繁。最後終於看到了這個帖子解決了問題——關於Fragment疊加點選穿透的解決方案](blog.csdn.net/xieluoxixi/…)。以下內容均借鑑於此貼:
這個問題實際上是點選事件分發的問題,當多個Fragment新增進Fragment棧時,棧底的Fragment的點選事件在上層Fragment出現後仍然有效。具體的解決方法有三種,可以點進帖子中檢視。
在我的專案中由於使用Fragment比較多,所以我使用了第二種方案,在BaseFragment中全域性新增了
view.setClickable(true);
問題再也沒復現過了。@Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View rootView = inflater.inflate(this.getLayoutId(), container, false); rootView.setClickable(true); //把View的click屬性設為true,截斷點選時間段擴散 return super.onCreateView(inflater, container, savedInstanceState); } 複製程式碼
-
Fragment重疊(重影)
這個問題剛好跟上一個相反,上一個問題是介面看不出重疊,但點選事件重疊了。而這個是隻有介面重疊,點選卻沒有問題(也可能是因為我已經把上一個問題解決了,不解決的話可能就都有問題了- - )
出現的情況是當APP被異常銷燬重啟時,可能導致的又記憶體不足,或者旋轉螢幕方向之後沒有做處理等。可以在開發者模式中勾選【不保留活動】,讓每次退回到桌面再切換回APP時都重新載入一遍,模擬記憶體不足的效果,可以更方便地檢視這個問題是否存在。
這個問題的原因也比較好找,解決方法也不難,網上可以搜到很多帖子。其實前面講到旋轉螢幕後就會復現,那自然就能聯想到
onSaveInstanceState()
,這個問題出現的原因就在於,異常銷燬時,系統會預設使用銷燬前該Activity儲存的狀態來進行恢復,也就是將之前的Fragment重新恢復了,但APP銷燬後重新啟動,Fragment又被Add了一遍,所以造成了Fragment重疊。不過網上的帖子我看了一些,發現都沒有提到一個點,這個問題在Activity的xml根佈局中新增了
android:fitsSystemWindows="true"
方法後,就不會出現了,至少對於我是這樣。因為我的專案之前一直沒有出現這個問題,在我某天將首頁的佈局改為沉浸式,去掉了這個方法後,就出現Fragment重疊的現象了。在後面優化了這個問題後,我在想為什麼之前沒有出現過,是因為這行程式碼嗎?於是我找了一個老一點的版本安裝到手機,開啟開發者選項-不儲存活動,發現這個問題真的沒有出現。我不確定這是個別手機的問題,還是設了android:fitsSystemWindows="true"
之後就真的不會出現重疊。希望有了解的朋友們告知一下。回到如何解決這個問題。最簡單粗暴當然是直接禁止Activity銷燬時儲存狀態,將
onSaveInstanceState(Bundle outState)
方法中的內容註釋掉,重啟時自然就不會恢復然後重疊了。但是這樣太粗暴了,也沒有任何使用者體驗可言。那在專案中當然不能這麼一刀切,下面貼程式碼講一下我是如何處理的:-
在首頁MainActivity中的
onSaveInstanceState(Bundle outState)
方法裡,判斷當前所有Fragment,將已經載入的Fragment進行儲存@Override protected void onSaveInstanceState(Bundle outState) { /*fragment不為空時 儲存*/ for (int i = 0; i < TAB_SIZE; i++) { //確保fragment是否已經加入到fragment manager中 if (mFragmentList[i].isAdded() && mFragmentList[i] != null) { //儲存已載入的Fragment getSupportFragmentManager().putFragment(outState, mFragmentTags[i], mFragmentList[i]); } } //傳入當前選中的tab值,在銷燬重啟後再定向到該tab outState.putInt(CURRENT_INDEX, mCurrentIndex); super.onSaveInstanceState(outState); } 複製程式碼
這裡需要注意的是,通過
getSupportFragmentManager().putFragment();
方法按Tag儲存Fragment時,需要先確認該Fragment已經add到FragmentManager中了,否則會出現 IllegalStateException: Fragment is not currently in the FragmentManager 錯誤。 -
在
onCreate(Bundle savedInstanceState)
中恢復儲存的Fragment:@Override public void onCreate(Bundle savedInstanceState) { if (savedInstanceState != null) { /*獲取儲存的fragment 沒有的話返回null*/ for (int i = 0; i < TAB_SIZE; i++) { Fragment fragment = getSupportFragmentManager().getFragment(savedInstanceState, mFragmentTags[i]); if (fragment != null) { mFragmentList[i] = fragment; } } mCurrentIndex = savedInstanceState.getInt(CURRENT_INDEX, INDEX_HOME); } initFragment(); initTab(); } 複製程式碼
在進入
onCreate
函式時,先判斷savedInstanceState
是否為null,逐步判斷對應Tag的Fragment存不存在,存在則傳入到儲存Fragment的list中。 -
初始化Fragment
這一步本來是第一步,不過加了前面的操作之後,本來為空的FragmentList現在就不一定為空了,所以在初始化各個Fragment時,記得先判斷是否已經存在了,如果不存在才創新一個新的物件,否則就是已經新增了之前儲存的Fragment:
private void initFragment() { if (mFragmentList[0] == null) { mFragmentList[0] = new xxFragment//需要建立的Fragment; } if (mFragmentList[1] == null) { mFragmentList[1] = new xxFragment } if (mFragmentList[2] == null) { mFragmentList[2] = new xxFragment } if (mFragmentList[3] == null) { mFragmentList[3] = new xxFragment } if (mFragmentList[4] == null) { mFragmentList[4] = new xxFragment } } 複製程式碼
OK,到這裡Fragment該恢復的恢復,該建立的建立,接下來按正常流程執行就好了。重疊的問題就不會再出現啦。
-