Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常見錯誤

来源:http://www.cnblogs.com/mengdd/archive/2016/06/02/5552721.html
-Advertisement-
Play Games

嵌套Fragments (Nested Fragments), 是在Fragment內部又添加Fragment. 使用時, 主要要依靠宿主Fragment的 `getChildFragmentManager()` 來獲取FragmentManger. 雖然看起來和在activity中添加fragme... ...


嵌套Fragment的使用及常見錯誤

嵌套Fragments (Nested Fragments), 是在Fragment內部又添加Fragment.
使用時, 主要要依靠宿主Fragment的 getChildFragmentManager() 來獲取FragmentManger.
雖然看起來和在activity中添加fragment差不多, 但因為fragment生命周期及管理恢復模式不同, 其中有一些需要特別註意的地方.
本文內容還包括了從Fragment遷移到v4.Fragment代碼中需要改動的一些地方.

嵌套Fragments

嵌套Fragments Nested Fragments 是Android 4.2 API 17 引入的.
目的: 進一步增強動態復用.
如果要在Android 4.2之前使用, 可以用support library v4的版本, 後面會有詳細的遷移過程介紹.

嵌套Fragment的動態添加

在宿主fragment里調用getChildFragmentManager()
即可用它來向這個fragment內部添加fragments.

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

同樣, 對於內部的fragment來說, getParentFragment() 方法可以獲取到fragment的宿主fragment.

getChildFragmentManager() 和 getFragmentManager()

getChildFragmentManager()是fragment中的方法, 返回的是管理當前fragment內部子fragments的manager.
getFragmentManager()在activity和fragment中都有.
在activity中, 如果用的是v4 support庫, 方法應該用getSupportFragmentManager(), 返回的是管理activity中fragments的manager.
在fragment中, 還叫getFragmentManager(), 返回的是把自己加進來的那個manager.

也即, 如果fragment在activity中, fragment.getFragmentManager()得到的是activity中管理fragments的那個manager.
如果fragment是嵌套在另一個fragment中, fragment.getFragmentManager()得到的是它的parent的getChildFragmentManager().

總結就是: getFragmentManager()是本級別管理者, getChildFragmentManager()是下一級別管理者.
這實際上是一個樹形管理結構.

使用Support library

為什麼要使用support library? 有兩種原因:

  1. 要在API level11之前使用fragment.
  2. 要在API Level 17之前使用getChildFragmentManager(), 即使用嵌套Fragment.

遷移到support library需要改動哪些地方?

把Fragment遷移到v4版本, 需要改動如下地方:

import android.app.Fragment; -> import android.support.v4.app.Fragment;
Activity -> FragmentActivity / AppCompatActivity
activity.getFragmentManager() -> getSupportFragmentManager()

Loader, LoaderManager, LoaderCursor也需要改成v4包的.
activity.getLoaderManager() -> getSupportLoaderManager()

Fragment中onTrimMemory()方法不見了
以前是這個方法

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        imageLoader.trimMemory(level);
    }

v4版本需要改成這個

   @Override
    public void onLowMemory() {
        super.onLowMemory();
        imageLoader.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
    }

嵌套Fragment使用常見錯誤

錯誤情形1: 把嵌套Fragment放在佈局里

把嵌套Fragment放在佈局里 -> InflateException in Binary XML

看起來嵌套fragment的使用除了要用getChildFragmentManager()以外, 其他跟之前似乎沒什麼區別.
如果嵌套的fragment不需要太多控制, 固定地占據了一塊地方, 你可能想當然地為了省事就把它放進了xml佈局文件里, 寫個標簽.
運行一下初看起來似乎沒什麼錯, run一下也能顯示出來, 但是千萬不要這樣做, 多玩兩下更複雜的你就知道了.

上面官網介紹時就有這麼一句:

Note: You cannot inflate a layout into a fragment when that layout includes a <fragment>.
Nested fragments are only supported when added to a fragment dynamically.

人家這麼說肯定是有原因的哇, 下麵我來告訴你我知道的問題:
如果Fragment被嵌套寫在了佈局里, inflate到這個標簽的時候就相當於將它加進了FragmentManager里.
如果嵌套的parent fragment因為需要重建View而重新走了onCreateView()方法, 再次inflate, 此時就會拋出異常: InflateException in Binary XML

之前為什麼可以呢? 非嵌套的情況, fragment直接加在activity里, 如果需要重新inflate, 必定是在onCreate()里, activity是重新建的, 所以沒有問題, 因為不存在fragmentManager中已經持有同一個fragment的問題.

舉一個例子:
在嵌套的情況下, 如果FragmentE佈局里有FragmentA, 這時候我們需要疊加一個FragmentD.
用了replace(), 並且addToBackStack().
當D顯示的時候, E實際上View是被銷毀的, 然後back回來, 重建View, 即FragementE需要重新從onCreateView
()開始走生命周期, 走到inflate的時候又看到了fragmentA的標簽.
但是這時候A實際上還在FragmentManager裡面, 所以就會拋出如下的異常:
android.view.InflateException: Binary XML file line # XX: Binary XML file line #XX: Error inflating class fragment
崩潰的位置就在parent fragment(FragmentE) inflate的時候.
列印具體的異常棧信息可以看到:

at com.example.ddmeng.helloactivityandfragment.fragment.FragmentE.onCreateView(FragmentE.java:35)
at android.app.Fragment.performCreateView(Fragment.java:2220)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:973)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1587)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:578)
at android.support.v4.app.BaseFragmentActivityEclair.onBackPressedNotHandled(BaseFragmentActivityEclair.java:27)
at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:189)
 Caused by: java.lang.IllegalArgumentException: Binary XML file line #16: Duplicate id 0x7f0c0059, tag null, or parent id 0xffffffff with another fragment for com.example.ddmeng.helloactivityandfragment.fragment.FragmentA
at android.app.FragmentManagerImpl.onCreateView(FragmentManager.java:2205)

實驗例子代碼

Solution 1: 動態添加child fragment

解決上面的問題有各種方法, 最常規的做法是, 使用動態添加:

Fragment fragmentA = getChildFragmentManager().findFragmentByTag(NESTED_FRAGMENT_TAG);
if (fragmentA == null) {
    Log.i(LOG_TAG, "add new FragmentA !!");
    fragmentA = new FragmentA();
    FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
    fragmentTransaction.add(R.id.fragment_container, fragmentA, NESTED_FRAGMENT_TAG).commit();
} else {
    Log.i(LOG_TAG, "found existing FragmentA, no need to add it again !!");
}

Solution 2: 在異常之前remove child fragment

如果你的子fragment非要加在佈局里不可, 而你的程式確實會有重建父fragment view的情形.
為了避免上面的異常, 你也可以這樣做(tricky and not recommended):

public void removeChildFragment(Fragment parentFragment) {
    FragmentManager fragmentManager = parentFragment.getChildFragmentManager();
    Fragment child = fragmentManager.findFragmentById(R.id.child);
    if (child != null) {
        fragmentManager.beginTransaction()
        .remove(child)
        .commitAllowingStateLoss();
    }
}

在parentFragment的onCreateView()方法中inflate之前和onSaveInstanceState()方法中做save工作之前調用它.
這兩個地方是發生異常的地方, 只要在其之前remove就好.

錯誤情形2: 把fragment放在一個動態佈局里

把fragment放在一個動態佈局里 -> java.lang.IllegalArgumentException: No view found for id

發現這個錯誤是因為項目中的一個子Fragment是添加在RecyclerView裡面的一塊的.
RecyclerView要等到Loader的數據取到了之後再populate每一塊的佈局.
還是上面的流程, 啟動父fragment, load數據, 添加子fragment, 這都沒有問題.
但是一旦如果是上面的replace()addToBackStack() , 並且再次返回, 就會出現異常.

因為當重建View的時候, fragmentManager其中是持有child fragment的, 但是找不到它的container, 於是就會拋出異常.
我也同樣做了一個小實驗, 在我的demo程式里:
HelloActivityAndFragment
Nested Fragment in Dynamic Container:
在Fragment F中, 先添加一個FrameLayout, 再把child fragment A加進去.
然後在Activity中, 用D replace F, 按back鍵返回, 就會有crash:

     java.lang.IllegalArgumentException: No view found for id 0x7f0c0062 (com.example.ddmeng.helloactivityandfragment:id/frame_container) for fragment FragmentA{b37763 #0 id=0x7f0c0062 FragmentA}
         at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:965)
         at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
         at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1130)
         at android.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1953)
         at android.app.Fragment.performActivityCreated(Fragment.java:2234)
         at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:992)
         at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1148)
         at android.app.BackStackRecord.popFromBackStack(BackStackRecord.java:1670)
         at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1587)
         at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:578)
         at android.app.Activity.onBackPressed(Activity.java:2503)

這是因為返回的時候FragmentManager找不到對應的container了.
所以應該避免這種做法, 儘量把fragment加進parent的根佈局里, 而不是某個動態添加的佈局.

其他

關於嵌套fragments的情況, 可能和ViewPager結合使用的情形比較多.
這個感覺說來話長了, 以為有很多系統幫忙做的事情, 改天有空再說吧.

這裡有個大哥寫了個工具類Fragmentation.
他也有幾篇博文分析遇到的坑和原因(見上面repo的README給出的鏈接), 裡面有一些back stack的問題, 還有動畫什麼的, 大家有興趣可以看看.

參考資料

Guide: Nested Fragments

相關Demo


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本次博客是一篇總結性質的博客,總結的是各種創建控制器的方式以及一些需要註意的操作。 1、通過storyboard創建控制器 正如我上一篇博客中所說,當 Main Interface 沒有選定的時候,我們一般只能通過代碼來創建一個 UIWindow,不再使用系統創建好的 Main.storyboard ...
  • Linux文件的訪問許可權* 在Android中,每一個應用是一個獨立的用戶* drwxrwxrwx* 第1位:d表示文件夾,-表示文件* 第2-4位:rwx,表示這個文件的擁有者(創建這個文件的應用)用戶對該文件的許可權 * r:讀 * w:寫 * x:執行* 第5-7位:rwx,表示跟文件擁有者用戶 ...
  • github地址 先安裝homeBrew command script import /usr/local/opt/chisel/libexec/fblldb.py WTF?沒有許可權?嘗試提升許可權安裝 sudo brew install chisel 直接報錯,拒絕執行。 嘗試 command sc ...
  • 最近仿照QQ聊天做了一個類似界面,先看下界面組成(畫面不太美湊合湊合唄,,,,): 其中聊天背景可以是一個LinearLayout或者RelativeLayout裡面存放的是ListView(將ListView的分割線設置成透明:android:divider="#0000"否則聊天界面會顯示出分割 ...
  • #單元測試junit* 定義一個類繼承AndroidTestCase,在類中定義方法,即可測試該方法 * 在指定指令集時,targetPackage指定你要測試的應用的包名 <instrumentation android:name="android.test.InstrumentationTest ...
  • SmartFoxServer 是專門為Adobe Flash設計的跨平臺socket伺服器,讓開發者高效地開發多人應用及游戲. 該伺服器主要用來創建多玩家游戲。並提供強大的製作工具,各種回合制游戲和實時游戲都可以勝任。 SmartFoxServer 支持的播放器從Flash player6版到最新的 ...
  • 一、引子 學完了可視化編程的Xib和Storyboard,LZ對它們的感受就是的就是UI控制項創建直接拖拽,尺寸適配加約束,Storyboard的頁面跳轉邏輯清晰可見,比起代碼佈局節省了很多的工作量。但是LZ相信還是很多人喜歡用純代碼來編寫一個程式的(LZ就是一個,用代碼寫出來東西的成就感很足!),所 ...
  • OC 協議 概念:定義了一個介面,其他類負責來實現這些介面。如果你的類實現了一個協議的方法時,則說該類遵循此協議。 非正式協議:非正式協議雖名為協議,但實際上是掛於NSObject上的未實現分類(Unimplemented Category)的一種稱謂。 協議的格式: 協議中定義的方法還有兩個修飾符 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...