Android Fragment 該怎么用?

這篇文章是講有關(guān)于Fragment的使用豺撑,大部分還是比較基礎(chǔ)的知識點前硫。之所以寫出來呢屹电,因為我在工作中發(fā)現(xiàn)在使用fragment時走了很多彎路危号,遇見了很多坑。就是因為還有很多細(xì)節(jié)的東西沒有掌握猪半。在這里分享出來磨确,也能方便自己回顧乏奥。

1. fragment概述

fragment是一種控制器對象邓了,activity可以委派一些任務(wù)給它媳瞪,通常這些任務(wù)是管理用戶界面蛇受。受管的用戶界面可以是整個屏幕或者是小部分屏幕。

fragment與支持庫

Google在API11的時候引入的fragment(為滿足平板UI設(shè)計的靈活性要求)熊响,最低能兼容到API4诗赌。
為了應(yīng)用能夠兼容舊版本設(shè)備铭若,Android提供了開發(fā)支持庫叼屠,分別是Fragment(android.support.v4.app.Fragment)镜雨、FragmentActivity(android.support.v4.app.Fragment-Activity)

關(guān)于操作系統(tǒng)內(nèi)置版的fragment庫

開發(fā)者在平時都會優(yōu)先使用支持庫版的fragment而不是系統(tǒng)內(nèi)置的版本荚坞,是因為Google每年都會更新支持庫菲盾,通過此來發(fā)布引入新特性懒鉴,修復(fù)Bug临谱,如果想體現(xiàn)這些好處璃俗,我們只需要升級項目的支持庫版本就好,Google提供支持庫的本意就是能夠方便開發(fā)人員在不支持API的版本是能體驗Fragment悉默。

2. 托管UI fragment

我們都知道fragment是由activity來管理的城豁,在托管fragment的期間,activity必須處理好以下倆件事:

  1. 管理好fragment的生命周期
  2. 在布局中為fragment安排位置

2.1 fragment的生命周期

fragment生命周期 fragment與Activity生命周期的對比

fragment生命周期與activity的生命周期方法非常類似麦牺,也具備停止钮蛛、暫停、運行轉(zhuǎn)態(tài)剖膳,也擁有可以覆蓋的方法,在關(guān)鍵的節(jié)點時候完成一些任務(wù)吱晒,許多方法也對應(yīng)了activity的生命周期方法甸饱。大部分人都知道fragment的onAttach()、onCreat()、onCreatView()這三個生命周期方法是在Activity的onCreat()的時候執(zhí)行的叹话⊥狄牛看過源碼應(yīng)該知道,這三個方法是在onCreate()中的setContentView()中調(diào)用的驼壶。

生命周期方法在開發(fā)中是非常重要的氏豌,因為fragment代表activity工作,它的狀態(tài)就反應(yīng)著activity的狀態(tài)热凹,顯然泵喘,fragment需要相對應(yīng)的生命周期方法來處理activity的工作。那么fragment與activity的生命周期方法最為關(guān)鍵的區(qū)別就在于般妙,fragment的生命周期方法是由activity來調(diào)用而不是操作系統(tǒng)纪铺,操作系統(tǒng)不關(guān)心activity用來管理視圖的fragment。

2.2 托管的倆種方式

activity托管fragment有以下倆種方式:

  1. 在activity布局中添加fragment
  2. 在activity通過code添加

第一種方式比較簡單碟渺,直接通過activityu的布局綁定fragment鲜锚,但是不夠靈活,在fragment生命周期過程中苫拍,無法切換其他fragment視圖芜繁,所以在此我們不多作介紹。

第二種方式:
雖然我們是要在托管的activity中通過代碼添加fragment怯疤,但是首先得定義布局容器浆洗,也就是為frgament安排位置,布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:id="@+id/fragment_container"
             android:layout_width="match_parent"
             android:layout_height="match_parent">

</FrameLayout>

創(chuàng)建fragment以及實現(xiàn)生命周期方法

package com.haife.album_master.activities;


import android.os.Bundle;
import android.support.v4.app.Fragment;

/**
 * A simple {@link Fragment} subclass.
 */
public class BlankFragment extends Fragment {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
   
}

以上代碼需要注意幾點:

  • Fragment的生命周期方法都是公共方法集峦,與Activity不同伏社,activity生命周期方法是保護(hù)方法,前面也說了塔淤,這樣是為了讓托管的activity能夠調(diào)用
  • 類似于activity摘昌,fragment中也有保存和獲取狀態(tài)的Bundle。如同activity的onSaveInstance()方法使用相同高蜂,我們也可以覆蓋這個方法在fragment中
  • fragment的試圖創(chuàng)建并不是在Fragment.onCreat()方法中生成聪黎,雖然我們在方法中配置了fragment 的實例,但創(chuàng)建和配置視圖在另一個生命周期方法onCreatView()中完成的
package com.haife.album_master.activities;


import android.os.Bundle;
import android.support.v4.app.Fragment;

/**
 * A simple {@link Fragment} subclass.
 */
public class BlankFragment extends Fragment {
  
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        TextView textView = new TextView(getActivity());
        textView.setText(R.string.hello_blank_fragment);
        return textView;
    }
}

添加fragment到FragmentManager

FragmentManager類管理的是fragment隊列和fragment的事物回退棧,下圖為FragmentManager圖解:

Paste_Image.png

要以代碼的方式添加fragment到activity中备恤,我們只需要調(diào)用activity中的FragmentManager,首先獲取FragmentManager稿饰。

 FragmentManager fm = getSupportFragmentManager();

Fragment事務(wù)

當(dāng)我們在activity中獲取到FragmentManager時,我們需要把fragment交由其管理露泊。代碼如下:

public class MainActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {
            fragment = new BlankFragment();
            fm.beginTransaction()
                    .add(R.id.fragment_container, fragment)
                    .commit();
        }
    }
}

add(...)方法是整個事務(wù)的核心喉镰,除了添加,fragment事務(wù)還可被用作移除、附加惭笑、分離侣姆、替換fragment隊列中的fragment生真,這個是使用fragment在運行時組裝和重新組裝用戶界面的關(guān)鍵,F(xiàn)ragmentManager管理著fragment事務(wù)的回退棧捺宗。

FragmentManager.beginTransaction()方法是創(chuàng)建并返回FragmentTransaction實例柱蟀,由此可得到一個FragmentTransaction隊列。

現(xiàn)在我們重頭到位總結(jié)一下上面的代碼,首先蚜厉,用過資源Id向FragmenbManager發(fā)出請求獲取fragment實例长已,如果獲取的fragment不為空,那么FragmentManager直接返回它昼牛,這里有個問題痰哨,很多人會問為什么fragment會存在于隊列中呢?你明明還沒有添加fragment,其實很簡單匾嘱,我們之前說過,當(dāng)設(shè)備旋轉(zhuǎn)的時候activity的生命周期會發(fā)生變化早抠,在activity銷毀的時候霎烙,activity的FragmentManager會將fragment隊列保存起來,當(dāng)activity執(zhí)行生命周期的onCreat()方法的時候蕊连,F(xiàn)ragmentManager會優(yōu)先回去到保存的Fragment隊列悬垃,然后重建重而恢復(fù)之前的狀態(tài),這樣你們就知道原因了吧甘苍!下面判空的時候如果為空尝蠕,就直接添加到fragment隊列中0。

抽象的activity類

我們可以看到上面的實現(xiàn)代碼幾乎通用载庭,但是唯一不足的地方就是在添加fragment到FragmentManager的時候?qū)嵗膄ragment只能是BlankFragment,為了應(yīng)用的靈活性看彼。所以接下來我們繼續(xù)優(yōu)化。如何處理呢囚聚,那我們是不是可以在上面的activity定義一個抽象的方法來當(dāng)作activity的父類靖榕,那么子類就會實現(xiàn)該方法來返回fragment實例。

為了區(qū)分顽铸,我們創(chuàng)建一個BaseFragmentActivity

public class BaseFragmentActivity extends FragmentActivity {

    protect abstrct Fragment creatFragment();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {
            fragment = creatFragment();
            fm.beginTransaction()
                    .add(R.id.fragment_container, fragment)
                    .commit();
        }
    }
}

MainActivity修改如下

public class MainActivity extends BaseFragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
    }
    
    @Override
    protected Fragment creatFragment(){
        retrun new BlankFragment();
    }
}

現(xiàn)在看起來我們的MainActivity是不是簡練整潔茁计,在實際開發(fā)中多使用抽象類會大大的節(jié)約你的開發(fā)時間,提高效率谓松。

3.使用Fragment argument

在開發(fā)中有一個非常普遍你的需求星压,就是activity與fragmenr之間的值傳遞。
比如鬼譬,現(xiàn)在我們需求娜膘,從一個fragment跳轉(zhuǎn)到另一新的目標(biāo)activity中(攜帶參數(shù)),然H后目標(biāo)Fragment要負(fù)責(zé)接收這個值拧簸。實現(xiàn)代碼部分如下:

目標(biāo)activity

public class TargetActivity extends BaseFragmentActivity {

    public static final String EXTRA_UUID = "com.haife.album_master.activities.targetId"
    
    public static Intent newIntent(Context ctx,String targetId){
        Intent intent = new Intent(ctx,TargetActivity.class);
        intent.putExtra(EXTRA_UUID,targetId);
        return intent;
    }
}

跳轉(zhuǎn)處

 @Override
public void onClick(){
    Intent intent = TargetActivity.newInstance(getActivity(),id);
    startActivity(intent);
}

到這里劲绪,參數(shù)id已經(jīng)被被安全存儲在TargetActivity中,然而獲取參數(shù)和使用的是Fragment類。

fragment中獲取extra信息

fragment有倆種方式來獲取intent中的數(shù)據(jù)贾富,一種比較簡單直接歉眷,另一種復(fù)雜靈活的方式是涉及到fragment arguement。我們先看簡單的實現(xiàn)方式颤枪,在fragment中通過getActivity()方式獲取到TargetActivity的intent汗捡,返回至TargetFragment類,得到TargetActivity的intent 的extra信息后畏纲,在用它獲取String id的值扇住。

public class TargetFragment extends Fragment {

   public void oncreat(Bundle saveInstanceState){
       String id = getActivity().getIntent().getStringExtra(TargetAtivity.EXTRA_UUID);
   }
}


繼續(xù)幾行代碼,我們就能從托管的activity的intent中獲取到信息盗胀,然而這種方式會讓我們的TargetFragment無法復(fù)用艘蹋,當(dāng)前只能被用于TargetActivity,如何優(yōu)化票灰?我們可以將要獲取的值保存在TargetFragment的某個地方女阀,而不是在TargetAvcivity的私有空間中,這樣屑迂,fragment就不用依賴于activity的intent內(nèi)指定的intent浸策,就能獲取自己所需的extra數(shù)據(jù)。這“某個地方”其實就是fragment argument惹盼。

fragment argument

每個Fragment都存在一個Bundle對象庸汗,bundle中含有鍵值對。我們可以類似于附加extra到activity的intent中一樣手报,一個鍵值對應(yīng)一個argument蚯舱。如何附加arguement給fragment呢?我們需要調(diào)用Fragment.setArgument(Bundle)昧诱,注意這個方法必須在fragment創(chuàng)建后調(diào)用晓淀。andriod開發(fā)者通用的做法是在fragment中定一個靜態(tài)的newInstance()方法,在此方法內(nèi)完成fragment的實例和Bundle對象的創(chuàng)建盏档,然后把argument放入bundle中附加給fragment凶掰。代碼如下:

package com.haife.album_master.activities;


import android.os.Bundle;
import android.support.v4.app.Fragment;

/**
 * A simple {@link Fragment} subclass.
 */
public class TargetFragment extends Fragment {
   private static final String ARG_TARGET_ID = "arg_target_id";

    public static BlankFragment newInstance(String id){
        Bundle bundle = new Bundle();
        bundle.putString(ARG_TARGET_ID,id);

        BlankFragment blankFragment = new BlankFragment();
        blankFragment.setArguments(bundle);
        return blankFragment;
    }

}

現(xiàn)在,需要創(chuàng)建fragment的時候蜈亩,activity要調(diào)用BlankFragment.newInstance(String)的方法懦窘,而不是向上面那樣直接調(diào)用構(gòu)造方法,activity可傳任意參數(shù)到newInstance()方法稚配,按實際開發(fā)需要畅涂,這里傳的是在activity中extra的值:

public class TargetActivity extends BaseFragmentActivity {

    private static final String EXTRA_UUID = "com.haife.album_master.activities.targetId"
    
   @Override
    protected Fragment creatFragment(){
       String id = getIntent().getStringExtra(EXTRA_UUID);
       return TargetFragment.newInstance(id);
    }
}

這里將EXTRA_ID改為私有是因為其他類都用不到了。注意的是道川,activity和fragment不需要也無法同時保持獨立性午衰,activity必要要了解fragment 的內(nèi)部細(xì)節(jié)立宜。

獲取 argument

上面我們已經(jīng)附加了argument給TargetFragment,接下來我們?nèi)绾潍@取到他的argument臊岸?

public class TargetFragment extends Fragment {
   private static final String ARG_TARGET_ID = "arg_target_id";
    private static final String DEFAULT_VALUE = "default_value";
   @Override
   public void onCreate(Bundle saveInstanceState){
         String id = getArguments().getString(ARG_TARGET_ID, DEFAULT_VALUE);
   }

}

代碼寫到這里橙数,雖然比較少,但是很通用帅戒。下面是fragment的一點小拓展灯帮,如果frament中存在一個列表項(RecycleView),當(dāng)數(shù)據(jù)源發(fā)生改變時,我們該如何在fragment中刷新它逻住?

 public class BlankFragment extends Fragment {

    @Override
    public void onResume() {
        super.onResume();
        updateUI();
    }

    private void updateUI() {
        if (mAdapter == null) {
           mAdapter = new MyAdapter(userList);
            recycle.setAdapter(mAdapter);
        }else {
            mAdapter.notifyDataSetChanger();
        }
    }


}

解釋一下為什么選擇覆蓋onResum()方法來刷新列表钟哥,而不用onStart(),當(dāng)有其他activbity位于BlankFragment的宿主activity的之前時,我們無法確定宿主activity是否會被停止瞎访,如果前面的activity是透明的腻贰,那么在onStart()方法中刷新列表是無效,一般來說扒秸,要保證fragment視圖得到刷新银受,在onResum()方法中處理是最安全的。

到這里fragment argument就介紹完了鸦采,那為什么要使用它呢?我們?yōu)槭裁床恢苯釉趂ragment內(nèi)部創(chuàng)建一個實例變量咕幻?想想就知道渔伯,當(dāng)操作系統(tǒng)重建fragment或者用戶離開應(yīng)用,甚至系統(tǒng)回收內(nèi)存肄程,又或是應(yīng)用配置發(fā)生改變時锣吼,所有的實例變量就不復(fù)存在了,所以設(shè)計fragment argument的本意就是為了上述場景蓝厌。當(dāng)然又有人說你可以用實例狀態(tài)保存下來玄叠,然后通過onSaveInstanceState(Bundle)方法存儲。這當(dāng)然也可以拓提,前提是读恃,你回過頭來看代碼的時候能夠記的住~~~

4.通過fragment獲取返回結(jié)果

fragment與activity,有Fragment.startActivityForResult(...)和Fragment.onActivityResult(...),用法類似于Activity的同名方法.但是代态,從fragment中返回結(jié)果的處理有些不同寺惫,fragment能從activity中接受返回結(jié)果,其自身是無法持有返回結(jié)果的蹦疑。盡管Fragment有自己的startActivityForResult(...)和onActivityResult(...),卻沒有setResult(...)方法西雀,相反,我們可以讓activity返回結(jié)果值歉摧。具體代碼如下:


public class BlankFragment extends Fragment {
    ...

    public void returnResult(){
        getActivity().setResult(Activity.Result_OK,null);
    }

    ...

5. 結(jié)尾

最后說點閑話吧艇肴,在我們設(shè)計應(yīng)用時腔呜,正確的使用fragment是非常重要的,但是我們也不能濫用它再悼。Google設(shè)計fragment的本意是封裝可復(fù)用的組件核畴,這里的組件是指屏幕的組件,單屏上最多使用2-3個fragment是比較好的帮哈。其實在選擇activity還是UI fragment來管理用戶界面的時候膛檀,我認(rèn)為不用考慮太多。能使用fragment實現(xiàn)的時候娘侍,就不要去考慮activity了咖刃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市憾筏,隨后出現(xiàn)的幾起案子嚎杨,更是在濱河造成了極大的恐慌,老刑警劉巖氧腰,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枫浙,死亡現(xiàn)場離奇詭異,居然都是意外死亡古拴,警方通過查閱死者的電腦和手機(jī)箩帚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來黄痪,“玉大人紧帕,你說我怎么就攤上這事∥Υ颍” “怎么了是嗜?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挺尾。 經(jīng)常有香客問我鹅搪,道長,這世上最難降的妖魔是什么遭铺? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任丽柿,我火速辦了婚禮,結(jié)果婚禮上魂挂,老公的妹妹穿的比我還像新娘航厚。我一直安慰自己,他們只是感情好锰蓬,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布株灸。 她就那樣靜靜地躺著措拇,像睡著了一般菌湃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赦抖,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音辅肾,去河邊找鬼队萤。 笑死,一個胖子當(dāng)著我的面吹牛矫钓,可吹牛的內(nèi)容都是我干的要尔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼新娜,長吁一口氣:“原來是場噩夢啊……” “哼赵辕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起概龄,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤还惠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后私杜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚕键,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年衰粹,在試婚紗的時候發(fā)現(xiàn)自己被綠了锣光。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡铝耻,死狀恐怖嫉晶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情田篇,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布箍铭,位于F島的核電站泊柬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诈火。R本人自食惡果不足惜兽赁,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冷守。 院中可真熱鬧刀崖,春花似錦、人聲如沸拍摇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽充活。三九已至蜂莉,卻和暖如春蜡娶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背映穗。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工窖张, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚁滋。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓宿接,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辕录。 傳聞我的和親對象是個殘疾皇子睦霎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容