Activity中使用Fragment筆記

一 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之後一般情況下是獲取不到值。

請尊重博主勞動成果,轉載請標明原文鏈接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章