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

最近項目進入了無休止的修bug階段,不少問題也着實讓我頭疼了一陣子,其中就包括對單Activity頁面中多Fragment的管理。多是我對Fragment瞭解太少了,遇到了不少問題,因此這篇文章着重於講述我遇到了怎樣的問題,以及個人解決方法。但願對有遇到相同問題的人提供一點幫助。java

5月23日修改,在我寫完這篇文章的5天后,修改了主頁的佈局,將大部份內容都放置到了ViewStub中進行一個延時加載的操做。結果發現下面的onSaveInstanceState中保存Fragment的方法失效了,每次銷燬後回來Fragment的數據還在,但頁面變成空了。通過我一天的不斷嘗試,最後終於發現··在ViewStub中,不會出現Fragment重疊的問題- -,屬實被本身給坑了。。android

  • Fragment點擊穿透

    我目前項目的首頁是一個MainActivity包含5個Fragment,經過hide&show來進行tab切換。在剛開始就遇到了一個很噁心的問題:當前Fragment頁,點擊能跳轉到其餘Fragment頁的內容。具體來講就是不該該被點擊的位置,出現了其它Fragment頁面對應位置的點擊事件。這個問題不是100%的復現的,並且有些機型不會出現,有些又很頻繁。最後終於看到了這個帖子解決了問題——關於Fragment疊加點擊穿透的解決方案](blog.csdn.net/xieluoxixi/…)。如下內容均借鑑於此貼:ide

    這個問題其實是點擊事件分發的問題,當多個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時都從新加載一遍,模擬內存不足的效果,能夠更方便地查看這個問題是否存在。this

    這個問題的緣由也比較好找,解決方法也不難,網上能夠搜到不少帖子。其實前面講到旋轉屏幕後就會復現,那天然就能聯想到onSaveInstanceState(),這個問題出現的緣由就在於,異常銷燬時,系統會默認使用銷燬前該Activity保存的狀態來進行恢復,也就是將以前的Fragment從新恢復了,但APP銷燬後從新啓動,Fragment又被Add了一遍,因此形成了Fragment重疊。spa

    不過網上的帖子我看了一些,發現都沒有提到一個點,這個問題在Activity的xml根佈局中添加了android:fitsSystemWindows="true"方法後,就不會出現了,至少對於我是這樣。由於個人項目以前一直沒有出現這個問題,在我某天將首頁的佈局改成沉浸式,去掉了這個方法後,就出現Fragment重疊的現象了。在後面優化了這個問題後,我在想爲何以前沒有出現過,是由於這行代碼嗎?因而我找了一個老一點的版本安裝到手機,打開開發者選項-不保存活動,發現這個問題真的沒有出現。我不肯定這是個別手機的問題,仍是設了android:fitsSystemWindows="true"以後就真的不會出現重疊。但願有了解的朋友們告知一下。.net

    回到如何解決這個問題。最簡單粗暴固然是直接禁止Activity銷燬時保存狀態,將onSaveInstanceState(Bundle outState)方法中的內容註釋掉,重啓時天然就不會恢復而後重疊了。可是這樣太粗暴了,也沒有任何用戶體驗可言。那在項目中固然不能這麼一刀切,下面貼代碼講一下我是如何處理的:code

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

相關文章
相關標籤/搜索