1魏身、概述
首先我們簡(jiǎn)單回顧一下惊橱,相信大家對(duì)Fragment的都不陌生,對(duì)于Fragment的使用箭昵,一方面Activity需要在布局中為Fragment安排位置税朴,另一方面需要管理好Fragment的生命周期。Activity中有個(gè)FragmentManager家制,其內(nèi)部維護(hù)fragment隊(duì)列掉房,以及fragment事務(wù)的回退棧。
一般情況下慰丛,我們?cè)贏ctivity里面會(huì)這么添加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();
}
}
}
針對(duì)上面代碼卓囚,問兩個(gè)問題:
1、為什么需要判null呢诅病?
主要是因?yàn)槟囊冢?dāng)Activity因?yàn)榕渲冒l(fā)生改變(屏幕旋轉(zhuǎn))或者內(nèi)存不足被系統(tǒng)殺死粥烁,造成重新創(chuàng)建時(shí),我們的fragment會(huì)被保存下來蝇棉,但是會(huì)創(chuàng)建新的FragmentManager讨阻,新的FragmentManager會(huì)首先會(huì)去獲取保存下來的fragment隊(duì)列,重建fragment隊(duì)列篡殷,從而恢復(fù)之前的狀態(tài)钝吮。
2、add(R.id.id_fragment_container,mContentFragment)中的布局的id有何作用板辽?
一方面呢奇瘦,是告知FragmentManager,此fragment的位置劲弦;另一方面是此fragment的唯一標(biāo)識(shí)耳标;就像我們上面通過fm.findFragmentById(R.id.id_fragment_container)查找~~
好了,簡(jiǎn)單回顧了一下基本用法邑跪,具體的還請(qǐng)參考上面的博客或者其他資料次坡,接下來,介紹一些使用的意見~~
2画畅、Fragment Arguments
下面描述一個(gè)簡(jiǎn)單的場(chǎng)景砸琅,比如我們某個(gè)按鈕觸發(fā)Activity跳轉(zhuǎn),需要通過Intent傳遞參數(shù)到目標(biāo)Activity的Fragment中轴踱,那么此Fragment如何獲取當(dāng)前的Intent的值呢明棍?
有哥們會(huì)說,這個(gè)簡(jiǎn)單寇僧?看我的代碼(問題代碼):
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)了细办,但是呢橙凳?存在一個(gè)大問題:我們用Fragment的一個(gè)很大的原因,就是為了復(fù)用笑撞。你這么寫岛啸,相當(dāng)于這個(gè)Fragment已經(jīng)完全和當(dāng)前這個(gè)宿主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
依舊是一個(gè)簡(jiǎn)單的場(chǎng)景:兩個(gè)Fragment从诲,一個(gè)展示文章列表的Fragment(叫做ListTitleFragment)左痢,一個(gè)顯示詳細(xì)信息的Fragment(叫做:ContentFragment)靡羡,當(dāng)然了,這兩個(gè)Fragment都有其宿主Activity俊性。
現(xiàn)在略步,我們點(diǎn)擊列表Fragment中的列表項(xiàng),傳入相應(yīng)的參數(shù)定页,去詳細(xì)信息的Fragment展示詳細(xì)的信息趟薄,在詳細(xì)信息頁面,用戶可以進(jìn)行點(diǎn)評(píng)典徊,當(dāng)用戶點(diǎn)擊back以后杭煎,我們以往點(diǎn)評(píng)結(jié)果顯示在列表的Fragment對(duì)于的列表項(xiàng)中;
也就是說卒落,我們點(diǎn)擊跳轉(zhuǎn)到對(duì)應(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();
}
}
}
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;
}
}
貼出了兩個(gè)Fragment的代碼,可以看到我們?cè)贚istTitleFragment.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)行回顯导犹;為大家以后在遇到類似問題時(shí)唱凯,提供了解決方案;也說明了一個(gè)問題:fragment能夠從Activity中接收返回結(jié)果谎痢,但是其自設(shè)無法產(chǎn)生返回結(jié)果磕昼,只有Activity擁有返回結(jié)果。
接下來我要貼一下节猿,這兩個(gè)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)兩個(gè)Activity中的代碼極其的類似票从,且使用了同一個(gè)布局文件:
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峰鄙,會(huì)發(fā)現(xiàn)大量類似的代碼,那么我們就可以抽象一個(gè)Activity出來太雨,托管我們的Single Fragment吟榴。
詳細(xì)見下一節(jié)。
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();
}
}
}
那么吩翻,有了這個(gè)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;
}
}
是不是簡(jiǎn)潔很多锥咸,相信優(yōu)先使用Fragment的項(xiàng)目狭瞎,類似的Activity非常多,使用SingleFragmentActivity來簡(jiǎn)化你的代碼吧~~
好了搏予,此代碼是來自文章開始推薦的書中的熊锭,再次推薦一下~~。
5雪侥、FragmentPagerAdapter與FragmentStatePagerAdapter
相信這兩個(gè)PagerAdapter的子類碗殷,大家都不陌生吧~自從Fragment問世,使用ViewPager再結(jié)合上面任何一個(gè)實(shí)例的制作APP主頁的案例特別多~~
那么這兩個(gè)類有何區(qū)別呢速缨?
主要區(qū)別就在與對(duì)于fragment是否銷毀锌妻,下面細(xì)說:
FragmentPagerAdapter:對(duì)于不再需要的fragment,選擇調(diào)用detach方法,僅銷毀視圖,并不會(huì)銷毀fragment實(shí)例厅瞎。
FragmentStatePagerAdapter:會(huì)銷毀不再需要的fragment震叙,當(dāng)當(dāng)前事務(wù)提交以后,會(huì)徹底的將fragmeng從當(dāng)前Activity的FragmentManager中移除,state標(biāo)明,銷毀時(shí),會(huì)將其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)存唤冈,但是銷毀新建也是需要時(shí)間的。一般情況下银伟,如果你是制作主頁面你虹,就3、4個(gè)Tab彤避,那么可以選擇使用FragmentPagerAdapter傅物,如果你是用于ViewPager展示數(shù)量特別多的條目時(shí),那么建議使用FragmentStatePagerAdapter琉预。
篇幅原因董饰,具體的案例就不寫了,大家自行測(cè)試圆米。
6卒暂、Fragment間的數(shù)據(jù)傳遞
上面3中,我們展示了榨咐,一般的兩個(gè)Fragment間的數(shù)據(jù)傳遞介却。
那么還有一種比較特殊的情況谴供,就是兩個(gè)Fragment在同一個(gè)Activity中:例如块茁,點(diǎn)擊當(dāng)前Fragment中按鈕,彈出一個(gè)對(duì)話框(DialogFragment)桂肌,在對(duì)話框中的操作需要返回給觸發(fā)的Fragment中数焊,那么如何數(shù)據(jù)傳遞呢?對(duì)于對(duì)話框的使用推薦:Android 官方推薦 : DialogFragment 創(chuàng)建對(duì)話框
我們繼續(xù)修改我們的代碼:現(xiàn)在是ListTitleFragment 崎场, ContentFragment 佩耳, 添加一個(gè)對(duì)話框:EvaluateDialog,用戶點(diǎn)擊ContentFragment 內(nèi)容時(shí)彈出一個(gè)評(píng)價(jià)列表谭跨,用戶選擇評(píng)價(jià)干厚。
現(xiàn)在我們的關(guān)注點(diǎn)在于:ContentFragment中如何優(yōu)雅的拿到EvaluateDialog中返回的評(píng)價(jià):
記住我們?cè)谝粋€(gè)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)代碼谆扎,注意看挂捅,我們?cè)趏nCreateView中為textview添加了click事件,用于彈出我們的dialog堂湖,注意一行代碼:
dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);
我們調(diào)用了Fragment.setTargetFragment 闲先,這個(gè)方法状土,一般就是用于當(dāng)前fragment由別的fragment啟動(dòng),在完成操作后返回?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ù)慰照,最后手動(dòng)調(diào)用onActivityResult進(jìn)行返回?cái)?shù)據(jù)~~
最后我們?cè)贑ontentFragment的onActivityResult接收即可。