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作用:
2.Fragment生命周期
先來看官方文檔提供的有關Fragment生命周期的圖檔。
是不是能發現Fragment和Activity的生命周期太相似了,現在隻需要再介紹幾個Activity中沒講過的新方法:
在上圖中畫了幾條線,可以看到Fragment周期中的狀态幾乎都是成對出現的,是以不難了解下圖幾種變化下Fragment生命周期方法的調用順序了。
加載到Activity中的Fragment在各種變化下方法的調用順序更值得注意。需要提一句的是,Activity的FragmentManager負責調用隊列中Fragment的生命周期方法,隻要Fragment的狀态與Activity的狀态保持了同步,托管Activity的FragmentManager便會繼續調用其他生命周期方法以繼續保持Fragment與Activity的狀态一緻。
Fragment生命周期與Activity生命周期的一個關鍵差別就在于,Fragment的生命周期方法是由托管Activity而不是作業系統調用的。Activity中生命周期方法都是protected,而Fragment都是public,也能印證了這一點,因為Activity需要調用Fragment那些方法并管理它。
3.加載Fragment方法
現在就來學習如何在Activity中加載Fragment。
(1)靜态加載:在托管Activity的layout檔案中聲明Fragment
靜态加載Fragment大緻過程如圖,分成四步:
下面通過一個簡單的例子感受Fragment靜态加載到Activity的過程。
第一步:建立frag_layout.xml,為Fragment指定一個布局,這裡簡單的放一個TextView和一個Button。
第二步:建立一個MyFragment類并繼承Fragment,這裡引用android.app包下的就可以,另一個包下主要用于相容低版本的安卓系統。然後重寫onCreateView()方法,這個方法裡通過LayoutInflater的inflate()方法将剛剛定義的frag_layout布局加載進來并得到一個View,再return這個View。
第三步:建立mian.xml,作為Activity的布局,使用< fragment>标簽添加碎片,并且一定要有android:name屬性且值為被加載的Fragment類的包名.類名完整形式。
第四步:在MainActivity中加載main布局。現在MyFragment就完成了靜态加載到MainActivity中,這時碎片裡的控件自然也是活動的一個部分,可直接在活動中擷取到Button的執行個體,來注冊點選事件了。
運作一下看看能不能達到效果:
(2)動态加載:在托管Activity通過代碼動态添加
動态加載的代碼也非常簡單,直接看例子。修改main.xml,将整個< fragment>删掉。但還保留一個LinerLayout的空間并且還給了Id,為何這樣做?馬上揭秘。
接下來在MainActivity中添加幾行代碼:
可将整個過程大緻分為三個步驟:
第一步,先用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如何實作類似 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傳值:
步驟:
- 要傳的值,放到bundle對象裡;
- 在Activity中建立該Fragment的對象fragment,通過調用
傳遞到fragment中;fragment.setArguments()
- 在該Fragment中通過調用
得到bundle對象,就能得到裡面的值。getArguments()
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的差別:
-
add + hide + show的方式:
其實add是一層層添加上去的,通過show去顯示目前界面,hide去隐藏其他的界面,這時候的FrameLayout是會有很多層的。Fragment A 切換到Fragment B,然後再由Fragment B 切換到Fragment A 的時候,Fragment A 的所有生命周期是不會走的,隻會調用
,也就是說hide和show隻是把其他界面隐藏,目前界面顯示的效果,并不會走生命周期方法。onHiddenChanged(boolean isHidden)
-
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适用于頁面較多的情況。