相關文章:
1、《Fragment詳解之一——概述》 2、《Fragment詳解之二——基本使用方法》 3、《Fragment詳解之三——管理Fragment(1)》 4、《Fragment詳解之四——管理Fragment(2)》 5、《Fragment詳解之五——Fragment間參數傳遞》 6、《Fragment詳解之六——如何監聽fragment中的回退事件與怎樣保存fragment狀態》
上一篇,給大家講了有關Fragment管理的幾個函數,即add,replace,remove,這節再講講其它函數,然后再給大家看一個系統BUG。
一、hide()、show()
1、基本使用
這兩個函數的功能非常簡單,
[java] view plaincopy
public ?FragmentTransaction?hide(Fragment?fragment);??public ?FragmentTransaction?show(Fragment?fragment);??
先看下面的效果圖:
首先,依次添加fragment1,fragment2,fragment3 然后點擊”frag3 hide”,將fragment3隱藏不顯示,所以就顯示出來它的下一層fragment2的視圖 然后再點擊“frag3 show”,將fragment3重新顯示出來 然后點擊“frag2 hide”按鈕,將fragment2隱藏,但是由于fragment3覆蓋在fragment2之上,fragment2隱藏之后對fragment3沒有任何影響,所以在視圖上看不到任何效果。 這時候,我們再點擊“hide frag3”,將fragment3隱藏起來,這時候,由于fragment3和fragment2都隱藏了,所以顯示的就是fragment1的視圖。 最后,點擊“frag2 show”將fragment2顯示出來?
代碼如下: (1)、同樣是新建三個fragment,命名為Fragment1,Fragment2,Fragment3,同樣是用背景色和文字來區別; (2)、然后是點擊“add frag1”按鈕的代碼
[java] view plaincopy
Fragment1?fragment1?=?new ?Fragment1();?? addFragment(fragment1,?"fragment1" );??
其中:
[java] view plaincopy
private ?void ?addFragment(Fragment?fragment,?String?tag)?{??????FragmentManager?manager?=?getSupportFragmentManager();?? ????FragmentTransaction?transaction?=?manager.beginTransaction();?? ????transaction.add(R.id.fragment_container,fragment,?tag);?? ????transaction.addToBackStack(tag);?? ????transaction.commit();?? }??
這個函數已經在前面幾章用過N多次了,就不再講了。
(3)、frag3 hide的代碼:
[java] view plaincopy
FragmentManager?manager?=?getSupportFragmentManager();?? Fragment?fragment?=?manager.findFragmentByTag("fragment3" );?? FragmentTransaction?transaction?=?manager.beginTransaction();?? transaction.hide(fragment);?? transaction.addToBackStack("hide?fragment3" );?? transaction.commit();??
也沒什么難度,跟前面幾篇不一樣的地方就是調用了transaction.hide(fragment);函數;
(4)、frag3 show的代碼:
[java] view plaincopy
FragmentManager?manager?=?getSupportFragmentManager();?? Fragment?fragment?=?manager.findFragmentByTag("fragment3" );?? FragmentTransaction?transaction?=?manager.beginTransaction();?? transaction.show(fragment);?? transaction.addToBackStack("show?fragment3" );?? transaction.commit();??
這里也基本上與以前的操作代碼都一樣,只是使用了transaction.show(fragment);函數
2、在實戰中的運用方法
如果我們使用replace來切換頁面,那么在每次切換的時候,Fragment都會重新實例化,重新加載一邊數據,這樣非常消耗性能和用戶的數據流量。 這是因為replace操作,每次都會把container中的現有的fragment實例清空,然后再把指定的fragment添加進去,就就造成了在切換到以前的fragment時,就會重新實例會fragment。 正確的切換方式是add(),切換時hide(),add()另一個Fragment;再次切換時,只需hide()當前,show()另一個。 這樣就能做到多個Fragment切換不重新實例化:(基本算法如下)
[java] view plaincopy
public ?void ?switchContent(Fragment?from,?Fragment?to)?{??????if ?(!to.isAdded())?{?????? ????????transaction.hide(from).add(R.id.content_frame,?to).commit();??? ????}?else ?{?? ????????transaction.hide(from).show(to).commit();??? ????}?? }??
大家可能覺得這里有個問題,如果我們要show()的fragment不在最頂層怎么辦?如果不在ADD隊列的隊首,那顯然show()之后是不可見的;那豈不影響了APP邏輯。大家有這個想法是很棒的,但在APP中不存在這樣的情況,因為我們的APP的fragment是一層層ADD進去的,而且我們的fragment實例都是唯一的,用TAG來標識,當退出的時候也是一層層剝離的,所以當用戶的動作導致要添加某個fragment時,那說明這個fragment肯定是在棧頂的。
二、detach()、attach()
這兩個函數的聲明如下:
[java] view plaincopy
public ?FragmentTransaction?detach(Fragment?fragment);??public ?abstract ?FragmentTransaction?attach(Fragment?fragment);??
detach():
會將view與fragment分離,將此將view從viewtree中刪除!而且將fragment從Activity的ADD隊列中移除!所以在使用detach()后,使用fragment::isAdded()返回的值是false;但此fragment實例并不會刪除,此fragment的狀態依然保持著使用,所以在fragmentManager中仍然可以找到,即通過FragmentManager::findViewByTag()仍然是會有值的。
attach():
顯然這個方法與detach()所做的工作相反,它一方面利用fragment的onCreateView()來重建視圖,一方面將此fragment添加到ADD隊列中;這里最值得注意的地方在這里:由于是將fragment添加到ADD隊列,所以只能添加到列隊頭部,所以attach()操作的結果是,最新操作的頁面始終顯示在最前面!這也就解釋了下面的例子中,為了fragment2 detach()后,當再次attach()后,卻跑到了fragment3的前面的原因。還有,由于這里會將fragment添加到Activity的ADD隊列中,所以在這里調用fragment::isAdded()將返回True;
下面用一個例子來講講,有關這上面所講解的知識,效果圖如下:
(1)、同樣,先依次添加Fragment1,Fragment2,Fragment3 (2)、然后點擊“frag3 detach”,將fragment3的View視圖刪除,然后從ADD隊列中將fragment移除。之后點擊“fragment is added?”根據TOAST可以看出,fragment::isAdded()函數返回值是false; (3)、然后點擊“frag3 attach”,將fragment重新與Activity綁定,它有兩個動作,一方面重建fragment視圖,一方面將fragment添加到Activity的ADD隊列中;所以這時候點擊“fragment is added?”,fragment::isAdded()函數返回值是true; (4)、然后點擊“frag2 detach”,由于fragment2在fragment3之下,所以給fragment2使用detach,在界面上看不出任何效果。 (5)、但當點擊“frag2 attach”時,問題出現了,由于attach()會做兩件事,一方面重建fragment視圖,一方面將fragment添加到Activity的ADD隊列中;由于是ADD隊列,所以肯定添加的位置肯定在隊首;所以fragment2就顯示在了最上方,把fragment3蓋住了,這就是為什么在點擊“frag2 attach”之后,卻可以看到fragment2的視圖的原因!? 好了,下面就是代碼部分了,這部分代碼是在上一部分的上面添加了幾個按鈕而來的,直接看按鈕點擊時的代碼操作:
1、點擊frag3 detach按鈕的代碼
[java] view plaincopy
FragmentManager?manager?=?getSupportFragmentManager();?? Fragment?fragment?=?manager.findFragmentByTag("fragment3" );?? FragmentTransaction?transaction?=?manager.beginTransaction();?? transaction.detach(fragment);?? transaction.addToBackStack("detach?fragment3" );?? transaction.commit();??
從代碼也可以看到,沒什么難度,這個函數的最難點在于知道detach()在執行過程中都干了什么!再重申一遍:一方面刪除fragment的View視圖;一方面將fragment從Activity的ADD隊列中移除!說是Activity的ADD隊列,倒不如說是container的ADD隊列更貼切些;因為一個Activity上面可以有多個Container來盛裝Fragment實例組,每一個Container都會被分配一個ADD隊列來記錄當前通過add()方法,添加到這個container里的所有fragment實例。
2、點擊“frag3 attach”按鈕的代碼
[java] view plaincopy
FragmentManager?manager?=?getSupportFragmentManager();?? Fragment?fragment?=?manager.findFragmentByTag("fragment3" );?? FragmentTransaction?transaction?=?manager.beginTransaction();?? transaction.attach(fragment);?? transaction.addToBackStack("attach?fragment3" );?? transaction.commit();??
依然,相比以前的fragment操作也只多了一個transaction.attach(fragment);沒什么難度。關鍵仍然在于知道attach()操作都做了哪些事!再次重申:一方面重建fragment的View,注意是重建!另一方面,將fragment實例添加進container的ADD隊列中;關于"frag2 detach"和"frag2 attach"的代碼就不再貼出來了,跟frag3的一樣。
好了,到這里,有關Fragment的操作都已經講完了,下面就講講有關在Fragment操作中Android的BUG!
源碼在文章底部給出
三、系統BUG——add()和replace()千萬不要共用!!!
先寫個例子來看一下問題: 這個例子分為兩部分,
第一部分:先利用add()函數,依次add進去fragment1,fragment2,fragment3,fragment4,fragment5,然后利用"print back stack"打印出當前在回退棧中每次操作的名稱;每回退一次打一次回退棧內容,可見一切都是正常的,即回退棧頂的項,正是當前VIEW頂部顯示的內容。 第二部分,如果我們先利用add()函數,依次add進去fragment1,fragment2,fragment3,fragment4,然后再利用replace函數添加進去fragment5;然后利用"print back stack"打印出當前在回退棧中每次操作的名稱;可以看到,當回退棧頂是"add fragment4"時,fragment4卻沒有出現,點擊返回按鈕,卻把這個"add fragment4"的Transaction操作給返回了。同樣的現象也發生在fragment2中;
還是先看看實現代碼: 1、添加fragment,比如添加fragment1,其它fragment2,fragment3,fragment4同理
[java] view plaincopy
Fragment1?fragment1?=?new ?Fragment1();?? addFragment(fragment1,?"add?fragment1" );??
其中:
[java] view plaincopy
private ?void ?addFragment(Fragment?fragment,?String?tag)?{??????FragmentManager?manager?=?getSupportFragmentManager();?? ????FragmentTransaction?transaction?=?manager.beginTransaction();?? ????transaction.add(R.id.fragment_container,?fragment);?? ????transaction.addToBackStack(tag);?? ????transaction.commit();?? }??
2、replace fragment5:
代碼上沒什么難度,在添加到回退棧時,添加TAG:"replace fragment5"
[java] view plaincopy
Fragment5?fragment5?=?new ?Fragment5();?? FragmentManager?manager?=?getSupportFragmentManager();?? FragmentTransaction?transaction?=?manager.beginTransaction();?? transaction.replace(R.id.fragment_container,?fragment5);?? transaction.addToBackStack("replace?fragment5" );?? transaction.commit();??
3、打印出回退棧中的內容:
這里要講一個函數了:
[java] view plaincopy
public ?int ?getBackStackEntryCount();??public ?BackStackEntry?getBackStackEntryAt(int ?index);??
其中getBackStackEntryAt()返回的變量BackStackEntry,就是回退棧中保存每次transaction操作的變量;它有很多方法,其中BackStackEntry::getName()是獲取Transacion操作的名字,即通過transaction.addToBackStack("replace fragment5");傳進去的字符串。關于BackStackEntry的其它方法,靠大家自己去發掘啦,這個函數用的不多,就不再細講了。
[java] view plaincopy
TextView?tv?=?(TextView)?findViewById(R.id.tv_stack_val);?? ?? FragmentManager?manager?=?getSupportFragmentManager();?? int ?count?=?manager.getBackStackEntryCount();??StringBuilder?builder?=?new ?StringBuilder("回退棧內容為:\n" );?? for ?(int ?i?=?--count;i>=0 ;i--){??????FragmentManager.BackStackEntry?entry=?manager.getBackStackEntryAt(i);?? ????builder.append(entry.getName()+"\n" );?? }?? tv.setText(builder.toString());??
好啦,代碼看完了,要講問題了。那問題來了,為什么在回退棧中有add fragment4和add fragment2的操作,卻不顯示呢?
問題出在了replace()操作上,replace()操作原意的實現應該是清空container中所有的fragment實例,然后再將指定的fragment添加到container的ADD隊列中;但在清空時,他們的代碼是這樣寫的:
[java] view plaincopy
for ?(int ?i=0 ;i<mManager.mAdded.size();?i++)?{??????Fragment?old?=?mManager.mAdded.get(i);?? ????……?? ????mManager.removeFragment(old,?mTransition,?mTransitionStyle);?? }??
其中:mAdded就是我們前面說的container的ADD隊列;看他的操作:
首先,先逐個得到mAdded隊列中的fragment,即:
[java] view plaincopy
Fragment?old?=?mManager.mAdded.get(i);??
然后,將這個fragment實例移除:
[java] view plaincopy
mManager.removeFragment(old,?mTransition,?mTransitionStyle);??
有沒有看出什么問題?他把這個fragment從mAdded隊列中直接移除了!!!!那這不打亂了原來的順序了么,在移除下一個fragment時就根本對不上號了。看不懂?沒關系,我們舉個例子來講:
比如,我們上面的,在mAdded隊列中有1,2,3,4,5這五個fragment;
首先,當i=0時,移除1,這沒錯!但它是將mAdded隊列中的1直接移除的哦!所以移除1以后,mAdded隊列的值變成了2,3,4,5
這時候,當i=1時,刪除的是3!!!!知道問題所在了吧!所以在刪除3后,mAdded隊列的值為2,4,5
所以當i=2時,刪除的是5!!!!所以這就造成了為什么我們的fragment2和fragment4明明在回退棧中,即顯示不出來的原因,因為他們在刪除時根本就沒有刪除,而在回退棧回退時卻又要跟著操作順序來回退,即remove fragment5,逐個add進去fragment4,fragment3,fragment2,fragment1,而又由于fragment4和fragment2沒有被刪除,所以出現了錯誤,導致系統哪里出了問題,所以顯示不出來,至于是哪里出了問題,我也不知道,因為我嘗試了show()和attach() fragment4都還是沒有效果。可能是系統底層的問題吧。所以,這里忠告大家,add()和replace()不能共用!!!!!
有關這個系統BUG的細節分析,請參考文章:
1、《Fragment(五)Transaction 源碼分析》
2、 《報給GOOGLE的BUG地址》
總結
以上是生活随笔 為你收集整理的Fragment详解之四——管理Fragment(2) 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。