1. 程式人生 > >[Android]Fragment 初探索|生命週期|懶載入|重疊解決

[Android]Fragment 初探索|生命週期|懶載入|重疊解決

一、Fragment介紹

Fragment是在Android 3.0 (API level 11)開始引入的,它能讓你的app在現有基礎上效能大幅度提高,並且佔用記憶體降低,同樣的介面Activity佔用記憶體比Fragment要多,響應速度Fragment比Activty在中低端手機上快了很多,甚至能達到好幾倍,"單Activity + 多Fragment架構""多模組Activity + 多Fragment架構"應運而生! 


Fragment導包時有兩個選擇:

  • android.app.Fragment

  • android.support.v4.app.Fragment

後者相容android3.0以下的版本。兩個版本都可以使用,但是要注意相關函式引用要保持一致,更不要混用。 

本文涉及原始碼:https://github.com/gitEkko/MyApplication.git

二、生命週期

 

 

onAttach()  關聯到Activity的時候呼叫。如果,需要使用Activity的引用或者使用Activity作為其他操作的上下文,將在此回撥方法中實現
onCreate() 系統建立Fragment的時候回撥
onCreateView()
當第一次繪製Fragment的UI時系統呼叫這個方法,該方法將返回一個View,如果Fragment不提供UI也可以返回null。
onActivityCreated() 當Activity中的onCreate方法執行完後呼叫。可以在這個函式裡面做和Activity UI互動的操作(因為Activity的onCreate()函式之後Activity的UI已經準備好了,可以UI互動)。
onStart()  啟動Fragment的時候回撥,這個時候Fragment可見
onResume()
Fragment變為活動狀態獲取焦點的時候是回撥,這個時候Fragment已經完全展示在前臺,並且可以和使用者互動
onPause() Fragemnt變成非活動狀態失去焦點的時候呼叫,注意這個時候Fragment還是可見的,只是不能和使用者互動了而已
onStop()  Fragment變成不可見的時候呼叫。這個時候Fragment還是活著的,只是可能被加入到了Fragment的回退棧中
onDestroyView() Fragment中的佈局被移除的時候呼叫
onDestroy() Fragment被銷燬的時候呼叫
onDetach() 

Fragment和Activity解除關聯的時候呼叫個 

此外,注意以下週期函式:

  • onViewCreated(): 是在onCreateView()函式之後執行,我們通常在onViewCreated()函式裡面findViewById。
  • setUserVisibleHint():當前頁面是否可見(一般ViewPager+Fragemnt配合使用會用到,懶(延時)載入的時候這個函式有大用處),因為ViewPager+Fragemnt的時候是會同時去載入前後多個Fragment的,這個時候就有些Fragment是可見的一些Fragment是不可見的。有一點要注意setUserVisibleHint()只在ViewPager+Fragment這情況下才會回撥,其他靜態載入和動態載入Fragment不會被呼叫到。
  • onHiddenChanged():hide()、show()切換Fragment顯示的時候,Fragment只會回撥onHiddenChanged()。Fragment在add()的時候不會回撥onHiddenChanged()函式。還有,在ViewPager+Fragment使用的時候Fragment也不會回撥onHiddenChanged()函式的。

極力推薦大神總結的部落格,認真看一遍,自己打打Log,絕對能把Fragment生命週期安排的明明白白。https://blog.csdn.net/MeloDev/article/details/53406019#comments 

三、具體使用 

  1. 靜態載入
  2. 動態載入 

靜態載入 :直接將Fragment當作一個控制元件,新增在佈局檔案中。

<LinearLayout 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:orientation="vertical" >

   <fragment android:name="com.xxx.app.FragmentTest.ExampleFragment"
        android:id="@+id/content"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

動態載入: 

FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (!navigateFragment.isAdded()) {
    fragmentTransaction.add(R.id.fragment_content, navigateFragment, getString(R.string.tab_1));
} else {
    fragmentTransaction.show(navigateFragment);
}
//fragmentTransaction.replace(R.id.fragment_content, navigateFragment, getString(R.string.tab_1));
fragmentTransaction.commit();
//fragmentTransaction.commitAllowingStateLoss();

根據上述程式碼,可以看出Fragment的動態使用用到了FragmentManager 。下面開始介紹FragmentManager :

Fragment常用的三個類:

  1. android.app.Fragment 
  2. android.app.FragmentManager 用於在Activity中才做Fragment
  3. android.app.FragmentTransaction 保證一系列Fragment操作的原子性,事務相關。 

對應v4包:

  1. android.support.v4.app.Fragment
  2. android.support.v4.app.FragmentManager
  3. android.support.v4.app.FragmentTransaction

獲取 FragmentManager:getFragmentManager(); v4:getSupportFragmentManager()

FragmentManager fragmentManager = getSupportFragmentManager();//v4包
//FragmentManager fragmentManager = getFragmentManager();

開啟FragmentTransaction事務:

FragmentTransaction transaction = fragmentManager.beginTransaction();
  • transaction.add() :新增一個Fragment
  • transaction.remove():移除一個Fragment,如果此Fragment沒有加入到回退棧,其例項將會被銷燬
  • transaction.replace():使用另一個Fragment替換當前,作用等效於add+remove
  • transaction.hide():隱藏當前的Fragment,僅僅是設為不可見,不會銷燬例項
  • transaction.show():將隱藏的fragment顯示出來。

提交 FragmentTransaction事務:

transaction.commit();
//transaction.commitAllowingStateLoss();

注:commit方法要在Activity.onSaveInstance()之前呼叫,否則會出現Activity狀態不一致:State loss 的問題。

在commit()方法之前,你可以呼叫addToBackStack(),把這個transaction加入back stack中去,這個back stack是由activity管理  的,當用戶按返回鍵時,就會回到上一個fragment的狀態。

你只能在activity儲存它的狀態(當用戶要離開activity時)之前呼叫commit(),如果在儲存狀態之後呼叫commit(),將會丟擲一個異常。這是因為當activity再次被恢復時commit之後的狀態將丟失。如果丟失也沒關係,那麼使用commitAllowingStateLoss()方法。

四、通訊方式 

移步:[Android]Fragment與Activity之間的通訊方案

五、Fragment+Tablayout+ViewPager+ BottomNavigationView 

原始碼:https://github.com/gitEkko/MyApplication.git

先上Demo截圖:

比較主流的佈局方式,單Activity+多Fragment。上方利用Tablayout和ViewPager實現頁面切換。下方利用 BottomNavigationView導航欄實現頁面切換。第一頁導航頁新增Tablayout+ViewPager佈局。

該Demo涉及:

  1. BottomNavigationView使用
  2. Fragment生命週期驗證
  3. Fragment懶載入
  4. 解決Fragment重疊問題

1. BottomNavigationView使用

 New-Activity-Bottom Navigation Activity 即可建立。可在生成的res/menu/navigation.xml檔案中控制底部導航選單欄的樣式。

佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/common_content_bg"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <FrameLayout
        android:id="@+id/fragment_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="@color/bg_bottom_navigation"
        app:itemIconTint="@drawable/selector_navigation_color"
        app:itemTextColor="@drawable/selector_navigation_color"
        app:menu="@menu/navigation" />

</LinearLayout>

 menu檔案navigation.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_bottom_navigation"
        android:title="@string/tab_1" />

    <item
        android:id="@+id/navigation_music"
        android:icon="@drawable/ic_bottom_music"
        android:title="@string/tab_2" />

    <item
        android:id="@+id/navigation_car"
        android:icon="@drawable/ic_bottom_car"
        android:title="@string/tab_3" />
    <item
        android:id="@+id/navigation_setting"
        android:icon="@drawable/ic_bottom_setting"
        android:title="@string/tab_4" />
    <item
        android:id="@+id/navigation_toys"
        android:icon="@drawable/ic_bottom_toys"
        android:title="@string/tab_5" />

</menu>

 MainActivity.java  menu選單監聽程式碼,會自動幫我們新增。

 @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        //supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main2);

        toolbar_title = (TextView) findViewById(R.id.toolbar_title);
        toolbar_title.setText(R.string.tab_1);

        //底部 Tab頁
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
        //當BottomNavigationView的item個數大於3時,取消自帶動畫效果。
        //利用反射
        BottomNavigationViewHelper.disableShiftMode(navigation);

        //正常啟動
        initHomeFragment();             
    }

    //BottomNavigationView 底部Tab頁 menu選單監聽
    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            Log.d(TAG, "onNavigationItemSelected");
            switch (item.getItemId()) {
                case R.id.navigation_home:
                    toolbar_title.setText(R.string.tab_1);
                    clickNavigationLayout(fragmentTransaction);
                    return true;
                case R.id.navigation_music:
                    toolbar_title.setText(R.string.tab_2);
                    clickMusicLayout(fragmentTransaction);
                    return true;
                case R.id.navigation_car:
                    toolbar_title.setText(R.string.tab_3);
                    clickCarLayout(fragmentTransaction);
                    return true;
                case R.id.navigation_setting:
                    toolbar_title.setText(R.string.tab_4);
                    clickSettingLayout(fragmentTransaction);
                    return true;
                case R.id.navigation_toys:
                    toolbar_title.setText(R.string.tab_5);
                    clickToysLayout(fragmentTransaction);
                    return true;
            }
            return false;
        }
    };

底部導航欄BottomNavigationView的選單最多是5個,當大於3個時,效果會有所變化,會增加點選動畫,且選中item的所佔空間會變大。如圖:

使item佔用空間相同的解決方案:利用反射。具體原理可百度。

/**
 * 當BottomNavigationView的item的個數 >3時,item之間不會再佔用相同控制元件,而是添加了一個動畫。
 * 通過原始碼,用反射解決
 */
public class BottomNavigationViewHelper {

    @SuppressLint("RestrictedApi")
    public static void disableShiftMode(BottomNavigationView view) {
        //獲取子View BottomNavigationMenuView的物件
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
        try {
            //設定私有成員變數mShiftingMode可以修改
            Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
            shiftingMode.setAccessible(true);
            shiftingMode.setBoolean(menuView, false);
            shiftingMode.setAccessible(false);
            for (int i = 0; i < menuView.getChildCount(); i++) {
                BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
                //去除shift效果
                item.setShiftingMode(false);
                item.setChecked(item.getItemData().isChecked());
            }
        } catch (NoSuchFieldException e) {
            Log.e("BNVHelper", "沒有mShiftingMode這個成員變數", e);
        } catch (IllegalAccessException e) {
            Log.e("BNVHelper", "無法修改mShiftingMode的值", e);
        }
    }
}

在Activity中載入BottomNavigationView後,可新增以下程式碼消除自帶效果。

 //底部 Tab頁
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
//當BottomNavigationView的item個數大於3時,取消自帶動畫效果。
//利用反射
BottomNavigationViewHelper.disableShiftMode(navigation);

2.Fragment生命週期驗證

不寫了,詳見大神部落格: https://blog.csdn.net/MeloDev/article/details/53406019#comments 


3.Fragment懶載入

ViewPager的預載入機制,預設去預載入相鄰兩頁,來保證滑動的順暢性。但是如果每個tab頁都涉及了大量的網路請求和頁面重新整理,預載入機制會在使用者不知情的情況下消耗大量使用者流量,帶來麻煩。然而ViewPager預載入快取頁的數量最少為1。所以預載入無法取消。要做到用時載入,不用時則不載入,這就需要用到ViewPager的懶載入。

前邊生命週期提到過一個周期函式:setUserVisibleHint();

setUserVisibleHint();並不會被主動呼叫,而是在ViewPager預載入的時候,左右相鄰的fragment都會回撥此方法。看一段log:

12-17 15:29:04.895 E 19450    - NavigateFragment: onAttach 
12-17 15:29:04.896 E 19450    - NavigateFragment: onCreate 
12-17 15:29:04.896 E 19450    - NavigateFragment: onCreateView 
12-17 15:29:04.897 E 19450    - NavigateFragment: onViewCreated 
12-17 15:29:04.903 E 19450    - NavigateFragment: onActivityCreated 
12-17 15:29:04.903 E 19450    - NavigateFragment: onStart 
12-17 15:29:04.905 E 19450    - NavigateFragment: onResume 
12-17 15:29:04.919 E 19450    - ScienceFragment: setUserVisibleHint : isVisibleToUser = false 
12-17 15:29:04.919 E 19450    - GameFragment: setUserVisibleHint : isVisibleToUser = false 
12-17 15:29:04.919 E 19450    - ScienceFragment: setUserVisibleHint : isVisibleToUser = true 
12-17 15:29:04.919 E 19450    - ScienceFragment: onAttach 
12-17 15:29:04.919 E 19450    - ScienceFragment: onCreate 
12-17 15:29:04.920 E 19450    - GameFragment: onAttach 
12-17 15:29:04.920 E 19450    - GameFragment: onCreate 
12-17 15:29:04.920 E 19450    - ScienceFragment: onCreateView 
12-17 15:29:04.921 E 19450    - ScienceFragment: onViewCreated 
12-17 15:29:04.922 E 19450    - ScienceFragment: onActivityCreated 
12-17 15:29:04.922 E 19450    - ScienceFragment: onStart 
12-17 15:29:04.922 E 19450    - ScienceFragment: onResume 
12-17 15:29:04.922 E 19450    - GameFragment: onCreateView 
12-17 15:29:04.923 E 19450    - GameFragment: onViewCreated 
12-17 15:29:04.924 E 19450    - GameFragment: onActivityCreated 
12-17 15:29:04.924 E 19450    - GameFragment: onStart 
12-17 15:29:04.924 E 19450    - GameFragment: onResume
---------------------------------------------------------------------------------------
12-17 15:29:22.304 E 19450    - EquipmentFragment: setUserVisibleHint : isVisibleToUser = false 
12-17 15:29:22.305 E 19450    - ScienceFragment: setUserVisibleHint : isVisibleToUser = false 
12-17 15:29:22.305 E 19450    - GameFragment: setUserVisibleHint : isVisibleToUser = true 
12-17 15:29:22.305 E 19450    - EquipmentFragment: onAttach 
12-17 15:29:22.305 E 19450    - EquipmentFragment: onCreate 
12-17 15:29:22.306 E 19450    - EquipmentFragment: onCreateView 
12-17 15:29:22.310 E 19450    - EquipmentFragment: onViewCreated 
12-17 15:29:22.311 E 19450    - EquipmentFragment: onActivityCreated 
12-17 15:29:22.312 E 19450    - EquipmentFragment: onStart 
12-17 15:29:22.312 E 19450    - EquipmentFragment: onResume 

可以看出,當ViewPager滑動到某tab頁時,上一頁,本也,下一頁都會呼叫setUserVisibleHint()函式。同時也能看出確實對下一個tab頁進行預載入,例如第二段log,當前頁是GameFragment,EuqipmentFragment的生命週期從onAttach走到了onResum(第一頁是ScienceFragment,第二頁是GameFragmnet,第三頁是EquipmentFragment)

說過Fragment並不會主動呼叫setUserVisibleHint()函式,那是誰呼叫的,答案是ViewPager的adapter呼叫的,例如FragmentStatePagerAdapter,可以檢視其原始碼得知

所以,我們可以在setUserVisibleHint()函式中,進行判斷,如果isVisibleToUser == true 或者 getUserVisibleHint() == true時,我們再進行資料載入。

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    Log.e(TAG, "setUserVisibleHint : isVisibleToUser = " + isVisibleToUser);
	
    if (getUserVisibleHint()) {
        //如果此時該fragment可見,則進行資料載入
        lazyLoadData();
    }
}

僅僅這樣還不行,執行你會發現,第一頁並不會進行資料載入,滑動到第二頁,第二頁倒是會出現資料載入。為什麼會這樣,是因為setUserVisibleHint()函式,我們希望它在載入資料的時候,view是已經初始化完畢的,即fagment是可用的。我們參考上邊的log,第二頁GameFragment之所以會實現資料載入,是因為在GameFragment: setUserVisibleHint : isVisibleToUser = true之前,GameFragment的生命週期已經從onAttach走到了onResume,所以在setUserVisibleHint()判定是否要載入資料時,其fragment已經可用,且view已經初始化完成。

而第一頁ScienceFragment: setUserVisibleHint : isVisibleToUser = true之前並沒有ScienceFragment的生命週期,之後才開始執行ScienceFragment的生命週期onAttach-onResume。所以fragment當時並不可用,所以並不會實現資料載入。解決方案,在onStart()函式中新增setUserVisibleHint(true);

@Override
public void onStart() {
    super.onStart();
    Log.e(TAG, "onStart");
    //解決第一個fragment 起始不載入資料
    //ViewPager有預載入機制,預設左右兩側的tab頁都會預載入並回調setUserVisibleHint方法,
    //只有當fragment的view載入完成後,才可以進行資料載入。
    //所以在此onStart()方法中呼叫setUserVisibleHint,此時view已經建立完畢,進行資料獲取。
    if (getUserVisibleHint()) {
        setUserVisibleHint(true);
    }
}

同時,我們應該在第一次進入某個fragment時才進行資料載入,第二次進入時,應當是使用者主動下拉進行資料載入。所以新增條件如下:

/**
* 懶載入
* 只有當第一次進入fragment並且UI已建立完成才進行資料重新整理
*/
public void lazyLoadData() {
    Log.e(TAG, "isFirst = " + isFirst);
    if (!isFirst || !isViewCreated) {
        return;
    }
    isFirst = false;
    //重新整理資料
    refreshData();
}

我們知道ViewPager預載入機制最少設定快取頁為1個。我們有5個tab頁,我們可以設定viewPager.setOffscreenPageLimit(5);這樣開啟應用後,ViewPager會幫我們自動載入5個fragment(生命週期onAttach-onResume),此時,無論我們是滑動還是點選上方tab,都會執行懶載入函式。

viewPager.setAdapter(adapter);
//設定所有tab頁全部預載入
viewPager.setOffscreenPageLimit(5);
tabLayout.setupWithViewPager(viewPager);

附上基類BaseFragment程式碼:

public abstract class BaseFragment extends Fragment {

    public Photo[] photos = {
            new Photo("Kobe", R.mipmap.nba1),
            new Photo("Miami", R.mipmap.nba2),
            new Photo("Griffin", R.mipmap.nba3),
            new Photo("Kobe", R.mipmap.nba4),
            new Photo("Kobe", R.mipmap.nba5),
            new Photo("Jordan", R.mipmap.nba6),
            new Photo("James", R.mipmap.nba7),
            new Photo("Kobe", R.mipmap.nba8),
            new Photo("Jordan", R.mipmap.nba9),
            new Photo("Kobe", R.mipmap.nba10),
            new Photo("Irving", R.mipmap.nba11),
            new Photo("Kobe", R.mipmap.nba12),
            new Photo("Kobe", R.mipmap.nba13),
            new Photo("Kobe", R.mipmap.nba14),
            new Photo("1996", R.mipmap.nba15),
            new Photo("Kobe", R.mipmap.nba16),
            new Photo("Kobe", R.mipmap.nba17)};

    private final String TAG = "Ekko1 - " + this.getClass().getSimpleName();

    public boolean isViewCreated = false;
    private boolean isFirst = true;

    public FragmentActivity fragmentActivity;
    //public Activity activity;

    public View rootView;

    private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN";

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        //防止子Fragment 獲取getActivity()為null
        //activity = (Activity) context;
        fragmentActivity = (FragmentActivity) context;

        Log.e(TAG, "onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(TAG, "onCreate");
    }


    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        Log.e(TAG, "onCreateView");
        rootView = inflater.inflate(setFragmentLayoutID(), container, false);
        initData();
        return rootView;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Log.e(TAG, "onViewCreated");
        isViewCreated = true;
        initView();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.e(TAG, "onActivityCreated");
    }


    @Override
    public void onStart() {
        super.onStart();
        Log.e(TAG, "onStart");
        //解決第一個fragment 起始不載入資料
        //ViewPager有預載入機制,預設左右兩側的tab頁都會預載入並回調setUserVisibleHint方法,
        //只有當fragment的view載入完成後,才可以進行資料載入。
        //所以在此onStart()方法中呼叫setUserVisibleHint,此時view已經建立完畢,進行資料獲取。
        if (getUserVisibleHint()) {
            setUserVisibleHint(true);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.e(TAG, "onResume");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.e(TAG, "onPause");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.e(TAG, "onStop");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.e(TAG, "onDestroyView");
        //當Fragment銷燬時,重置變數狀態
        resetState();
    }

    private void resetState() {
        isFirst = true;
        isViewCreated = false;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.e(TAG, "onDetach");
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        Log.e(TAG, "onHiddenChanged : hidden = " + hidden);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.e(TAG, "setUserVisibleHint : isVisibleToUser = " + isVisibleToUser);

        if (getUserVisibleHint()) {
            //如果此時該fragment可見,則進行資料載入
            lazyLoadData();
        }
    }

    /**
     * 懶載入
     * 只有當第一次進入fragment並且UI已建立完成才進行資料重新整理
     */
    public void lazyLoadData() {
        Log.e(TAG, "isFirst = " + isFirst);
        if (!isFirst || !isViewCreated) {
            return;
        }
        isFirst = false;
        //重新整理資料
        refreshData();
    }

    //相關資料初始化
    public abstract void initData();

    //重新整理資料
    public abstract void refreshData();

    //初始化View 控制元件
    public abstract void initView();

    //返回fragment繫結的layout ID
    public abstract int setFragmentLayoutID();

}

其他Fragment繼承此BaseFragment就可以實現懶載入等功能。 Tablayout首頁ScienceFragment程式碼:

public class ScienceFragment extends BaseFragment {

    private static final String TAG = "Ekko";

    private List<Photo> list = new ArrayList<>();
    private PhotoAdapter adapter;
    private SwipeRefreshLayout swipeRefresh;

    public static ScienceFragment newInstance(String title) {
        ScienceFragment fragment = new ScienceFragment();
        Bundle args = new Bundle();
        args.putString("title", title);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void initData() {
        list.clear();
        for (int i = 0; i < 50; i++) {
            Random random = new Random();
            int index = random.nextInt(photos.length);
            list.add(photos[index]);
        }
    }

    @Override
    public void refreshData() {
        Log.e(TAG, "ScienceFragment - refreshData");
        //仿網路重新整理資料
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                fragmentActivity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        initData();
                        //通知資料發生變化
                        adapter.notifyDataSetChanged();
                        //表示重新整理事件結束,並隱藏重新整理進度條
                        swipeRefresh.setRefreshing(false);
                    }
                });
            }
        }).start();

    }

    @Override
    public void initView() {
        RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.science_recycler_view);
        GridLayoutManager layoutManager = new GridLayoutManager(fragmentActivity, 2);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new PhotoAdapter(list);
        recyclerView.setAdapter(adapter);

        swipeRefresh = rootView.findViewById(R.id.swipeRefresh);
        swipeRefresh.setColorSchemeResources(R.color.colorPrimary);
        swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //應該為網路請求最新資料
                refreshData();
            }
        });
    }

    @Override
    public int setFragmentLayoutID() {
        return R.layout.fragment_science;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        swipeRefresh.setRefreshing(true);
    }
}

4.解決Fragment重疊問題

為什麼會發生重疊:

  • 發生了頁面重啟,旋轉螢幕或者記憶體不足強殺Activity.

 當記憶體重啟或者旋轉螢幕時,activity會被銷燬並重新建立,銷燬之前會呼叫onSaveInstanceState(Bundle outState)這個方法,來儲存activity的一些資訊,同時也儲存了新增過的fragment,當恢復時,儲存的fragment預設為顯示狀態,同時重新執行activity的onCreat方法,會初始化首頁fragment顯示,造成fragment重複新增。點選導航欄也會導致fragment重複新增,造成重疊狀況。其實就是activity恢復時,顯示了銷燬之前被儲存的fragment,恢復後,經過初始化或者點選又顯示了新的fragment,造成重疊。

詳細的重疊原因以及方案可以參考:

經過參考,我的解決方案是: 

  • 新增fragment時,設定tag
  • 在onCreat函式中,判定savedInstanceState是否為null
  • 如果發生記憶體重啟,通過findFragmentByTag找到fragment例項,如果沒有被新增過則為null
  • 新增各個fragmet時,都會先判定例項是否為null,避免重複新建fragment例項
  • AndroidManifest.xml檔案中,設定android:configChanges="orientation|keyboardHidden|screenSize"取消螢幕切換帶來的影響

上程式碼:

 MainActivity:

public class Main2Activity extends FragmentActivity {

    TextView toolbar_title;
    private static final String TAG = "Main2Activity1";
    private NavigateFragment navigateFragment;
    private MusicFragment musicFragment;
    private CarFragment carFragment;
    private SettingFragment settingFragment;
    private ToysFragment toysFragment;
    private BaseFragment currentFragment;

    private static final String CURRENTFRAGMENT = "navigateFragment";

    private ArrayList<Fragment> fragmentsList = new ArrayList<>();


    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        //supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main2);

        toolbar_title = (TextView) findViewById(R.id.toolbar_title);
        toolbar_title.setText(R.string.tab_1);

        //底部 Tab頁
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
        //當BottomNavigationView的item個數大於3時,取消自帶動畫效果。
        //利用反射
        BottomNavigationViewHelper.disableShiftMode(navigation);

        FragmentManager fragmentManager = getSupportFragmentManager();

        if (savedInstanceState == null) {
            //正常啟動
            initHomeFragment();
        } else {
            //解決重疊問題
            navigateFragment = (NavigateFragment) fragmentManager.findFragmentByTag(getString(R.string.tab_1));
            musicFragment = (MusicFragment) fragmentManager.findFragmentByTag(getString(R.string.tab_2));
            carFragment = (CarFragment) fragmentManager.findFragmentByTag(getString(R.string.tab_3));
            settingFragment = (SettingFragment) fragmentManager.findFragmentByTag(getString(R.string.tab_4));
            toysFragment = (ToysFragment) fragmentManager.findFragmentByTag(getString(R.string.tab_5));

            Log.d(TAG, "navigateFragment = " + navigateFragment);
            Log.d(TAG, "musicFragment = " + musicFragment);
            Log.d(TAG, "carFragment = " + carFragment);
            Log.d(TAG, "settingFragment = " + settingFragment);
            Log.d(TAG, "toysFragment = " + toysFragment);
            //恢復當前顯示的fragment  currentFragment
            currentFragment = (BaseFragment) fragmentManager.getFragment(savedInstanceState, CURRENTFRAGMENT);
            Log.d(TAG, "currentFragment = " + currentFragment);
        }
    }

    //BottomNavigationView 底部Tab頁 menu選單監聽
    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            Log.d(TAG, "onNavigationItemSelected");
            switch (item.getItemId()) {
                case R.id.navigation_home:
                    toolbar_title.setText(R.string.tab_1);
                    clickNavigationLayout(fragmentTransaction);
                    return true;
                case R.id.navigation_music:
                    toolbar_title.setText(R.string.tab_2);
                    clickMusicLayout(fragmentTransaction);
                    return true;
                case R.id.navigation_car:
                    toolbar_title.setText(R.string.tab_3);
                    clickCarLayout(fragmentTransaction);
                    return true;
                case R.id.navigation_setting:
                    toolbar_title.setText(R.string.tab_4);
                    clickSettingLayout(fragmentTransaction);
                    return true;
                case R.id.navigation_toys:
                    toolbar_title.setText(R.string.tab_5);
                    clickToysLayout(fragmentTransaction);
                    return true;
            }
            return false;
        }
    };

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        //儲存currentFragment  當前顯示的fragment
        fragmentManager.putFragment(outState, CURRENTFRAGMENT, currentFragment);
        super.onSaveInstanceState(outState);
    }

    private void initHomeFragment() {
        Log.d(TAG, "initHomeFragment");
        //設定預設顯示的fragment
        if (navigateFragment == null) {
            navigateFragment = NavigateFragment.newInstance(getString(R.string.tab_1));
        }
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        if (!navigateFragment.isAdded()) {
            fragmentTransaction.add(R.id.fragment_content, navigateFragment, getString(R.string.tab_1));

        } else {
            fragmentTransaction.show(navigateFragment);
        }
        fragmentTransaction.commitAllowingStateLoss();

        currentFragment = navigateFragment;

    }

    //判定fragment是add 還是 hide
    private void addOrShowFragment(FragmentTransaction transaction, Fragment fragment, String TAG1) {

        Log.d(TAG, "currentFragment = " + currentFragment);
        Log.d(TAG, "fragment = " + fragment);
        if (currentFragment == fragment) {
            return;
        }
        if (!fragment.isAdded()) {
            transaction.hide(currentFragment);
            transaction.add(R.id.fragment_content, fragment, TAG1);
            //addToList(fragment);
        } else {
            transaction.hide(currentFragment);
            transaction.show(fragment);
        }
        transaction.commitAllowingStateLoss();

        currentFragment.setUserVisibleHint(false);
        currentFragment = (BaseFragment) fragment;
        currentFragment.setUserVisibleHint(true);
    }


    private void clickNavigationLayout(FragmentTransaction fragmentTransaction) {
        Log.d(TAG, "clickNavigationLayout");
        if (navigateFragment == null) {
            navigateFragment = NavigateFragment.newInstance(getString(R.string.tab_1));
        }
        addOrShowFragment(fragmentTransaction, navigateFragment, getString(R.string.tab_1));
    }

    private void clickMusicLayout(FragmentTransaction fragmentTransaction) {
        Log.d(TAG, "clickMusicLayout");
        if (musicFragment == null) {
            musicFragment = MusicFragment.newInstance(getString(R.string.tab_2));
        }
        addOrShowFragment(fragmentTransaction, musicFragment, getString(R.string.tab_2));
    }

    private void clickCarLayout(FragmentTransaction fragmentTransaction) {
        Log.d(TAG, "clickCarLayout");
        if (carFragment == null) {
            carFragment = CarFragment.newInstance(getString(R.string.tab_3));
        }
        addOrShowFragment(fragmentTransaction, carFragment, getString(R.string.tab_3));
    }

    private void clickSettingLayout(FragmentTransaction fragmentTransaction) {
        Log.d(TAG, "clickSettingLayout");
        if (settingFragment == null) {
            settingFragment = SettingFragment.newInstance(getString(R.string.tab_4));
        }
        addOrShowFragment(fragmentTransaction, settingFragment, getString(R.string.tab_4));
    }

    private void clickToysLayout(FragmentTransaction fragmentTransaction) {
        Log.d(TAG, "clickSettingLayout");
        if (toysFragment == null) {
            toysFragment = ToysFragment.newInstance(getString(R.string.tab_5));
        }
        addOrShowFragment(fragmentTransaction, toysFragment, getString(R.string.tab_5));
    }
}

Fragment也並不是那麼神祕,研究了好多天總算研究個明白了。待補充,待修正。 

本文原始碼:https://github.com/gitEkko/MyApplication.git