請參考教材,全面理解和完成本章節內容... ...java
複製工程ch9,將工程目錄更名爲ch10。安全
本章,咱們將實現CriminalIntent「陋習手記」應用的列表與明細部分的關聯。用戶點擊某個「陋習」crime列表項時,會生成一個負責託管CrimeFragment的CrimeActivity,並顯示出某特定Crime實例的明細信息。如圖10-1所示。編碼
圖10-1 從CrimeListActivity中啓動CrimeActivityspa
從fragment中啓動activity的實現方式,基本等同於從activity中啓動另外一activity的實現方式。咱們調用Fragment.startActivity(Intent)
方法,該方法在後臺會調用對應的Activity
方法。操作系統
在CrimeListFragment
的onListItemClick(...)
實現方法裏,用啓動CrimeActivity
實例的代碼,替換日誌記錄crime標題的代碼,如代碼清單10-1所示。(暫時忽略Crime
變量未使用的提示信息,下一節會使用它)3d
代碼清單10-1 啓動CrimeActivity
(CrimeListFragment.java)日誌
以上代碼中,指定要啓動的activity爲CrimeActivity
,CrimeListFragment
建立了一個顯式intent。在CrimeListFragment
中使用getActivity()
方法傳入它的託管activity
,此activity
是Intent
構造方法須要的Context
對象。code
運行CriminalIntent應用。點擊任意列表項,屏幕上會出現一個託管CrimeFragment
的CrimeActivity
,如圖10-2所示。對象
圖10-2 空白的CrimeFragmentblog
因爲不知道該顯示哪一個Crime
對象的信息,CrimeFragment
也就沒有顯示出特定Crime
對象的數據信息。
經過將mCrimeId
值附加到Intent
的extra上,咱們能夠告知CrimeFragment
應顯示的Crime
。在onListItemClick(...)
方法中,將用戶所選Crime
的mCrimeId
值附加到用來啓動CrimeActivity
的intent上。輸入代碼清單10-2所示代碼,IDE會報告一個錯誤信息,這是由於CrimeFragment.EXTRA_CRIME_ID
的key值尚未建立。暫時忽略該條錯誤信息,稍後咱們會建立它。
代碼清單10-2 啓動附加extra的CrimeActivity
(CrimeListFragment.java)
建立了顯式intent後,調用putExtra(
)
方法,傳入匹配mCrimeId
的字符串key與key值,完成extra信息的準備。這裏,因爲UUID
是Serializable
對象,咱們調用了可接受Serializable
對象的putExtra(
)
方法,即putExtra(String, Serializable)
方法。
簡單獲取extra的方法是,返回至CrimeFragment
類,爲extra添加key。而後,在onCreate(Bundle)
方法中,獲得CrimeActivity
的intent內的extra信息後,再使用它獲取Crime
對象,如代碼清單10-3所示。
代碼清單10-3 獲取extra信息並取得Crime
對象(CrimeFragment.java)
在代碼清單10-3中,除了getActivity()
方法的調用,獲取extra數據的實現代碼與activity裏獲取extra數據的代碼同樣。getIntent()
方法返回用來啓動CrimeActivity
的Intent
。而後調用Intent
的getSerializableExtra(String)
方法獲取UUID
並存入變量中。
取得Crime
的ID後,利用該ID從CrimeLab
單例中調取Crime
對象。使用CrimeLab.get(...)
方法須要Context
對象,所以CrimeFragment
傳入了CrimeActivity
。
Crime
數據更新CrimeFragment
視圖既然CrimeFragment
獲取了Crime
對象,它的視圖即可顯示該Crime
對象的數據。參照代碼清單10-4,更新onCreateView(...)
方法,顯示Crime
對象的標題及解決狀態。(顯示日期的代碼早已就緒)
代碼清單10-4 更新視圖對象(CrimeFragment.java)
運行應用。選中Crime #4,查看顯示了正確crime數據信息的CrimeFragment實例,如圖10-3所示。CriminalIntent
圖10-3 Crime #4列表項的明細內容
只需幾行簡單的代碼,就可實現讓fragment直接獲取託管activity的intent。然而,這種方式是以犧牲fragment的封裝性爲代價的。CrimeFragment
再也不是可複用的構建單元,由於它老是須要由某個具體activity託管着,該activity的Intent
又定義了名爲EXTRA_CRIME_ID
的extra。
就CrimeFragment
類來講,這看起來合情合理。但這也意味着,按照當前的編碼實現,CrimeFragment
便再也沒法用於任何其餘的activity了。
一個比較好的作法是,將mCrimeId
存儲(Stash)在CrimeFragment
的某個地方,而不是將它保存在CrimeActivity
的私有空間裏。這樣,無需依賴於CrimeActivity
的intent內指定extra的存在,CrimeFragment
就能(本身)獲取本身所需的extra數據信息。fragment的「某個地方」實際就是它的arguments bundle。
每一個fragment實例均可附帶一個Bundle
對象。該bundle可含有多個key-value對,咱們能夠如同附加extra到Activity
的intent中那樣使用它們。一個key-value對即一個argument。
要建立fragment argument,首先需建立Bundle
對象。而後,使用Bundle
限定類型的「put」方法(相似於Intent
的方法),將argument添加到bundle中(如如下代碼所示)。
Bundle args = new Bundle();
args.putSerializable(EXTRA_MY_OBJECT, myObject);
args.putInt(EXTRA_MY_INT, myInt);
args.putCharSequence(EXTRA_MY_STRING, myString);
附加argument bundle給fragment,需調用Fragment.setArguments(Bundle)
方法。注意,該任務必須在fragment建立後、添加給activity前完成。
爲知足以上苛刻的要求,Android開發者遵循的習慣作法是:添加名爲newInstance()
的靜態方法給Fragment
類。使用該方法,完成fragment實例及bundle對象的建立,而後將argument放入bundle中,最後再附加給fragment。
託管activity須要fragment實例時,需調用newInstance()
方法,而非直接調用其構造方法。並且,爲知足fragment建立argument的要求,activity可傳入任何須要的參數給newInstance()
方法。
如代碼清單10-5所示,在CrimeFragment
類中,編寫能夠接受UUID
參數的newInstance(UUID)
方法,經過該方法,完成arguments bundle以及fragment實例的建立,最後附加argument給fragment。
代碼清單10-5 編寫newInstance(UUID)
方法(CrimeFragment.java)
如今,當CrimeActivity
建立CrimeFragment
時,應調用CrimeFragment.newInstance(UUID)
方法,並傳入從它的extra中獲取的UUID
參數值。回到CrimeActivity
類中,在createFragment()
方法裏,從CrimeActivity
的intent中獲取extra數據信息,並將之傳入CrimeFragment.newInstance(UUID)
方法,如代碼清單10-6所示。
代碼清單10-6 使用newInstance(UUID)
方法(CrimeActivity.java)
注意,交互的activity和fragment不須要也沒法同時保持通用獨立性。CrimeActivity
必須瞭解CrimeFragment
的內部細節,好比知曉它內部有一個newInstance(UUID)
方法。這很正常。託管activity就應該知道有關託管fragment方法的細節,但fragment則沒必要知道其託管activity的細節問題。至少在須要保持fragment通用獨立性的時候是如此。
fragment在須要獲取它的argument時,會先調用Fragment
類的getArguments()
方法,接着再調用Bundle
的限定類型的「get」方法,如getSerializable(...)
方法。
如今回到CrimeFragment.onCreate(...)
方法中,調整代碼,改成從fragment的argument中獲取UUID
,如代碼清單10-7所示。
代碼清單10-7 從argument中獲取crime ID(CrimeFragment.java)
運行CriminalIntent應用。雖然運行結果仍與以前一致,但咱們應該感到由衷地高興。由於咱們不只保持了CrimeFragment
類的獨立性,又爲下一章實現CriminalIntent應用更爲複雜的列表項導航打下了良好基礎。
運行CriminalIntent應用,點擊某個列表項,而後修改對應的Crime明細信息。這些修改的數據被保存至模型層,但返回列表後,列表視圖並無發生改變。下面咱們來處理這個問題。
如模型層保存的數據發生改變(或可能發生改變),應通知列表視圖的adapter,以便其及時獲取最新數據並從新加載顯示列表項。在適當的時點,與系統的ActivityManager回退棧協同運做,能夠完成列表項的刷新。
CrimeListFragment啓動CrimeActivity實例後,CrimeActivity被置於回退棧頂。這致使原先處於棧頂的CrimeListActivity實例被暫停並中止。
用戶點擊後退鍵返回到列表項界面,CrimeActivity隨即被彈出棧外並被銷燬。CrimeListActivity繼而被從新啓動並恢復運行。應用的回退棧如圖10-4所示。
圖10-4 CriminalIntent應用的回退棧
CrimeListActivity恢復運行狀態後,操做系統會向它發出調用onResume()生命週期方法的指令。CrimeListActivity接到指令後,它的FragmentManager會調用當前被activity託管的fragment的onResume()方法。這裏,CrimeListFragment即惟一的目標fragment。
在CrimeListFragment中,覆蓋onResume()方法刷新顯示列表項,如代碼清單10-8所示。
代碼清單10-8 在onResume()方法中刷新列表項(CrimeListFragment.java)
爲何選擇覆蓋onResume()方法來刷新列表項顯示,而非onStart()方法呢?當一個activity位於咱們的activity以前時,咱們沒法保證本身的activity是否會被中止。如前面的activity是透明的,則咱們的activity可能只會被暫停。對於此場景下暫停的activity,onStart()方法中的更新代碼是不會起做用的。通常來講,要保證fragment視圖獲得刷新,在onResume()方法內更新代碼是最安全的選擇。
運行CriminalIntent應用。選擇某個crime項並修改其明細內容。而後返回到列表項界面,如預期那樣,列表項當即刷新反映了更改的內容。
通過前兩章的開發,CriminalIntent應用已得到大幅更新。如今,咱們來看看更新後的應用對象圖解,如圖10-5所示。
圖10-5 應用對象圖解更新版