一 Activity中添加Fragment方式
(1)Xml佈局文件添加,如下
<fragment
android:id="@+id/fragment"
android:name="xxx.xxx.MyFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
(2)View中動態添加
Fragment fragment = getSupportFragmentManager().findFragmentByTag("tag");
if(fragment == null){
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
fragment = MyFragment.newInstance();
transaction.add(R.id.fragment_container, fragment, "tag");
transaction.commit();
}
或
Fragment fragment = null;
if(savedInstanceState == null){
fragment = MyFragment.newInstance();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.fragment_container, fragment, "tag");
transaction.commit();
}else{
fragment = getSupportFragmentManager().findFragmentByTag("tag");
}
之所以在添加Fragment前判斷是否有存在相同的Fragment,是因爲在內存不足或橫豎屏切換(如未在android:configChanges設置)時,會保存其內的Fragment狀態信息。當Activity被創建顯示時,Fragment會被自動恢復(恢復Fragment和狀態的代碼在Fragment Activity的onCreate方法中)。如果不判斷,則添加多個相同的Fragment。
在添加Fragment時,如果需要向其傳遞一些初始化的參數,則可使用Bundle來傳遞。在Fragment內使用getArguments()獲取參數。代碼如下:
//------ 傳遞參數 ------
Bundle data = new Bundle();
//除了基本數據類型的值,還可以傳遞序列化和實現Parcelable類型的數據
data.putString(key, value);
...
fragment.setArguments(data);
//------ 獲取參數 ------
Bundle data = getArguments();
if(data != null){
//獲取參數
String value = data.getString(key);
...
}
(3)使用TabLayout和ViewPager添加
在佈局文件添加:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="40dp" />
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_below="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
給ViewPager設置Fragment適配器和TabLayout綁定ViewPager:
TabLayout table_layout = (TabLayout) findViewById(R.id.tab_layout);
ViewPager view_pager = (ViewPager) findViewById(R.id.view_pager);
//設置自定義的Fragment適配器
view_pager.setAdapter(new MyFragAdapter(getSupportFragmentManager()));
//TabLayout綁定ViewPager
table_layout.setupWithViewPager(view_pager);
table_layout.setTabMode(TabLayout.MODE_FIXED);
自定義的Fragment適配器必須要繼承FragmentStatePagerAdapter或FragmentPagerAdapter,而這兩個類則繼承了android.support.v4.view.PagerAdapter。FragmentStatePagerAdapter與FragmentPagerAdapter區別則體現在Fragment的添加,移除和狀態保存這三個方面。FragmentStatePagerAdapter添加Fragment時會緩存到mFragments集合中,並恢復其狀態(如果有保存狀態),而當不可見時則會先使用Fragment.SavedState來保存其狀態信息,將其在mFragments的緩存置爲null,然後直接從FragmentManager中remove掉。而FragmentPagerAdapter直接使用FragmentManager管理Fragment緩存,當不可見時,只會調用detach將其與Activity分離。
FragmentStatePagerAdapter的instantiateItem和destroyItem方法代碼(使用…省略部分代碼):
@Override
public Object instantiateItem(ViewGroup container, int position) {
...
//本地緩存有(非FragmentManager緩存),則直接返回
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
...
//創建實例
Fragment fragment = getItem(position);
...
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
//恢復保存的狀態信息
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
//增加本地緩存
while (mFragments.size() <= position) {
mFragments.add(null);
}
...
//添加到本地緩存
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
...
//增加狀態緩存
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
//判斷是否保存Fragment狀態信息
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
//清除Fragment對象緩存
mFragments.set(position, null);
//從FragmentManager中移除
mCurTransaction.remove(fragment);
}
...
@Override
public Parcelable saveState() {
Bundle state = null;
//保存狀態信息
...
return state;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
//恢復狀態信息
...
}
}
FragmentPagerAdapter的instantiateItem和destroyItem方法代碼:
@Override
public Object instantiateItem(ViewGroup container, int position) {
...
final long itemId = getItemId(position);
String name = makeFragmentName(container.getId(), itemId);
//獲取FragmentManager緩存
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
//attach到當前Activity
mCurTransaction.attach(fragment);
} else {
//創建實例
fragment = getItem(position);
mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach((Fragment)object);
}
...
@Override
public Parcelable saveState() {
//默認未做狀態信息保存
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
//默認未恢復狀態信息
}
由這幾個方法對比可知:
FragmentStatePagerAdapter適用於管理大數量Fragemnt,如新聞頁面,根據不同的頻道顯示對應的新聞信息,佔用內存較少,保存狀態信息有利於再次快速回顯數據。
FragmentPagerAdapter使用少量的固定的Tab頁面,所有被顯示的Fragment都會保存在內存中,這使得切換頁面時顯示更快,同時也會佔用一定的內存。
在使用ViewPager作爲Fragment容器時,需要根據顯示Fragmet的數量來使用Adapter,以免佔用過多的內存或切換頁面時內容顯示緩慢。
二 Fragment中使用Fragment
Fragment中添加子Fragment的方式與Activity中添加方式基本相同,只是獲取FragmentManager對象的方法不同。Activity中使用getSupportFragmentManager()方法獲取,Fragment中需要使用getChildFragmentManager(),而不是調用getFragmentManager()。
Fragment的getFragmentManager()代碼註釋:
Return the FragmentManager for interacting with fragments associated with this fragment’s activity
獲取與Fragment關聯的Activity交互的FragmentManager。
Fragment的getFragmentManager()代碼註釋:
Return a private FragmentManager for placing and managing Fragments inside of this Fragment.
獲取Fragment內一個管理子Fragment的私有的FragmentManager。
子Fragment使用startActivityForResult()方法在低版本時onActivityResult得不到執行完成的回調問題。
先看看android 4.4源碼中的FragmentActivity的onActivityResult代碼:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFragments.noteStateNotSaved();
int index = requestCode>>16;
if (index != 0) {
index--;
...
//mFragments爲FragmentManagerImpl實例,通過getSupportFragmentManager()獲取的即該實例。
Fragment frag = mFragments.mActive.get(index);
if (frag == null) {
...
} else {
frag.onActivityResult(requestCode&0xffff, resultCode, data);
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
通過以上註釋可知:
FragmentActivity的onActivityResult方法只查找了通過getSupportFragmentManager()關聯的Fragment,而通過getFragmentManager()與Fragment關聯的子Fragment則直接被忽略了,從而導致在子Fragment無法收到回調。
這個問題在23.2.0以下的support-v4包中都存在,23.2.0版本才修復了這個Bug,改用以上版本則可避免。或着在自定義的Activity基類中重寫此方法,自己實現對子Fragment的onActivityResult回調。建議還是使用高版本的v4包,低版本的Fragment中問題實在是太多了。
三 Fragment內部數據保存和恢復
在onSaveInstanceState()保存Fragment數據,自定義的業務類儘量實現Parcelable接口(建議不使用Serializable)。代碼如下:
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//保存數據
outState.putString(key, value);
}
在Fragment被調用onSaveInstanceState方法後,如果有操作DialogFragment,需使用commitAllowingStateLoss()提交,使用commit()則會拋出異常。
在onViewStateRestored()恢復數據並更新UI。
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if(savedInstanceState != null){
//恢復數據
String value = savedInstanceState.getString(key);
...
}
}
四 ViewPager中Fragment延時加載數據
自定義一個Fragment基類:
public abstract class BaseFragment extends Fragment {
/**
* 是否已加載數據
*/
protected boolean hasLoadData = false;
@Override
public void onResume() {
super.onResume();
if (getUserVisibleHint() && !hasLoadData) {
//加載數據
loadData(true);
hasLoadData = true;
}
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if(getUserVisibleHint() && !hasLoadData){
//加載數據
loadData(true);
hasLoadData = true;
}
}
/**
* 加載數據
* @param isRefresh true爲刷新,false爲加載更多
*/
public abstract void loadData(boolean isRefresh);
}
子中實現數據加載,在onSaveInstanceState方法中保存已經加載的數據,onViewStateRestored方法中恢復數據並將數據更新到UI。這樣既可以避免加載未顯示的Fragment,又可以在Fragment被重新顯示時無需加載即可快速恢復數據並顯示。
五 Fragment獲取Activity
Fragment中獲取FragmentActivity的getActivity方法代碼:
final public FragmentActivity getActivity() {
return mHost == null ? null : (FragmentActivity) mHost.getActivity();
}
Fragment是通過屬性mHost(FragmentHostCallbackde實例)關聯FragmentActivity,該屬性沒有限定標識符,即給其賦值的類在同一包名中。既然管理Fragment的是FragmentManager,那麼給mHost賦值的操作應該在其方法內。而FragmentManager爲抽象類,子類爲FragmentManagerImpl,則賦值操在其方法內。通過查看Fragment添加和顯示理財,可知具體設置mHost值是在moveToState方法中:
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
switch (f.mState) {
case Fragment.INITIALIZING:
if (f.mSavedFragmentState != null) {
...
//恢復保存的狀態信息
}
//設置mHost
f.mHost = mHost;
//
f.mParentFragment = mParent;
//
f.mFragmentManager = mParent != null ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
f.mCalled = false;
//執行Fragment的onAttach方法
f.onAttach(mHost.getContext());
...
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
if (f.mAnimatingAway != null) {
...
} else {
if (!f.mRetaining) {
f.performDestroy();
}
f.mCalled = false;
//執行Fragment的onDetach
f.onDetach();
...
//如果不需要保活此Fragment
if (!keepActive) {
if (!f.mRetaining) {
makeInactive(f);
} else {
//將Fragment中的mHost置爲null
f.mHost = null;
f.mParentFragment = null;
f.mFragmentManager = null;
f.mChildFragmentManager = null;
}
}
}
}
在onAttach之後纔可以調用getActivity獲取到FragmentActivity對象,在onDetach之後一般情況下是獲取不到值。