Android Fragment 解析

基本介紹

1团甲、Fragment是依賴于Activity的,不能獨(dú)立存在黍聂。
2躺苦、一個(gè)Activity可以有多個(gè)Fragment。
3产还、一個(gè)Fragment可以被多個(gè)Activity重用匹厘。
4、Fragment有自己的生命周期脐区,并能接收輸入事件愈诚。
5、我們能在Activity運(yùn)行時(shí)動(dòng)態(tài)添加或刪除Fragment牛隅。

生命周期

image

通過這張圖可以看到Fragment的生命周期比Activity多幾個(gè)炕柔。

  • onAttach(Activity)
    當(dāng)Fragment與Activity發(fā)生關(guān)聯(lián)時(shí)調(diào)用
  • onCreateView(LayoutInflater, ViewGroup, Bundle)
    創(chuàng)建該Fragment視圖
  • onActivityCreated(Bundle)
    當(dāng)Activity的onCreate方法返回時(shí)調(diào)用
  • onDestoryView()
    與onCreateView想對(duì)應(yīng),當(dāng)該Fragment的視圖被移除時(shí)調(diào)用
  • onDetach()
    與onAttach相對(duì)應(yīng)倔叼,當(dāng)Fragment與Activity關(guān)聯(lián)被取消時(shí)調(diào)用
注意:除了OnCreateView()汗唱,其他所有方法如果你重寫了宫莱,必須調(diào)用父類對(duì)該方法是實(shí)現(xiàn)(super方法)丈攒。

靜態(tài)使用Fragment

這是使用Fragment最簡(jiǎn)單的一種方式,把Fragment當(dāng)成普通的控件,直接寫在Activity的布局中巡验。步驟:

1际插、 繼承Fragment,重寫onCreateView方法显设,填入布局
2框弛、在Activity中聲明此Fragment,就相當(dāng)普通控件一樣

下面展示一個(gè)例子(將Fragment作為Activity的布局)
Fragment的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center"
        android:text="使用Fragment做主面板"
        android:textSize="20sp"
        android:textStyle="bold" />
 
</LinearLayout>

類直接繼承Fragment

public class ContentFragment extends Fragment{
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState){
        return inflater.inflate(R.layout.fragment_content, container, false);
    }
}

MainActivity

public class MainActivity extends Activity{
 
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
    }
}

Activity布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <fragment
        android:id="@+id/id_fragment_title"
        android:name="com.application.demo.TitleFragment"
        android:layout_width="match_parent"
        android:layout_height="45dp" />
 
    <fragment
        android:layout_below="@id/id_fragment_title"
        android:id="@+id/id_fragment_content"
        android:name="com.application.demo.ContentFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

就是把Fragment當(dāng)成普通控件一樣在Activity中聲明捕捂,然后所有事件都交由Fragment處理瑟枫。

動(dòng)態(tài)使用Fragment

包括動(dòng)態(tài)添加、更新指攒、刪除Fragment

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <include
        android:id="@+id/id_ly_bottombar"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:layout_alignParentBottom="true"
        layout="@layout/bottombar" />
 
    <FrameLayout
        android:id="@+id/id_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/id_ly_bottombar" />
 
</RelativeLayout>

MainActivity代碼

public class MainActivity extends Activity implements OnClickListener{
    private LinearLayout mTabWeixin;
    private LinearLayout mTabFriend;
 
    private ContentFragment mWeixin;
    private FriendFragment mFriend;
 
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
 
        // 初始化控件和聲明事件
        mTabWeixin = (LinearLayout) findViewById(R.id.tab_bottom_weixin);
        mTabFriend = (LinearLayout) findViewById(R.id.tab_bottom_friend);
        mTabWeixin.setOnClickListener(this);
        mTabFriend.setOnClickListener(this);
 
        // 設(shè)置默認(rèn)的Fragment
        setDefaultFragment();
    }
 
    private void setDefaultFragment(){
        SupportFragmentManager fm = getSupportFragmentManager();
        FragmentTransaction transaction = fm.beginTransaction();
        mWeixin = new ContentFragment();
        transaction.replace(R.id.id_content, mWeixin);
        transaction.commit();
    }
 
    @Override
    public void onClick(View v){
        SupportFragmentManager fm = getSupportFragmentManager();
        // 開啟Fragment事務(wù)
        FragmentTransaction transaction = fm.beginTransaction();
 
        switch (v.getId()){
        case R.id.tab_bottom_weixin:
            if (mWeixin == null){
                mWeixin = new ContentFragment();
            }
            // 使用當(dāng)前Fragment的布局替代id_content的控件
            transaction.replace(R.id.id_content, mWeixin);
            break;
        case R.id.tab_bottom_friend:
            if (mFriend == null){
                mFriend = new FriendFragment();
            }
            transaction.replace(R.id.id_content, mFriend);
            break;
        }
        // transaction.addToBackStack();
        // 事務(wù)提交
        transaction.commit();
    }
}

Fragment常用api:

  • 獲取FragmentManager的方式(v4包)慷妙,getSupportFragmentManager()
  • 主要的操作方法都是FragmentTransaction的方法
    FragmentTransaction transaction = fm.benginTransatcion();//開啟一個(gè)事務(wù)
  • transaction.add()允悦;//往Activity中添加一個(gè)Fragment
  • transaction.remove()膝擂;//從Activity中移除一個(gè)Fragment,如果被移除的Fragment沒有添加到回退棧(回退棧后面會(huì)詳細(xì)說)隙弛,這個(gè)Fragment實(shí)例將會(huì)被銷毀
  • transaction.replace()架馋;//使用另一個(gè)Fragment替換當(dāng)前的,實(shí)際上就是remove()然后add()的合體
  • transaction.hide()全闷;//隱藏當(dāng)前的Fragment叉寂,僅僅是設(shè)為不可見,并不會(huì)銷毀
  • transaction.show()总珠; //顯示之前隱藏的Fragment
  • detach()办绝;//會(huì)將view從UI中移除,和remove()不同,此時(shí)fragment的狀態(tài)依然由FragmentManager維護(hù)
  • attach();//重建view視圖姚淆,附加到UI上并顯示
  • transatcion.commit(); //提交一個(gè)事務(wù)
注意:常用Fragment的哥們孕蝉,可能會(huì)經(jīng)常遇到這樣Activity狀態(tài)不一致:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
    at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
    at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
    at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
該異常出現(xiàn)的原因是:commit()在onSaveInstanceState()后調(diào)用。首先腌逢,onSaveInstanceState()在onPause()之后降淮,onStop()之前調(diào)用。onRestoreInstanceState()在onStart()之后搏讶,onResume()之前佳鳖,所以commit方法一定要在Activity.onSaveInstance()之前調(diào)用

為了更好的使用Fragment,一定要清楚這些方法媒惕,哪個(gè)會(huì)銷毀視圖系吩,哪個(gè)會(huì)銷毀實(shí)例,哪個(gè)僅僅只是隱藏妒蔚。

比如:

  • 我在FragmentA中的EditText填了一些數(shù)據(jù)穿挨,當(dāng)切換到FragmentB時(shí)月弛,如果希望回到A還能看到數(shù)據(jù),則適合你的就是hide和show科盛;也就是說帽衙,希望保留用戶操作的面板,你可以使用hide和show贞绵,當(dāng)然了不要使勁在那new實(shí)例厉萝,進(jìn)行下非null判斷。
  • 我不希望保留用戶操作榨崩,你可以使用remove()谴垫,然后add();或者使用replace()這個(gè)和remove,add是相同的效果
  • remove和detach有一點(diǎn)細(xì)微的區(qū)別母蛛,在不考慮回退棧的情況下弹渔,remove會(huì)銷毀整個(gè)Fragment實(shí)例,點(diǎn)擊back鍵后會(huì)銷毀Activity溯祸,而detach則只是銷毀其視圖結(jié)構(gòu)肢专,實(shí)例并不會(huì)被銷毀。那么二者怎么取舍使用呢焦辅?如果你的當(dāng)前Activity一直存在博杖,那么在不希望保留用戶操作的時(shí)候,你可以優(yōu)先使用detach筷登!

回退棧理解

類似與Android系統(tǒng)為Activity維護(hù)一個(gè)任務(wù)棧剃根,我們也可以通過Activity維護(hù)一個(gè)回退棧來保存每次Fragment事務(wù)發(fā)生的變化。如果你將Fragment任務(wù)添加到回退棧前方,當(dāng)用戶點(diǎn)擊后退按鈕時(shí)狈醉,將看到上一次的保存的Fragment。一旦Fragment完全從后退棧中彈出惠险,用戶再次點(diǎn)擊后退鍵苗傅,則退出當(dāng)前Activity。

如何添加一個(gè)Fragment事務(wù)到回退棧:
  • 事務(wù)添加這個(gè)方法 addToBackStack(String sr)

下面通過例子說明一下班巩,功能如下:共有三個(gè)Fragment:F1, F2, F3渣慕,F(xiàn)1在初始化時(shí)就加入Activity,點(diǎn)擊F1中的按鈕跳轉(zhuǎn)到F2抱慌,點(diǎn)擊F2的按鈕跳轉(zhuǎn)到F3逊桦,點(diǎn)擊F3的按鈕回退到F1。
1抑进、在Activity的onCreate()中强经,將F1加入Activity中,并將F1添加到回退棧:

getSupportFragmentManager().beginTransaction()
    .add(R.id.container, f1, "f1")
    .addToBackStack(Fragment1.class.getSimpleName())
    .commit();

F1按鈕的onClick()內(nèi)容如下寺渗,將F2添加到回退棧:

getFragmentManager().beginTransaction()
    .replace(R.id.container, f2, "f2")
    .addToBackStack(Fragment2.class.getSimpleName())
    .commit();

F2按鈕的onClick()如下匿情,將F3添加到回退棧:

getFragmentManager().beginTransaction()
    .replace(R.id.container, f3, "f3")
    .addToBackStack(Fragment3.class.getSimpleName())
    .commit();

F3按鈕的onClick()如下:

getFragmentManager().popBackStack(Fragment2.class.getSimpleName(),
             FragmentManager.POP_BACK_STACK_INCLUSIVE);

這樣就回退到F1了兰迫,上面出現(xiàn)了一個(gè)popBackStack,這個(gè)是干什么用的呢码秉?下面來說一下:
1、addToBackStack()是加入回退棧鸡号,那么與之對(duì)應(yīng)的是什么呢转砖?就是上面提到的popBackStack()。他有幾種變化:

  • popBackStack():將回退棧的棧頂彈出鲸伴,并回退該事務(wù)府蔗。
  • popBackStack(String name, int flag):name為addToBackStack(String name)的參數(shù),通過name能找到回退棧的特定元素汞窗,flag可以為0或者FragmentManager.POP_BACK_STACK_INCLUSIVE姓赤,0表示只彈出該元素以上的所有元素,POP_BACK_STACK_INCLUSIVE表示彈出包含該元素及以上的所有元素仲吏。這里說的彈出所有元素包含回退這些事務(wù)不铆。
  • popBackStack()是異步執(zhí)行的,是丟到主線程的MessageQueue執(zhí)行裹唆,popBackStackImmediate()是同步版本誓斥。

通過上面的一些例子我們大致對(duì)這個(gè)回退棧有了一些了解。

Fragment與Activity通信

因?yàn)閒ragment是依賴于Activity存在的许帐,通信并不復(fù)雜劳坑,大概歸納為:

1、如果你Activity中包含自己管理的Fragment的引用成畦,可以通過引用直接訪問所有的Fragment的public方法
2距芬、如果Activity中未保存任何Fragment的引用,那么沒關(guān)系循帐,每個(gè)Fragment都有一個(gè)唯一的TAG或者ID,可以通過getFragmentManager.findFragmentByTag()或者findFragmentById()獲得任何Fragment實(shí)例框仔,然后進(jìn)行操作。
3拄养、在Fragment中可以通過getActivity得到當(dāng)前綁定的Activity的實(shí)例存和,然后進(jìn)行操作。

*Fragment與Activity通信最佳實(shí)踐

因?yàn)橐紤]Fragment的重復(fù)使用衷旅,所以必須降低Fragment與Activity的耦合捐腿,而且Fragment更不應(yīng)該直接操作別的Fragment,畢竟Fragment操作應(yīng)該由它的管理者Activity來決定柿顶。
下面通過兩種方式實(shí)現(xiàn)fragment和Activity通信方式:
1茄袖、先看FragmentOne代碼:

public class FragmentOne extends Fragment implements OnClickListener{
    private Button mBtn;
 
    /**
     * 設(shè)置按鈕點(diǎn)擊的回調(diào)
     */
    public interface FOneBtnClickListener{
        void onFOneBtnClick();
    }
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState){
        View view = inflater.inflate(R.layout.fragment_one, container, false);
        mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
        mBtn.setOnClickListener(this);
        return view;
    }
 
    /**
     * 交給宿主Activity處理,如果它希望處理
     */
    @Override
    public void onClick(View v){
        if (getActivity() instanceof FOneBtnClickListener){
            ((FOneBtnClickListener) getActivity()).onFOneBtnClick();
        }
    }
 
}

可以看到現(xiàn)在的FragmentOne不和任何Activity耦合嘁锯,任何Activity都可以使用宪祥;并且我們聲明了一個(gè)接口聂薪,來回調(diào)其點(diǎn)擊事件,想要管理其點(diǎn)擊事件的Activity實(shí)現(xiàn)此接口就即可蝗羊〔匕模可以看到我們?cè)趏nClick中首先判斷了當(dāng)前綁定的Activity是否實(shí)現(xiàn)了該接口,如果實(shí)現(xiàn)了則調(diào)用耀找。
2翔悠、再看FragmentTwo:

public class FragmentTwo extends Fragment implements OnClickListener{
    
    private Button mBtn ;
    
    private FTwoBtnClickListener fTwoBtnClickListener ;
    
      public interface FTwoBtnClickListener{
        void onFTwoBtnClick();
    }
    //設(shè)置回調(diào)方法
    public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener){
        this.fTwoBtnClickListener = fTwoBtnClickListener;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState){
        View view = inflater.inflate(R.layout.fragment_two, container, false);
        mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
        mBtn.setOnClickListener(this);
        return view ; 
    }

    @Override
    public void onClick(View v){
        if(fTwoBtnClickListener != null){
            fTwoBtnClickListener.onFTwoBtnClick();
        }
    }
 
}

與FragmentOne很類似,但是我們提供了setListener這樣的方法野芒,意味著Activity不僅需要實(shí)現(xiàn)該接口蓄愁,還必須顯示調(diào)用mFTwo.setfTwoBtnClickListener(this)。
3狞悲、最后看MainActivity

public class MainActivity extends Activity implements FOneBtnClickListener,
        FTwoBtnClickListener{
 
    private FragmentOne mFOne;
    private FragmentTwo mFTwo;
    private FragmentThree mFThree;
 
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        mFOne = new FragmentOne();
        FragmentManager fm = getFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.add(R.id.id_content, mFOne, "ONE");
        tx.commit();
    }
 
    /**
     * FragmentOne 按鈕點(diǎn)擊時(shí)的回調(diào)
     */
    @Override
    public void onFOneBtnClick(){
        if (mFTwo == null){
            mFTwo = new FragmentTwo();
            mFTwo.setfTwoBtnClickListener(this);
        }
        FragmentManager fm = getFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.replace(R.id.id_content, mFTwo, "TWO");
        tx.addToBackStack(null);
        tx.commit();
    }
 
    /**
     * FragmentTwo 按鈕點(diǎn)擊時(shí)的回調(diào)
     */
    @Override
    public void onFTwoBtnClick(){
        if (mFThree == null){
            mFThree = new FragmentThree();
        }
        FragmentManager fm = getFragmentManager();
        FragmentTransaction tx = fm.beginTransaction();
        tx.hide(mFTwo);
        tx.add(R.id.id_content, mFThree, "THREE");
        // tx.replace(R.id.id_content, fThree, "THREE");
        tx.addToBackStack(null);
        tx.commit();
    }
}

上面兩種方法都能實(shí)現(xiàn)操作撮抓,到這里就結(jié)束了。

好文閱讀
https://mp.weixin.qq.com/s/dUuGSVhWinAnN9uMiBaXgw
https://blog.csdn.net/lmj623565791/article/details/37970961

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末摇锋,一起剝皮案震驚了整個(gè)濱河市丹拯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荸恕,老刑警劉巖咽笼,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異戚炫,居然都是意外死亡剑刑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門双肤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來施掏,“玉大人,你說我怎么就攤上這事茅糜∑甙牛” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵蔑赘,是天一觀的道長(zhǎng)狸驳。 經(jīng)常有香客問我,道長(zhǎng)缩赛,這世上最難降的妖魔是什么耙箍? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮酥馍,結(jié)果婚禮上辩昆,老公的妹妹穿的比我還像新娘。我一直安慰自己旨袒,他們只是感情好汁针,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布术辐。 她就那樣靜靜地躺著,像睡著了一般施无。 火紅的嫁衣襯著肌膚如雪辉词。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天猾骡,我揣著相機(jī)與錄音瑞躺,去河邊找鬼。 笑死卓练,一個(gè)胖子當(dāng)著我的面吹牛隘蝎,可吹牛的內(nèi)容都是我干的购啄。 我是一名探鬼主播襟企,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼狮含!你這毒婦竟也來了顽悼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤几迄,失蹤者是張志新(化名)和其女友劉穎蔚龙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體映胁,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡木羹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了解孙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坑填。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弛姜,靈堂內(nèi)的尸體忽然破棺而出脐瑰,到底是詐尸還是另有隱情,我是刑警寧澤廷臼,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布苍在,位于F島的核電站,受9級(jí)特大地震影響荠商,放射性物質(zhì)發(fā)生泄漏寂恬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一莱没、第九天 我趴在偏房一處隱蔽的房頂上張望掠剑。 院中可真熱鬧,春花似錦郊愧、人聲如沸朴译。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)眠寿。三九已至躬翁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盯拱,已是汗流浹背盒发。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狡逢,地道東北人宁舰。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像奢浑,于是被迫代替她去往敵國(guó)和親蛮艰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359