Android中的Fragment類使用進(jìn)階
0、回顧
Fragment 代表 Activity 當(dāng)中的一項(xiàng)操作或一部分用戶界面。 一個 Activity 中的多個 Fragment 可以組合在一起,形成一個多部分拼接而成的用戶界面組件,并可在多個 Activity 中復(fù)用。一個 Fragment 可被視為 Activity 中一個模塊化的部分, 它擁有自己的生命周期,并接收自己的輸入事件,在 Activity 運(yùn)行過程中可以隨時添加或移除它 (有點(diǎn)類似“子 Activity”,可在不同的 Activity 中重用)。
Fragment 必須嵌入某個 Activity 中,其生命周期直接受到宿主 Activity 生命周期的影響。 例如,當(dāng) Activity 被暫停(Paused)時,其內(nèi)部所有的 Fragment 也都會暫停。 而當(dāng) Activity 被銷毀時,它的 Fragment 也都會被銷毀。 不過,在 Activity 運(yùn)行期間(生命周期狀態(tài)處于 恢復(fù)(Resumed) 狀態(tài)時),每一個 Fragment 都可以被獨(dú)立地操作,比如添加或移除。 在執(zhí)行這些操作事務(wù)時,還可以將它們加入該 Activity 的回退棧(Back Stack)中 — Activity 回退棧的每個入口就是一條操作過的 Fragment 事務(wù)記錄。 回退堆棧使得用戶可以通過按下 回退(Back) 鍵來回退 Fragment 事務(wù)(后退一步)。
當(dāng)把 Fragment 加入 Activity 布局(Layout) 后,它位于 Activity View 層次架構(gòu)(Hierarchy)的某個 ViewGroup 里,且擁有自己的 View 布局定義。 通過在 Activity 的 Layout 文件中聲明 <fragment> 元素,可以在 Layout 中添加一個 Fragment。 也可以用程序代碼在已有的 ViewGroup 中添加一個 Fragment。 不過, Fragment 并不一定非要是 Activity 布局的一部分,它也可以沒有自己的界面,而是用作 Activity 的非可視化工作組件。
1、概述
相信大家對Fragment的都不陌生,對于Fragment的使用,一方面Activity需要在布局中為Fragment安排位置,另一方面需要管理好Fragment的生命周期。Activity中有個FragmentManager,其內(nèi)部維護(hù)fragment隊(duì)列,以及fragment事務(wù)的回退棧。
一般情況下,我們在Activity里面會這么添加Fragment:
public class MainActivity extends FragmentActivity { private ContentFragment mContentFragment ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager fm = getSupportFragmentManager(); mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container); if(mContentFragment == null ) { mContentFragment = new ContentFragment(); fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit(); } } }
針對上面代碼,問兩個問題:
(1)為什么需要判null呢?
主要是因?yàn)?,?dāng)Activity因?yàn)榕渲冒l(fā)生改變(屏幕旋轉(zhuǎn))或者內(nèi)存不足被系統(tǒng)殺死,造成重新創(chuàng)建時,我們的fragment會被保存下來,但是會創(chuàng)建新的FragmentManager,新的FragmentManager會首先會去獲取保存下來的fragment隊(duì)列,重建fragment隊(duì)列,從而恢復(fù)之前的狀態(tài)。
(2)add(R.id.id_fragment_container,mContentFragment)中的布局的id有何作用?
一方面呢,是告知FragmentManager,此fragment的位置;另一方面是此fragment的唯一標(biāo)識;就像我們上面通過fm.findFragmentById(R.id.id_fragment_container)查找~~
好了,簡單回顧了一下基本用法,具體的還請參考上面的博客或者其他資料,接下來,介紹一些使用的意見~~
2、Fragment Arguments
下面描述一個簡單的場景,比如我們某個按鈕觸發(fā)Activity跳轉(zhuǎn),需要通過Intent傳遞參數(shù)到目標(biāo)Activity的Fragment中,那么此Fragment如何獲取當(dāng)前的Intent的值呢?
有哥們會說,這個簡單?看我的代碼(問題代碼):
public class ContentFragment extends Fragment { private String mArgument ; public static final String ARGUMENT ="argument"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mArgument = getActivity().getIntent().getStringExtra(ARGUMENT); }
我們直接在Fragment的onCreate中,拿到宿主Activty,宿主Activity中肯定能通過getIntent拿到Intent,然后通過get方法,隨意拿參數(shù)~~
這么寫,功能上是實(shí)現(xiàn)了,但是呢?存在一個大問題:我們用Fragment的一個很大的原因,就是為了復(fù)用。你這么寫,相當(dāng)于這個Fragment已經(jīng)完全和當(dāng)前這個宿主Activity綁定了,復(fù)用直接廢了~~~所以呢?我們換種方式,推薦使用arguments來創(chuàng)建Fragment。
public class ContentFragment extends Fragment { private String mArgument; public static final String ARGUMENT = "argument"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // mArgument = getActivity().getIntent().getStringExtra(ARGUMENT); Bundle bundle = getArguments(); if (bundle != null) mArgument = bundle.getString(ARGUMENT); } /** * 傳入需要的參數(shù),設(shè)置給arguments * @param argument * @return */ public static ContentFragment newInstance(String argument) { Bundle bundle = new Bundle(); bundle.putString(ARGUMENT, argument); ContentFragment contentFragment = new ContentFragment(); contentFragment.setArguments(bundle); return contentFragment; }
給Fragment添加newInstance方法,將需要的參數(shù)傳入,設(shè)置到bundle中,然后setArguments(bundle),最后在onCreate中進(jìn)行獲??;
這樣就完成了Fragment和Activity間的解耦。當(dāng)然了這里需要注意:
setArguments方法必須在fragment創(chuàng)建以后,添加給Activity前完成。千萬不要,首先調(diào)用了add,然后設(shè)置arguments。
3、Fragment的startActivityForResult
依舊是一個簡單的場景:兩個Fragment,一個展示文章列表的Fragment(叫做ListTitleFragment),一個顯示詳細(xì)信息的Fragment(叫做:ContentFragment),當(dāng)然了,這兩個Fragment都有其宿主Activity。
現(xiàn)在,我們點(diǎn)擊列表Fragment中的列表項(xiàng),傳入相應(yīng)的參數(shù),去詳細(xì)信息的Fragment展示詳細(xì)的信息,在詳細(xì)信息頁面,用戶可以進(jìn)行點(diǎn)評,當(dāng)用戶點(diǎn)擊back以后,我們以往點(diǎn)評結(jié)果顯示在列表的Fragment對于的列表項(xiàng)中;
也就是說,我們點(diǎn)擊跳轉(zhuǎn)到對應(yīng)Activity的Fragment中,并且希望它能夠返回參數(shù),那么我們肯定是使用Fragment.startActivityForResult ;
在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是呢,沒有setResult()方法,用于設(shè)置返回的intent,這樣我們就需要通過調(diào)用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);。
詳細(xì)代碼:
ListTitleFragment
public class ListTitleFragment extends ListFragment { public static final int REQUEST_DETAIL = 0x110; private List<String> mTitles = Arrays.asList("Hello", "World", "Android"); private int mCurrentPos ; private ArrayAdapter<String> mAdapter ; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setListAdapter(mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mTitles)); } @Override public void onListItemClick(ListView l, View v, int position, long id) { mCurrentPos = position ; Intent intent = new Intent(getActivity(),ContentActivity.class); intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position)); startActivityForResult(intent, REQUEST_DETAIL); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Log.e("TAG", "onActivityResult"); super.onActivityResult(requestCode, resultCode, data); if(requestCode == REQUEST_DETAIL) { mTitles.set(mCurrentPos, mTitles.get(mCurrentPos)+" -- "+data.getStringExtra(ContentFragment.RESPONSE)); mAdapter.notifyDataSetChanged(); } } }
ContentFragment
public class ContentFragment extends Fragment { private String mArgument; public static final String ARGUMENT = "argument"; public static final String RESPONSE = "response"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle bundle = getArguments(); if (bundle != null) { mArgument = bundle.getString(ARGUMENT); Intent intent = new Intent(); intent.putExtra(RESPONSE, "good"); getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent); } } public static ContentFragment newInstance(String argument) { Bundle bundle = new Bundle(); bundle.putString(ARGUMENT, argument); ContentFragment contentFragment = new ContentFragment(); contentFragment.setArguments(bundle); return contentFragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Random random = new Random(); TextView tv = new TextView(getActivity()); tv.setText(mArgument); tv.setGravity(Gravity.CENTER); tv.setBackgroundColor(Color.argb(random.nextInt(100), random.nextInt(255), random.nextInt(255), random.nextInt(255))); return tv; } }
貼出了兩個Fragment的代碼,可以看到我們在ListTitleFragment.onListItemClick,使用startActivityForResult()跳轉(zhuǎn)到目標(biāo)Activity,在目標(biāo)Activity的Fragment(ContentFragment)中獲取參數(shù),然后調(diào)用getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);進(jìn)行設(shè)置返回的數(shù)據(jù);最后在ListTitleFragment.onActivityResult()拿到返回的數(shù)據(jù)進(jìn)行回顯;
為大家以后在遇到類似問題時,提供了解決方案;也說明了一個問題:fragment能夠從Activity中接收返回結(jié)果,但是其自設(shè)無法產(chǎn)生返回結(jié)果,只有Activity擁有返回結(jié)果。
接下來我要貼一下,這兩個Fragment的宿主Activity:
ListTitleActivity
public class ListTitleActivity extends FragmentActivity { private ListTitleFragment mListFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_fragment); FragmentManager fm = getSupportFragmentManager(); mListFragment = (ListTitleFragment) fm.findFragmentById(R.id.id_fragment_container); if(mListFragment == null ) { mListFragment = new ListTitleFragment(); fm.beginTransaction().add(R.id.id_fragment_container,mListFragment).commit(); } } }
ContentActivity:
public class ContentActivity extends FragmentActivity { private ContentFragment mContentFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_fragment); FragmentManager fm = getSupportFragmentManager(); mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container); if(mContentFragment == null ) { String title = getIntent().getStringExtra(ContentFragment.ARGUMENT); mContentFragment = ContentFragment.newInstance(title); fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit(); } } }
有沒有發(fā)現(xiàn)兩個Activity中的代碼極其的類似,且使用了同一個布局文件:
activity_single_fragment.xml
<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" android:id="@+id/id_fragment_container" > </RelativeLayout>
為什么要貼這Acticity的代碼呢?因?yàn)槲覀冺?xiàng)目中,如果原則上使用Fragment,會發(fā)現(xiàn)大量類似的代碼,那么我們就可以抽象一個Activity出來,托管我們的Single Fragment。
4、SingleFragmentActivity
于是抽象出來的Activity的代碼為:
package com.example.demo_zhy_23_fragments; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; public abstract class SingleFragmentActivity extends FragmentActivity { protected abstract Fragment createFragment(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_single_fragment); FragmentManager fm = getSupportFragmentManager(); Fragment fragment =fm.findFragmentById(R.id.id_fragment_container); if(fragment == null ) { fragment = createFragment() ; fm.beginTransaction().add(R.id.id_fragment_container,fragment).commit(); } } }
那么,有了這個SingleFragmentActivity,我們的ContentActivity和ListTitleActivity也能大變身了~
package com.example.demo_zhy_23_fragments; import android.support.v4.app.Fragment; public class ContentActivity extends SingleFragmentActivity { private ContentFragment mContentFragment; @Override protected Fragment createFragment() { String title = getIntent().getStringExtra(ContentFragment.ARGUMENT); mContentFragment = ContentFragment.newInstance(title); return mContentFragment; } }
package com.example.demo_zhy_23_fragments; import android.support.v4.app.Fragment; public class ListTitleActivity extends SingleFragmentActivity { private ListTitleFragment mListFragment; @Override protected Fragment createFragment() { mListFragment = new ListTitleFragment(); return mListFragment; } }
是不是簡潔很多,相信優(yōu)先使用Fragment的項(xiàng)目,類似的Activity非常多,使用SingleFragmentActivity來簡化你的代碼吧~~
好了,此代碼是來自文章開始推薦的書中的,再次推薦一下~~。
5、FragmentPagerAdapter與FragmentStatePagerAdapter
相信這兩個PagerAdapter的子類,大家都不陌生吧~~自從Fragment問世,使用ViewPager再結(jié)合上面任何一個實(shí)例的制作APP主頁的案例特別多~~~
那么這兩個類有何區(qū)別呢?
主要區(qū)別就在與對于fragment是否銷毀,下面細(xì)說:
(1)FragmentPagerAdapter:對于不再需要的fragment,選擇調(diào)用detach方法,僅銷毀視圖,并不會銷毀fragment實(shí)例。
(2)FragmentStatePagerAdapter:會銷毀不再需要的fragment,當(dāng)當(dāng)前事務(wù)提交以后,會徹底的將fragmeng從當(dāng)前Activity的FragmentManager中移除,state標(biāo)明,銷毀時,會將其onSaveInstanceState(Bundle outState)中的bundle信息保存下來,當(dāng)用戶切換回來,可以通過該bundle恢復(fù)生成新的fragment,也就是說,你可以在onSaveInstanceState(Bundle outState)方法中保存一些數(shù)據(jù),在onCreate中進(jìn)行恢復(fù)創(chuàng)建。
如上所說,使用FragmentStatePagerAdapter當(dāng)然更省內(nèi)存,但是銷毀新建也是需要時間的。一般情況下,如果你是制作主頁面,就3、4個Tab,那么可以選擇使用FragmentPagerAdapter,如果你是用于ViewPager展示數(shù)量特別多的條目時,那么建議使用FragmentStatePagerAdapter。
篇幅原因,具體的案例就不寫了,大家自行測試。
6、Fragment間的數(shù)據(jù)傳遞
上面3中,我們展示了,一般的兩個Fragment間的數(shù)據(jù)傳遞。
那么還有一種比較特殊的情況,就是兩個Fragment在同一個Activity中:例如,點(diǎn)擊當(dāng)前Fragment中按鈕,彈出一個對話框(DialogFragment),在對話框中的操作需要返回給觸發(fā)的Fragment中,那么如何數(shù)據(jù)傳遞呢?對于對話框的使用推薦:Android 官方推薦 : DialogFragment 創(chuàng)建對話框
我們繼續(xù)修改我們的代碼:現(xiàn)在是ListTitleFragment , ContentFragment , 添加一個對話框:EvaluateDialog,用戶點(diǎn)擊ContentFragment 內(nèi)容時彈出一個評價列表,用戶選擇評價。
現(xiàn)在我們的關(guān)注點(diǎn)在于:ContentFragment中如何優(yōu)雅的拿到EvaluateDialog中返回的評價:
記住我們在一個Activity中,那么肯定不是使用startActivityForResult;但是我們返回的數(shù)據(jù),依然在onActivityResult中進(jìn)行接收。
好了看代碼:
ContentFragment
public class ContentFragment extends Fragment { private String mArgument; public static final String ARGUMENT = "argument"; public static final String RESPONSE = "response"; public static final String EVALUATE_DIALOG = "evaluate_dialog"; public static final int REQUEST_EVALUATE = 0X110; //... @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Random random = new Random(); TextView tv = new TextView(getActivity()); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); tv.setLayoutParams(params); tv.setText(mArgument); tv.setGravity(Gravity.CENTER); tv.setBackgroundColor(Color.argb(random.nextInt(100), random.nextInt(255), random.nextInt(255), random.nextInt(255))); // set click tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { EvaluateDialog dialog = new EvaluateDialog(); //注意setTargetFragment dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE); dialog.show(getFragmentManager(), EVALUATE_DIALOG); } }); return tv; } //接收返回回來的數(shù)據(jù) @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_EVALUATE) { String evaluate = data .getStringExtra(EvaluateDialog.RESPONSE_EVALUATE); Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.putExtra(RESPONSE, evaluate); getActivity().setResult(Activity.REQUEST_OK, intent); } } }
刪除了一些無關(guān)代碼,注意看,我們在onCreateView中為textview添加了click事件,用于彈出我們的dialog,注意一行代碼:
dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);
我們調(diào)用了Fragment.setTargetFragment ,這個方法,一般就是用于當(dāng)前fragment由別的fragment啟動,在完成操作后返回?cái)?shù)據(jù)的,符合我們的需求吧~~~注意,這句很重要。
接下來看EvaluateDialog代碼:
package com.example.demo_zhy_23_fragments; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.DialogFragment; public class EvaluateDialog extends DialogFragment { private String[] mEvaluteVals = new String[] { "GOOD", "BAD", "NORMAL" }; public static final String RESPONSE_EVALUATE = "response_evaluate"; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Evaluate :").setItems(mEvaluteVals, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setResult(which); } }); return builder.create(); } // 設(shè)置返回?cái)?shù)據(jù) protected void setResult(int which) { // 判斷是否設(shè)置了targetFragment if (getTargetFragment() == null) return; Intent intent = new Intent(); intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]); getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE, Activity.RESULT_OK, intent); } }
重點(diǎn)就是看點(diǎn)擊后的setResult了,我們首先判斷是否設(shè)置了targetFragment,如果設(shè)置了,意味我們要返回一些數(shù)據(jù)到targetFragment。
我們創(chuàng)建intent封裝好需要傳遞數(shù)據(jù),最后手動調(diào)用onActivityResult進(jìn)行返回?cái)?shù)據(jù)~~
最后我們在ContentFragment的onActivityResult接收即可。
ok,終于把這些tips貫穿到一起了,到此我們的Fragment的一些建議的用法就結(jié)束了~~~那么,最后提供下源碼,也順便貼個效果圖:
- Android中ViewPager和Fragment的使用
- Android 開發(fā)之BottomBar+ViewPager+Fragment實(shí)現(xiàn)炫酷的底部導(dǎo)航效果
- Android應(yīng)用中使用Fragment組件的一些問題及解決方案總結(jié)
- 詳解Android應(yīng)用中DialogFragment的基本用法
- Android App中使用ListFragment的實(shí)例教程
- Android中Fragment子類及其PreferenceFragment的創(chuàng)建過程演示
- 實(shí)例探究Android開發(fā)中Fragment狀態(tài)的保存與恢復(fù)方法
- Android程序開發(fā)之Fragment實(shí)現(xiàn)底部導(dǎo)航欄實(shí)例代碼
- Android 動態(tài)添加Fragment的實(shí)例代碼
相關(guān)文章
Android開發(fā)實(shí)現(xiàn)圖片圓角的方法
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)圖片圓角的方法,涉及Android針對圖形圖像的相關(guān)操作技巧,需要的朋友可以參考下2016-10-10淺析Android中build.gradle的實(shí)用技巧
這篇文章主要介紹了淺析Android中build.gradle的實(shí)用技巧,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-03-03實(shí)例講解Android Fragment的兩種使用方法
今天小編就為大家分享一篇關(guān)于實(shí)例講解Android Fragment的兩種使用方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03Android設(shè)計(jì)模式之適配器(Adapter)模式
這篇文章主要介紹了Android設(shè)計(jì)模式之適配器(Adapter)模式,以源碼解析的方式分析適配器模式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11Android不顯示開機(jī)向?qū)Ш烷_機(jī)氣泡問題
這篇文章主要介紹了Android不顯示開機(jī)向?qū)Ш烷_機(jī)氣泡問題,需要的朋友可以參考下2019-05-05Android實(shí)現(xiàn)選擇相冊圖片并顯示功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)選擇相冊圖片并顯示功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04Android開發(fā)系列二之窗口Activity的生命周期
這篇文章主要介紹了Android學(xué)習(xí)系列二之窗口Activity的生命周期的相關(guān)資料,需要的朋友可以參考下2016-05-05Android實(shí)現(xiàn)讀寫JSON數(shù)據(jù)的方法
這篇文章主要介紹了Android實(shí)現(xiàn)讀寫JSON數(shù)據(jù)的方法,以完整實(shí)例形式分析了Android解析及生成json數(shù)據(jù)的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10Android實(shí)現(xiàn)微信朋友圈評論EditText效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)微信朋友圈評論EditText效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-11-11