Android通過hide&show管理多Fragment出現重疊以及點選穿透的解決之道

蘆葦科技App技術團隊發表於2019-05-17

最近專案進入了無休止的修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)方法中的內容註釋掉,重啟時自然就不會恢復然後重疊了。但是這樣太粗暴了,也沒有任何使用者體驗可言。那在專案中當然不能這麼一刀切,下面貼程式碼講一下我是如何處理的:

    1. 在首頁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 錯誤。

    2. 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中。

    3. 初始化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該恢復的恢復,該建立的建立,接下來按正常流程執行就好了。重疊的問題就不會再出現啦。

相關文章