天天看點

碎片Fragment

https://www.jianshu.com/p/184f0c8857d6

安卓自3.0開始引入Fragment的概念,主要是為了能在不同分辯率螢幕上進行更為動态和靈活的UI設計,讓程式更加合理和充分利用大螢幕空間。本篇将學習Fragment以下幾個知識點:

  • Fragment概要
  • Fragment生命周期
  • 加載Fragment方法
    • 靜态加載
    • 動态加載
  • Fragment與Activity之間通信

1.Fragment概要

學習Fragment的時候可以聯系之前學習過的Activity,因為它們有很多相似點:都可包含布局,有自己的生命周期,Fragment可看似迷你活動。正如Fragment的名字--碎片,它的出現是為了解決Android碎片化 ,它可作為Activity界面的組成部分,可在Activity運作中實作動态地加入、移除和交換。一個Activity中可同時出現多個Fragment,一個Fragment也可在多個Activity中使用。活動和碎片像極了夫妻, 雖然緊密聯系但是又有獨立空間,在一起讓彼此變得更好。

下面這個非常經典的例子更直覺地說明了Fragment作用:

碎片Fragment

2.Fragment生命周期

先來看官方文檔提供的有關Fragment生命周期的圖檔。

碎片Fragment

碎片Fragment

是不是能發現Fragment和Activity的生命周期太相似了,現在隻需要再介紹幾個Activity中沒講過的新方法:

碎片Fragment

在上圖中畫了幾條線,可以看到Fragment周期中的狀态幾乎都是成對出現的,是以不難了解下圖幾種變化下Fragment生命周期方法的調用順序了。

碎片Fragment

加載到Activity中的Fragment在各種變化下方法的調用順序更值得注意。需要提一句的是,Activity的FragmentManager負責調用隊列中Fragment的生命周期方法,隻要Fragment的狀态與Activity的狀态保持了同步,托管Activity的FragmentManager便會繼續調用其他生命周期方法以繼續保持Fragment與Activity的狀态一緻。

碎片Fragment

Fragment生命周期與Activity生命周期的一個關鍵差別就在于,Fragment的生命周期方法是由托管Activity而不是作業系統調用的。Activity中生命周期方法都是protected,而Fragment都是public,也能印證了這一點,因為Activity需要調用Fragment那些方法并管理它。

3.加載Fragment方法

現在就來學習如何在Activity中加載Fragment。

(1)靜态加載:在托管Activity的layout檔案中聲明Fragment

靜态加載Fragment大緻過程如圖,分成四步:

碎片Fragment

下面通過一個簡單的例子感受Fragment靜态加載到Activity的過程。

第一步:建立frag_layout.xml,為Fragment指定一個布局,這裡簡單的放一個TextView和一個Button。

碎片Fragment

第二步:建立一個MyFragment類并繼承Fragment,這裡引用android.app包下的就可以,另一個包下主要用于相容低版本的安卓系統。然後重寫onCreateView()方法,這個方法裡通過LayoutInflater的inflate()方法将剛剛定義的frag_layout布局加載進來并得到一個View,再return這個View。

碎片Fragment

第三步:建立mian.xml,作為Activity的布局,使用< fragment>标簽添加碎片,并且一定要有android:name屬性且值為被加載的Fragment類的包名.類名完整形式。

碎片Fragment

第四步:在MainActivity中加載main布局。現在MyFragment就完成了靜态加載到MainActivity中,這時碎片裡的控件自然也是活動的一個部分,可直接在活動中擷取到Button的執行個體,來注冊點選事件了。

碎片Fragment

運作一下看看能不能達到效果:

碎片Fragment

(2)動态加載:在托管Activity通過代碼動态添加

動态加載的代碼也非常簡單,直接看例子。修改main.xml,将整個< fragment>删掉。但還保留一個LinerLayout的空間并且還給了Id,為何這樣做?馬上揭秘。

碎片Fragment

接下來在MainActivity中添加幾行代碼:

碎片Fragment

可将整個過程大緻分為三個步驟:

第一步,先用getFragmentManager()方法擷取一個FragmentManager對象,再通過它的beginTransaction()擷取一個FragmentTransaction的執行個體。

第二步,用beginTransaction.add()方法将MyFragemnt執行個體添加到main布局裡LinearLayout裡,終于知道之前鋪墊的Id是怎麼回事了。一定要注意,add()方法裡的第一個參數是容器視圖資源Id,而不是layout。容器視圖資源Id有兩個作用:告知FragmentManager,碎片視圖應該出現在活動視圖的什麼地方;它也是FragmentManager隊列中碎片的唯一辨別符。而靜态加載時碎片的唯一辨別符正是在活動布局裡< fragment>下的id。

第三步:調用beginTransaction.commit()送出。另外,如果允許使用者通過按下傳回按鍵傳回到前一個Fragment狀态,在調用commit()之前先調用addToBackStack(true)方法。

這裡注意到動态加載進來的Fragment裡的控件并不能直接在活動中findViewById得到,那麼如何實作點選效果呢,學完下一個知識點就有辦法了。

4.Fragment與Activity之間通信

在活動中可以通過調用FragmentManager的findFragmentById()方法來得到相應碎片的執行個體,繼而可以調用碎片裡的方法。以上面demo舉例,如果想得到靜态加載碎片的執行個體,可在MainActivity添加代碼如下:

MyFragment myFragment = (MyFragment)getFragmentManager(). findFragmentById(R.id.fragment);
           

如果想得到動态加載碎片的執行個體,代碼如下:

MyFragment myFragment = (MyFragment)fragmentManager(). findFragmentById(R.id.layout);
           

在碎片中也可以通過調用getActivity()方法來得到和目前碎片相關聯的活動執行個體,這樣調用活動裡的方法就變得輕而易舉了。比如想在MyFragment得到MainActivity的執行個體:

MainActivity activity=(MainActivity)getActivity();
           

于是碎片和活動可以很友善地進行通信了。再想一想碎片和碎片之間如何進行通信?先在一個碎片中可以得到與它相關聯的活動,然後再通過這個活動去擷取另外一個碎片的執行個體,這樣實作了不同碎片之間的通信了。

現在你有沒有想到解決之前那個問題的辦法呢?可以這樣做,修改MyFragment,代碼如下圖所示:

碎片Fragment

現在按鈕點選就又有響應了!其實在實際開發中,如果某一闆塊每次用到都需要相同的功能,就完全可以在碎片中實作需求,而不必在活動中重複代碼,這樣可以提高代碼的複用性。

 Fragment如何實作類似 Activity棧的壓棧和出棧效果的?

Fragment的事物管理器内部維持了一個雙向連結清單結構,該結構可以記錄我們每次 add的 Fragment和 replace的 Fragment,然後當我們點選 back按鈕的時候會自動幫我們實作退棧操作。 

ViewPager+Fragment的左右滑動,如何實作Fragment的懶加載,Viewpager預設加載幾個?

Viewpager預設加載3個。1個Activity裡面可能會以Viewpager(或其他容器)與多個Fragment來組合使用,而如果每個fragment都需要去加載資料,或從本地加載,或從網絡加載,那麼在這個Activity剛建立的時候就變成需要初始化大量資源。是以我們要進行懶加載。方法比較多,這裡給大家提供兩種方案:

方案 

1:在 Fragment裡的 setUserVisibleHint,該方法用于告訴系統,這個 Fragment的 UI是否是可見的。是以我們隻需要繼承 Fragment并重寫該方法,即可實作在 Fragment可見時才進行資料加載操作,即 Fragment的懶加載。實作代碼: 

1.package fragment; 
2.import android.support.v4.app.Fragment; 
 


3./**
4.*Fragment懶加載
5.*/
6.public abstract class LazyFragment extends Fragment{
7.protected boolean isVisible;
8.@Override9.//frahment從不可見到完全可見的時候,會調用該方法
10.public void setUserVisibleHint(booleanisVisibleToUser){
11.super.setUserVisibleHint(isVisibleToUser);
12.if(getUserVisibleHint()){
13.isVisible=true;
14.onVisible();//可見
15.}else{
16.isVisible=false;
17.onInvisible();//不可見
18.}
19.}
20.//懶加載的方法,在這個方法裡面我們為Fragment的各個元件去添加資料
21.protected abstract void lazyLoad();
22.protected void onVisible(){
23.     lazyLoad();
24.}
25.protected void onInvisible(){
26. }
27.}
使用:
1.public class Fragment extends LazyFragment{
2.privatebooleanisPrepared;//标志位,标志已經初始化完成。
3.//在這個方法裡面去給我們的Fragment添加資料
4.@Override5.protectedvoidlazyLoad(){
6.if(isPrepared&&isVisible){
7.getNewsDate(getActivity(),channelId);
8.page++;
9.isPrepared=false;
10.}
11.}
12.@Override
13.public View 
 onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState){
View 
 view=LayoutInflater.from(getActivity()).inflate(R.layout.fragment,container,false);
17.isPrepared=true;
   lazyLoad();//這裡我們調用以下去加載我們的資料 

return view; 


20.}
           

方案 2:在繼承 FragmentStatePagerAdapter重寫以下的方法,傳回 PagerAdapter.POSITION_NONE; 

//這個參數為不進行預加載[email protected](Objectobject){

returnPagerAdapter.POSITION_NONE;

}

4. Fragment與Activity之間是如何傳值的:

1. Activity向Fragment傳值:

步驟:

  1. 要傳的值,放到bundle對象裡;
  2. 在Activity中建立該Fragment的對象fragment,通過調用

    fragment.setArguments()

    傳遞到fragment中;
  3. 在該Fragment中通過調用

    getArguments()

    得到bundle對象,就能得到裡面的值。

2. Fragment向Activity傳值:

第一種:

在Activity中調用getFragmentManager()得到fragmentManager,,調用findFragmentByTag(tag)或者通過findFragmentById(id)

FragmentManager fragmentManager = getFragmentManager();

Fragment fragment = fragmentManager.findFragmentByTag(tag);

第二種:

通過回調的方式,定義一個接口(可以在Fragment類中定義),接口中有一個空的方法,在fragment中需要的時候調用接口的方法,值可以作為參數放在這個方法中,然後讓Activity實作這個接口,必然會重寫這個方法,這樣值就傳到了Activity中

5. Fragment與Fragment之間是如何傳值的:

第一種:

通過

findFragmentByTag

得到另一個的Fragment的對象,這樣就可以調用另一個的方法了。

第二種:

通過接口回調的方式。

第三種:

通過

setArguments

getArguments

的方式。

6. FragmentTransaction的add和replace的差別:

  1. add + hide + show的方式:

    其實add是一層層添加上去的,通過show去顯示目前界面,hide去隐藏其他的界面,這時候的FrameLayout是會有很多層的。Fragment A 切換到Fragment B,然後再由Fragment B 切換到Fragment A 的時候,Fragment A 的所有生命周期是不會走的,隻會調用

    onHiddenChanged(boolean isHidden)

    ,也就是說hide和show隻是把其他界面隐藏,目前界面顯示的效果,并不會走生命周期方法。
  2. replace的方式:

    其實replace是會替換掉原有的,是以這種方式的FrameLayout是隻有一層的,再如上面的方式切換fragment,Fragment A會依次走生命周期方法:

    onAttach ---> onViewCreated ---> onActivityCreated ---> onStart。

    但是如果添加代碼

    ft.addToBackStack(null)

    ,生命周期方法

    onAttach

    方法就不會走。

7. Fragment如何實作類似Activity的壓棧和出棧效果的:

Fragment的事物管理器内部維持了一個雙向連結清單結構,該結構可以記錄我們add或者replace的Fragment,然後當我們按傳回鍵的時候,會自動幫我們實作出棧操作。

8. FragmentPagerAdapter與FragmentStatePagerAdapter的差別:

一. 由于FragmentStatePagerAdapter在destoryItem的時候調用mCurTransaction.remove(fragment),會回收記憶體的,而頁面比較多的時候,就比較消耗記憶體,是以FragmentStatePagerAdapter适合于頁面比較多的情況。

二. FragmentPagerAdapter在destoryItem的時候調用mCurTransaction.detach(fragment),沒有回收記憶體,隻是将fragment與activity的UI進行分離,是以FragmentPagerAdapter适合于頁面比較少的情況。

總結:FragmentPagerAdapter适用于頁面較少的情況,FragmentStatePagerAdapter适用于頁面較多的情況。

上一篇: Fragment碎片
下一篇: fragment(碎片)