我所見的Fragment

PolHislop 8年前發布 | 6K 次閱讀 安卓開發 Android開發 移動開發

是什么

Fragment (碎片)是 Android 中的行為或用戶界面部分。

為什么

在 Android 中引入 Fragment 主要是為了給大屏幕 (如平板電腦) 上更加動態和靈活的UI設計提供支持。使用Fragment可以在app適配平板時無需大范圍的更改布局。下圖是google提供的一個示例圖:

如上兩幅圖中可以看出, 通過使用兩個 Fragment 進行不同的排列組合可以很好的適配平板和手機兩種不同布局風格, 而減少大量的布局開發作業.

時間點

Android 在 Android 3.0(API 級別 11)中引入了片段, 如果使用v4包可以兼容到 Android 1.6;

如果使用 V4 包的話需要注意一些地方:

1. 果你使用了v4包下的 Fragment , 那么所在的那個Activity就要繼承 FragmentActivity .

2. 如果要使用FragmentManager必須使用 getSupportFragmentManager() ;

怎么用

1. 生命周期

管理 Fragment 的生命周期與管理 Activity 的生命周期非常相似,片段也以三種狀態存在:

1. 繼續
片段在運行中的 Activity 中可見。

  1. 暫停 另一個 Activity 位于前臺并具有焦點,但此片段所在的 Activity 仍然可見(前臺 Activity 部分透明,或未覆蓋整個屏幕)。

  2. 停止 片段不可見。宿主 Activity 已停止,或片段已從 Activity 中移除,但已添加到返回棧。 停止片段仍然處于活動狀態(系統會保留所有狀態和成員信息)。 不過,它對用戶不再可見,如果 Activity 被終止,它也會被終止。</code></pre>

    Fragment 與 Activity 的生命周期最顯著的區別是它們各自返回棧中的存儲方式, 和 Activity 停止時自動放入由系統管理的 Activity 返回棧不同, Fragment 僅在被移除的事務執行期間調用 addToBackStack() 顯示請求保存實例時, 系統才會將 Fragment 放入由宿主 Activity 管理的返回棧.

    Fragment 與 Activity 的生命周期具有協調一致性, 這體現在 Activity 的每次生命周期回調都會引發每個片段的類似回調. 例如, 當 Activity 收到 onPause() 時, Activity 中的每個片段也會收到 onPause(). Fragment中還有一些額外的方法:

    onAttach()
    在片段已與 Activity 關聯時調用(Activity 傳遞到此方法內)。

onCreateView() 調用它可創建與片段關聯的視圖層次結構。

onActivityCreated() 在 Activity 的 onCreate() 方法已返回時調用。

onDestroyView() 在移除與片段關聯的視圖層次結構時調用。

onDetach() 在取消片段與 Activity 的關聯時調用。</code></pre>

使用 <fragment> 標簽引入 Fragment 的 Activity 的創建到銷毀, 生命周期方法調用的順序:

-Fragment: onCreate
 -Fragment: onCreateView 
 -Fragment: onViewCreated
 -Activity: onCreate
 -Fragment: onActivityCreated
 -Activity: onStart
 -Fragment: onStart
 -Activity: onResume
 -Fragment: onResume
 -Fragment: onPause
 -Activity: onPause
 -Fragment: onStop
 -Activity: onStop
 -Fragment: onDestroyView
 -Fragment: onDestroy
 -Fragment: onDetach
 -Activity: onDestroy

2. 管理 Fragment

要想管理 Activity 中的 Fragment, 需要使用 FragmentManager. 可以從 Activity 中調用 getFragmentManager() 獲取.

可以使用 FragmentManager 執行的操作包括:

  • 通過 findFragmentById()(對于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(對于提供或不提供 UI 的片段)獲取 Activity 中存在的片段。
  • 通過 popBackStack()(模擬用戶發出的返回命令)將片段從返回棧中彈出。
  • 通過 addOnBackStackChangedListener() 注冊一個偵聽返回棧變化的偵聽器。

注意: 在 Fragment 的嵌套情況下在父 Fragment 中使用 getFragmentManager() / getSupportFragmentManager() 獲取 FragmentManager; 在子 Fragment 中需要使用getChildFragmentManager 來獲取.

2. Fragment的簡單使用

  • 使用< fragment >標簽在布局中添加碎片
  • 通過 android:name 屬性來顯式指明要添加的碎片類名,注意一定要將類的包名也加上。
<fragment
            android:id = "@+id/left_fragment"
            android:name = "com.example.FragmentTest.fragment.HomeFragment"
            android:layout_width = "1dp"
            android:layout_height = "match_parent"
            android:layout_weight = "1"
            tools:layout = "@layout/left_fragment"/>

注:每個片段都需要一個唯一的標識符,重啟 Activity 時,系統可以使用該標識符來恢復片段(您也可以使用該標識符來捕獲片段以執行某些事務,如將其移除)。 可以通過三種方式為片段提供 ID:

- 為 android:id 屬性提供唯一 ID。

- 為 android:tag 屬性提供唯一字符串。

- 如果您未給以上兩個屬性提供值,系統會使用容器視圖的 ID。

3. 動態添加Fragment

碎片真正的強大之處在于,它可以在程序運行時動態地添加到活動當中

  1. 創建待添加的碎片實例。
  2. 獲取到 FragmentManager,在活動中可以直接調用 getFragmentManager()方法得到。
  3. 開啟一個事務,通過調用 beginTransaction()方法開啟。
  4. 向容器內加入碎片,可以使用 replace() 或 add() 方法實現,需要傳入容器的 id 和待添加的碎
    片實例。
  5. 提交事務,調用 commit()方法來完成。
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.fg_main, new MainFragment());
ft.addToBackStack(null);
ft.commit();

注意:

1. 調用 commit() 不會立即執行事務,而是在 Activity 的 UI 線程(“主”線程)可以執行該操作時再安排其在線程上運行。不過,如有必要,您也可以從 UI 線程調用 executePendingTransactions() 以立即執行 commit() 提交的事務。通常不必這樣做,除非其他線程中的作業依賴該事務。

2. 您只能在 Activity 保存其狀態(用戶離開 Activity)之前使用 commit() 提交事務。如果您試圖在該時間點后提交,則會引發異常。 這是因為如需恢復 Activity,則提交后的狀態可能會丟失。 對于丟失提交無關緊要的情況,請使用 commitAllowingStateLoss()。

4. Fragment 和 Activity 之間進行通訊

  1. 在 Activity 中獲取相應 Fragment 的實例, 可以通過 FragmentManager 使用 findFragmentById() 或 findFragmentByTag().
RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);
  1. 得到當前碎片相關聯的活動實例
MainActivity activity = (MainActivity) getActivity();
  1. 同時出現在 Activity 中的兩個 Fragment 之間如何傳遞信息

    執行此操作的一個好方法是,在片段內定義一個回調接口,并要求宿主 Activity 實現它。 當 Activity 通過該接口收到回調時,可以根據需要與布局中的其他片段共享這些信息。

public static class FragmentA extends ListFragment 
{
    ...

// Container Activity must implement this interface
public interface OnArticleSelectedListener 
{
    public void onArticleSelected(Uri articleUri);
}

@Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnArticleSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); } } ... }</code></pre>

遇到的坑

對于Fragment中的各種坑推薦大神的 Fragment全解析系列 , 以及大神所寫的 Fragmentation庫 , 以下就只寫一些我自己遇到的問題了.

1. getActivity() return null

原因:

大部分原因是由于在 Fragment 與 Activity 斷開關系 (onDetach() 被調用) 后使用 getActivity() (e.g. 異步網絡訪問中onDetach() 被調用, 獲取訪問結果后調用 getActivity() 操作布局)

解決:

  1. 最好的辦法就是我們應該避免在已經onDetach這種情況之后再去調用宿主Activity對象,比如取消這些異步任務;
  2. 還可以在Fragment基類里設置一個Activity mActivity的全局變量,在onAttach(Activity activity)里賦值,使用mActivity代替getActivity(),保證Fragment即使在onDetach后,仍持有Activity的引用(有引起內存泄露的風險,但是異步任務沒停止的情況下,本身就可能已內存泄漏,相比Crash,這種做法“安全”些),即:
protected Activity mActivity;
@Override
public void onAttach(Activity activity) 
{
    super.onAttach(activity);
    this.mActivity = activity;
}

/**

  • 如果你用了support 23的庫,上面的方法會提示過時,有強迫癥的小伙伴,可以用下面的方法代替 */ @Override public void onAttach(Context context) { super.onAttach(context); this.mActivity = (Activity)context; }</code></pre>

    2. 異常:Can not perform this action after onSaveInstanceState

    原因:

    在你離開當前Activity等情況下,系統會調用onSaveInstanceState()幫你保存當前Activity的狀態、數據等,直到再回到該Activity之前(onResume()之前),你使用commit()提交了Fragment事務,就會拋出該異常!

    解決: 1. (不推薦)該事務使用`commitAllowingStateLoss()`方法提交, 但是有可能導致該次提交無效!(在此次離開時恰巧Activity被強殺時) 2. (推薦)在重新回到該Activity的時候(onResumeFragments()或onPostResume()), 再執行該事務!

    注意事項

    參數傳遞

    對Fragment傳遞數據,建議使用`setArguments(Bundle args)`,而后在onCreate中使用`getArguments()`取出,在 “內存重啟”前,系統會幫你保存數據,不會造成數據的丟失。和Activity的Intent恢復機制類似。

    創建 Fragment 對象

    使用`newInstance(參數) `創建Fragment對象,優點是調用者只需要關系傳遞的哪些數據,而無需關心傳遞數據的Key是什么。

    public static class TestFragment extends Fragment 
    {

    private static final String ARG = "arg";

    public static Fragment newInstance(String arg) {

     TestFragment fragment = new TestFragment();
     Bundle bundle = new Bundle();
     bundle.putString( ARG, arg);
     fragment.setArguments(bundle);
     return fragment;
    

    }

    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

     View rootView = inflater.inflate(R.layout. fragment_main, container, false);
     TextView tv = (TextView) rootView.findViewById(R.id. tv);
     tv.setText(getArguments().getString( ARG));
     return rootView;
    

    }

}</code></pre>

add() 和 replace()

  1. add() 和 replace() 的區別

    • show(),hide()最終是讓Fragment的View setVisibility(true還是false),不會調用生命周期;
    • replace()的話會銷毀視圖,即調用onDestoryView、onCreateView等一系列生命周期;
  2. 使用建議

    如果你有一個很高的概率會再次使用當前的Fragment,建議使用show(),hide(),可以提高性能。

在我使用Fragment過程中,大部分情況下都是用show(),hide(),而不是replace()。

  1. onHiddenChanged()

    當使用add() + show(),hide()跳轉新的Fragment時,舊的Fragment回調 onHiddenChanged() ,不會回調onStop()等生命周期方法,而新的Fragment在創建時是不會回調onHiddenChanged(),這點要切記。

注意:如果你的app有大量圖片,這時更好的方式可能是replace,配合你的圖片框架在Fragment視圖銷毀時,回收其圖片所占的內存。

使用 ViewPager + Fragment 時使用懶加載

我們在做應用開發的時候,一個Activity里面可能會以viewpager(或其他容器)與多個Fragment來組合使用,而如果每個fragment都需要去加載數據,或從本地加載,或從網絡加載,那么在這個activity剛創建的時候就變成需要初始化大量資源。這樣的結果,我們當然不會滿意。那么,能不能做到當切換到這個fragment的時候,它才去初始化(不同于View的初始化,這里指的是數據的加載)呢?

  • 答案就在Fragment里的setUserVisibleHint這個方法里具體可查看 android API

    該方法用于告訴系統,這個Fragment的UI是否是可見的。所以我們只需要繼承Fragment并重寫該方法,即可實現在fragment可見時才進行數據加載操作,即Fragment的懶加載。

  • 為什么不使用ViewPager的預加載功能呢

    現在大體上放置ViewPager預加載的方法有兩種:

    1. 在使用ViewPager嵌套Fragment的時候,由于VIewPager的幾個Adapter的設置來說,都會有一定的預加載(默認是左右各一個Frament)。通過設置 setOffscreenPageLimit (int number) 來設置預加載的熟練,在V4包中,默認的預加載是1,即使你設置為0,也是不起作用的,設置的只能是大于1才會有效果的。我們需要通過更改V4包中的默認屬性才可以。

    2. 限制預加載,會出現滑動過程中卡頓現象。其實Fragment中防止預加載主要是防止數據的預加載,Fragment中的VIew預加載是有好處的,我們可以通過Fragment中的一個方法來達到預加載View 但是不加載數據,在Fragment顯示的時候才去加載數據。

public abstract class LazyFragment extends Fragment 
{
    protected boolean isVisible;
    /**

 * 在這里實現Fragment數據的緩加載.
 * @param isVisibleToUser
 */
@Override
public void setUserVisibleHint(boolean isVisibleToUser) 
{
    super.setUserVisibleHint(isVisibleToUser);
    if(getUserVisibleHint()) 
    {
        isVisible = true;
        onVisible();
    } else {
        isVisible = false;
        onInvisible();
    }
}
protected void onVisible()
{
    lazyLoad();
}
protected abstract void lazyLoad();
protected void onInvisible(){}

}</code></pre>

在LazyFragment,增加了三個方法,一個是onVisiable,即fragment被設置為可見時調用,一個是onInvisible,即fragment被設置為不可見時調用。另外再寫了一個lazyLoad的抽象方法,該方法在onVisible里面調用。你可能會想,為什么不在getUserVisibleHint里面就直接調用呢?

我這么寫是為了代碼的復用。因為在fragment中,我們還需要創建視圖(onCreateView()方法),可能還需要在它不可見時就進行其他小量的初始化操作(比如初始化需要通過AIDL調用的遠程服務)等。而setUserVisibleHint是在onCreateView之前調用的,那么在視圖未初始化的時候,在lazyLoad當中就使用的話,就會有空指針的異常。而把lazyLoad抽離成一個方法,那么它的子類就可以這樣做:

public class OpenResultFragment extends LazyFragment
{
    // 標志位,標志已經初始化完成。
    private boolean isPrepared;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
    {
        Log.d(LOG_TAG, "onCreateView");
        View view = inflater.inflate(R.layout.fragment_open_result, container, false);
        //XXX初始化view的各控件
    isPrepared = true;
        //這里再一次調用lazyLoad()方法是因為setUserVisibleHint()是在onCreateView()方法之后調用的
        lazyLoad();
        return view;
    }
    @Override
    protected void lazyLoad() 
    {
        if(!isPrepared || !isVisible) 
        {
            return;
        }
        //填充各控件的數據
    }
}

 

來自:http://blog.csdn.net/ice_coffee_mzp/article/details/53583641

 

 本文由用戶 PolHislop 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!