1. 程式人生 > >最新Activity與Fragment完全理解

最新Activity與Fragment完全理解

Android是通過FragmentManager來管理Fragment,每次對Fragment進行新增和移除時需要開啟事務,通過事務處理這些相應的操作,然後commit事務。

1、新增、移除Fragment的幾種方式

在對Fragment進行管理前,需要開啟一個事務,如下:         FragmentManager fm = getSupportFragmentManager();         FragmentTransaction tx = fm.beginTransaction(); FragmentTransaction下管理Fragment的主要方法有add()、remove()、replace()、hide()、show()、detach()、attach()。
新增Fragment方式一:         FragmentManager fm = getSupportFragmentManager();         FragmentTransaction tx = fm.beginTransaction();         tx.add
(R.id.content, new Fragment1(),"Fragment1");         tx.commit(); 這裡是直接通過add將Fragment1繫結到id為content的View上。 新增Fragment方式二:
FragmentManager fm = getSupportFragmentManager();         FragmentTransaction tx = fm.beginTransaction();         tx.replace(R.id.content, new Fragment1(),"Fragment1");         tx.commit
(); 這裡使用replace來新增Fragment,replace的作用相當於是remove() + add() 組合後的作用。即使用replace會先移除掉當前id為content上的Fragment,這個被移除掉的Fragment就會被銷燬掉(如果當前事務),然後通過add再把新的Fragment新增到View上。 (1)使用replace方式,相當於在對應id為content的FrameLayout上只有一層,那就是上面的Fragment1,通過replace這種方式,會把Fragment的生命週期再走一遍,如果我們的Fragment中有獲取資料的操作的話,會頻繁的去拉取資料;使用replace,Fragment繫結的檢視一定會銷燬,Fragment例項不一定會銷燬
,主要看有沒有新增到回退棧。 (2)而通過add方式,我們可以在id為content的FrameLayout上新增多層,也即可以通過多次add來新增多個Fragment到FrameLayout上。這個時候,我們就可以配合hide()、show()方法來不斷切換不同的Fragment。在我們通過add方式添加了Fragment到FrameLayout 的View上之後,通過hide()、show()來切換Fragment還有一個優勢就是,當一個Fragment重新show展示出來的時候,它原來的資料還保留在該Fragment上,也就是說hide並不會銷燬Fragment,只是單純的隱藏了而已推薦方式: 因此,推薦使用add、hide、show的方式管理Fragment。但是這種方式一些情況下也會有一個缺陷就是:可能會造成Fragment重疊。 比如底部有四個Tab:tab1,tab2,tab3,tab4,對應的Fragment引用為tab1Fragment,tab2Fragment,tab3Fragment,tab4Fragment,首先通過add將這四個Fragment新增到FragmentManager後,通過hide和show切換不同TAB都可以處於正常情況,當我們切換到tab1時,假設tab1上的Fragment為 tab1Fragment變數指向的(即tab1Fragment=  new Fragment()),這個時候我們按下HOME鍵,如果長時間沒有回到應用或者記憶體不足了,系統回收了該引用,此時tab1Fragment= null;但是,tab1的Fragment例項其實還是存在與記憶體中,只是引用被銷燬了,這個時候,我們切換到tab2,這個步驟中,我們會把tab1的fragment隱藏掉,然後顯示tab2,即如下操作:         tx.hide(tab1Fragment);         tx.show(tab2Fragment);         tx.commit(); 但是,因為此時 tab1Fragment = null,引用變數為空,hide操作無法實現隱藏功能,但是又由於tab1上的Fragment例項還處於記憶體中,因此此時會造成tab2與tab1重疊的現象。再切換到tab1時,因為tab1Fragment = null,此時會再去建立一個新的Fragment,放入到tab1上,導致原來的tab1上的Fragment一直存在與記憶體中導致重疊,直至它被回收。 造成上述問題的原因還是因為我們無法找到那個例項物件Fragment,因為引用tab1Fragment已經為空了。這個時候,我們在add的時候可以給Fragment繫結一個tag,用它來標識該Fragment,如果引用為空了,再通過tag來找到該Fragment。如下:        
  1. //在新增Fragment時
  2.         FragmentManager fm = getSupportFragmentManager();  
  3.         FragmentTransaction tx = fm.beginTransaction();  
  4.         tab1Fragment = new Fragment1();  
  5.         tx.add(R.id.content, tab1Fragment,"fragment1");  
  6.          tx.commit();  
  7.         //在使用時,比如切換到tab2時
  8.         if(tab1Fragment != null){  
  9.             tx.hide(tab1Fragment);  
  10.             tx.show(tab2Fragment);  
  11.             tx.commit();  
  12.         }else{  
  13.             tab1Fragment = (Fragment1) fm.findFragmentByTag("fragment1");  
  14.             tx.hide(tab1Fragment);  
  15.             tx.show(tab2Fragment);  
  16.             tx.commit();  
  17.         }  

2、回退棧

當我們在一個Activity中有多個Fragment進行切換時,我們按下返回鍵時,會直接退出這個Activity,如果我們想在按下返回鍵時,退回到上一個顯示的Fragment,而不是直接返回,我們就需要使用到回退棧了,類似於系統為每個應用建立的Activity棧一樣,每個Activity也維護著一個事務回退棧,在我們通過事務對Fragment進行操作的時候,如果將這個事務新增到回退棧了,這個Fragment的例項就不會被銷燬。按返回鍵時就可以回到這裡。如下: 在MainActivity中,
  1. publicclass MainActivity extends Activity  {    
  2.     @Override
  3.     protectedvoid onCreate(Bundle savedInstanceState)  {    
  4.         super.onCreate(savedInstanceState);    
  5.         setContentView(R.layout.activity_main);    
  6.         FragmentManager fm = getSupportFragmentManager();    
  7.         FragmentTransaction tx = fm.beginTransaction();    
  8.         tx.add(R.id.content, new Fragment1(),"fragment1");    
  9.         tx.commit();    
  10.     }    
  11. }    
進入Activity時初始化載入第一個Fragment1,這裡我們並沒有把它加入到回退棧中,這是因為當目前顯示的是Fragment1時,按下返回鍵我們就是需要直接退出,因為這是最初的那個Fragment,如果我們加入到了回退棧,那按下返回鍵後將是一片空白,再按一次返回鍵後才會退出這個Activity,這是因為第一次按下返回鍵時,相當於是將Fragment1從棧中彈出,此時被銷燬了,Activity的FrameLayout也就沒有了Fragment依附,因此一片空白。 在Fragment1中,有了一個按鈕,點選後開啟第二個Fragment2,
  1. publicclass Fragment1 extends Fragment implements OnClickListener  {    
  2.           private Button mBtn;    
  3.         @Override
  4.         public View onCreateView(LayoutInflater inflater, ViewGroup container,    
  5.                 Bundle savedInstanceState)  {    
  6.             View view = inflater.inflate(R.layout.fragment_one, container, false);    
  7.             mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);    
  8.             mBtn.setOnClickListener(this);    
  9.             return view;    
  10.         }    
  11.         @Override
  12.         publicvoid onClick(View v)  {    
  13.             Fragment2 fTwo = new Fragment2();    
  14.             FragmentManager fm = getFragmentManager();    
  15.             FragmentTransaction tx = fm.beginTransaction();    
  16.             tx.replace(R.id.content, fTwo, "fragment2");    
  17.             tx.addToBackStack(null);  //將當前事務新增到回退棧
  18.             tx.commit();    
  19.         }    
  20.     }    
在Fragment2中,
  1. publicclass Fragment2 extends Fragment implements OnClickListener {    
  2.         private Button mBtn ;    
  3.         @Override
  4.         public View onCreateView(LayoutInflater inflater, ViewGroup container,    
  5.                 Bundle savedInstanceState)  {    
  6.             View view = inflater.inflate(R.layout.fragment_two, container, false);    
  7.             mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);    
  8.             mBtn.setOnClickListener(this);    
  9.             return view ;     
  10.         }    
  11.         @Override
  12.         publicvoid onClick(View v)  {  //開啟Fragment3
  13.             Fragment3 fThree = new Fragment3();    
  14.             FragmentManager fm = getFragmentManager();    
  15.             FragmentTransaction tx = fm.beginTransaction();    
  16.             tx.hide(this);  //隱藏當前顯示的Fragment2
  17.             tx.add(R.id.content , fThree, "fragment3");  //新增Fragment3
  18.             tx.addToBackStack(null);  //將當前事務新增到回退棧
  19.             tx.commit();    
  20.         }    
  21.     }    
在Fragment3中我們只是列印了一些訊息,就不再寫了。 噹噹前顯示到Fragment3時,我們按下返回鍵,將會顯示出Fragment2出來,繼續按下返回鍵,顯示出Fragment1出來,再按下後,直接退出Activity。 因為我們在Fragment1和Fragment2中,在事務提交之前,即tx.commit()之前,我們把當前的事務(用新的Fragment替換當前顯示Fragment或者hide當前Fragment)加入到了回退棧,即tx.addToBackStack(null),點選返回鍵後,就從回退棧中退出棧頂元素,即上一個加入的事務。 上面我們使用了前面介紹的兩種新增Fragment的方式,即replace方式和hide()、add()方式,replace方式,Fragment繫結的檢視一定會銷燬,如果該事務加入到了回退棧,Fragment例項就不會被銷燬,只是檢視銷燬了;而hide()、add()方式隱藏當前Fragment,加入新的Fragment,隱藏的Fragment繫結的檢視也不會被銷燬。 這裡的檢視銷不銷燬,指的是我們的資料有沒有被儲存下來,如果檢視被銷燬了,說明重新回到這個Fragment後,會重新進入onCreate及之後的週期方法區建立一個新的檢視,這個時候資料肯定就不在了如果檢視沒有被銷燬,在重新回到這個Fragment時,原來的輸入資料還在,沒有丟失
當然,在Fragment裡面也有onSaveInstanceState(Bundle)方法,可以通過這個來儲存資料,然後再onCreate或其他方法裡面獲取到資料來解決資料丟失的問題

3、與Activity通訊

因為Fragment依附於Activity,Activity與Fragment通訊,可以有以下幾種辦法:

(1)如果你Activity中包含自己管理的Fragment的引用,可以通過引用直接訪問所有的Fragment的public方法

(2)如果Activity中沒有儲存任何Fragment的引用,那麼沒關係,每個Fragment都有一個唯一的TAG或者ID,可以通過getSupportFragmentManager().findFragmentByTag()或者findFragmentById()獲得任何Fragment例項,然後進行操作。

(3)在Fragment中可以通過getActivity得到當前繫結的Activity的例項,然後進行操作。