Android技能樹 — Fragment整體小結

前言:

Android基礎知識java

Android技能樹 — Fragment整體小結react

Android技能樹 — 動畫小結android

Android技能樹 — View小結ios

Android技能樹 — Activity小結segmentfault

Android技能樹 — View事件體系小結bash

Android技能樹 — Android存儲路徑及IO操做小結網絡

Android技能樹 — 多進程相關小結app

Android技能樹 — Drawable小結ide

Android技能樹 — 屏幕適配小結函數


好久沒有寫文章了,沒其餘緣由,就是由於懶。

由於最近的APP開發,使用的是單Activity + 多Fragment的方式,不一樣於之前基本界面都是Activity的方式,因此Fragment用了不少,想到本身之前也寫了不少相關的基礎知識,Fragment卻歷來沒有寫過,因此就打算補上一篇fragment的基礎總結。

老樣子,先上腦圖:

咱們就按照腦圖的順序同樣樣來看Fragment的基礎知識。


正文:

1.Fragment的添加

咱們知道Fragment是一個"碎片(或者片斷)",添加在Activity中。若是我如今問你,Activity要顯示一個按鈕Button,你會怎麼作?

1. 直接在Layout.xml中添加<Button/>

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        />
</LinearLayout>
複製代碼

2. 在代碼中動態添加,好比咱們添加到一個LinearLayout中:

Button button = new Button(mActivity);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
button.setLayoutParams(params);
container.addView(button);
複製代碼

因此Fragment也很簡單,就把它當作一個簡單的View(但其實更像是「子 Activity」),而後添加方式也是同樣。

1. 直接在Layout.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
複製代碼

2. 直接在代碼中添加:

Fragment one = new FragmentOne();//自定義的Fragment類
//要先獲取FragmentManager對象
FragmentManager fragmentManager = getSupportFragmentManager();
//開啓一個FragmentTransaction事務
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.framelayout_view,one).commit();
複製代碼

其中添加到R.id.framelayout_view的這個idViewGourp能夠是<FrameLayout/>,也能夠是其餘的好比<LinearLayout/>等。


2. Fragment的基本操做

看到咱們上面的動態代碼添加的時候須要獲取FragmentTransactionFragmentManager

2.1 FragmentManager相關

1. getFragmentManager():

獲取Fragment父容器的管理器,可是如今該方法在Activity中已經被標記不推薦使用了。

/**
     * Return the FragmentManager for interacting with fragments associated
     * with this activity.
     *
     * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}
     */
    @Deprecated
    public FragmentManager getFragmentManager() {
        return mFragments.getFragmentManager();
    }
複製代碼

2. getSupportFragmentManager():

v4包下的這個方法,與上一個效果同樣,不過是Android推薦使用的方法(畢竟能夠兼容Android全部版本)

3. getChildFragmentManager():

咱們提過,Fragment更像是一個「子Activity」,那你說"子Activity"中可否再添加Fragment,答案固然是能夠。那麼在Fragment內部中的Fragment的管理器,就須要使用getChildFragmentManager()來獲取了。

2.2 FragmentTransaction相關

咱們能夠看到有添加刪除各類方法操做。

1. attach/detach方法:

  • detach(Fragment fragment) : 分離指定Fragment的UI視圖
  • attach(Fragment fragment) : 從新關聯一個Fragment(當這個Fragment的detach執行以後)
  1. 當Fragment被detach後,Fragment的生命週期執行完onDestroyView就終止了,這意味着Fragment的實例並無被銷燬,只是UI界面被移除了(注意和remove是有區別的)。
  2. 當Fragment被detach後,執行attach操做,會讓Fragment從onCreateView開始執行,一直執行到onResume。
  3. attach沒法像add同樣單獨使用,單獨使用會拋異常。方法存在的意義是對detach後的Fragment進行界面恢復。

2.add/remove方法:

我想這二個是用的最多的了,add()和remove()是將fragment添加和移除. remove()比detach()要完全一些, 若是不加入到回退棧中, remove()的時候, fragment的生命週期會一直走到onDetach();若是加入了回退棧,則會只執行到onDestoryView(),Fragment對象仍是存在的。

add一個fragment,若是加到的是同一個id的話,有點像咱們的Activity棧,啓動多個Activity時候,Activity一個個疊在上面,fragment也是相似,一個個fragment疊在上面。

3.replace方法:

replace = remove + add , 因此能夠理解爲先把相同id下的Fragment移除掉,而後再加入這個當前的fragment。

因此若是你以爲Fragment存在太多,影響性能,能夠用replace來切換各個界面,就能夠保證當前只有一個Fragment,可是由於每次切換後,Fragment都會重建,因此若是這個界面有網絡請求相關的,你就會發現這個界面又從新去請求網絡接口了,顯得不少此一舉。

4.hide/show方法:

就是字面意思,讓一個Fragment隱藏,讓一個Fragment顯示。你能夠理解爲Button設置了View.GONE和View.VISIBLE。經常配合有多個Fragment及有TAB等切換方式的時候,選中某個按鈕,而後根據相應的讓對應的Fragment顯示,其餘Fragment隱藏。

5.commit/commitAllowingStateLoss:

我估計不少人認識這個commitAllowingStateLoss大部分是由於本身的代碼有閃退異常:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
複製代碼

在你離開當前Activity等狀況下,系統會調用onSaveInstanceState()幫你保存當前Activity的狀態、數據等,直到再回到該Activity以前(onResume()以前),你執行Fragment事務,就會拋出該異常。而後網上有不少教程,叫你提交的時候使用commitAllowingStateLoss()方法,雖說不會拋出錯誤,可是若是在Activity已經保存狀態完以後提交了它,到時候Ativity意外崩潰,再恢復數據的時候就不會恢復在Activity保存狀態以後提交的fragment的更新,形成狀態丟失了。

額外補充:
1.commit()方法並不當即執行transaction中包含的動做,而是把它加入到UI線程隊列中. 若是想要當即執行,能夠在commit以後當即調用FragmentManager的executePendingTransactions()方法.

2. commit()方法必須在狀態存儲以前調用,不然會拋出異常,若是以爲狀態丟失不要緊, 能夠調用commitAllowingStateLoss(). 可是除非萬不得已, 通常不推薦用這個方法, 會掩蓋不少錯誤.

6. addToBackStack:

咱們能夠看到FragmentTransaction裏面有加入回退棧方法,可是沒有退出的方法:popBackStack。這是由於這個方法在FragmentManager裏面。

也就是以下圖:

通常反應是,addToBackStack和popBackStack不是應該像上面的相似add和remove同樣,都一個層級的嗎??因此popBackStack不也應該是FragmentTransaction下的一個方法???

因此咱們單從圖片所示就能知道,popBackStackFragmentTransaction是一個層級,因此popBackStack操做的其實也是《fragment事務》(FragmentTransaction),因此能夠理解爲addToBackStack把咱們前面的FragmentTransaction事務(好比add,remove,replace等一系列操做)加入到了回退棧(!!!記住不是把fragment加入到了回退棧),而popBackStack是操做回退棧裏面的事務。

固然具體的源碼過程分析,細講的話又是不少,均可以另外專門寫一篇文章,因此直接借鑑網上別人已經寫好的文章:

Fragment那點事①Fragment棧管理

額外補充:
1.加入回退棧:remove掉的fragment執行onDestoryView,並無執行onDestory,fragment實例對象仍是存在,當回退時候,fragment從onCreateView處執行

2. 未加入回退棧:remove掉的fragment 執行 onDestoryView和onDestory,完全銷燬移除


3.Fragment中獲取Context

咱們能夠直接在fragment代碼裏面直接使用getActivity()getContext()方法。

可是有時候獲取爲空,因此通常咱們使用的是:

Class xxxFragment extends Fragment {

    private Context mContext;
    
    //'高版本後,都是回調這個方法'
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mContext = context;
    }
    
    //'API低於 23 的版本的時候,是會回調這個方法'
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mContext = activity;
    }
}
複製代碼

4.Fragment配合ViewPager

ViewPager配合Fragment的時候,主要使用FragmentPagerAdapterFragmentStatePagerAdapter這二個Adapter。其實使用很簡單(通常的最最簡單的寫法):

public class FragmentAdapter extends FragmentPagerAdapter{
    private ArrayList<Fragment> list;

    //經過構造獲取fragment集合
    public Fragment_pager(FragmentManager fm,ArrayList<Fragment> list) {
        super(fm);
        this.list=list;
    }
    //設置具體position的fragment
    @Override
    public Fragment getItem(int position) {
        // TODO Auto-generated method stub
        return list.get(position);
    }
    //設置有多少個fragment
    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return list.size();
    }
}
複製代碼

而後ViewPager.setAdapter(xxxx);

可是你們會奇怪爲啥有二個Adapter:FragmentPagerAdapterFragmentStatePagerAdapter,他們的區別咱們能夠看具體的源碼:

FragmentPagerAdapter源碼:

public abstract class FragmentPagerAdapter extends PagerAdapter {


    //'初始化建立Item:'
    @NonNull
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (this.mCurTransaction == null) {
            this.mCurTransaction = this.mFragmentManager.beginTransaction();
        }

        long itemId = this.getItemId(position);
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = this.mFragmentManager.findFragmentByTag(name);
        if (fragment != null) {
        
            //'後面使用fragment是經過FragmentTransaction.attach方式加進來的,'
            //'只是從新繪製了UI,fragment對象還在。'
            this.mCurTransaction.attach(fragment);
        } else {
        
            //'咱們知道剛返回fragment使用的是getItem(position)方法'
            //'咱們能夠看到第一次使用fragment是經過FragmentTransaction.add方式加進來的'
            fragment = this.getItem(position);
            this.mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
        }

        if (fragment != this.mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
        }

        return fragment;
    }
    
    
    //'銷燬item:'
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        if (this.mCurTransaction == null) {
            this.mCurTransaction = this.mFragmentManager.beginTransaction();
        }
        
        //'咱們能夠看到FragmentTransaction只是單純的detach了fragment,視圖不在了,可是fragment對象還在'
        this.mCurTransaction.detach((Fragment)object);
    }
    
}
複製代碼

咱們能夠看到fragment並無真的銷燬,FragmentPageAdapter則適用於固定的,少許的Fragment狀況,例如和TabLayout共同使用時。

FragmentStatePagerAdapter源碼:

public abstract class FragmentStatePagerAdapter extends PagerAdapter {


    
    @NonNull
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        Fragment fragment;
        if (this.mFragments.size() > position) {
            fragment = (Fragment)this.mFragments.get(position);
            if (fragment != null) {
                return fragment;
            }
        }

        if (this.mCurTransaction == null) {
            this.mCurTransaction = this.mFragmentManager.beginTransaction();
        }

        fragment = this.getItem(position);
        if (this.mSavedState.size() > position) {
            SavedState fss = (SavedState)this.mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }

        while(this.mFragments.size() <= position) {
            this.mFragments.add((Object)null);
        }

        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        this.mFragments.set(position, fragment);
        
        //'咱們能夠看到fragment都是add進來的'
        this.mCurTransaction.add(container.getId(), fragment);
        return fragment;
    }
    
    
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment)object;
        if (this.mCurTransaction == null) {
            this.mCurTransaction = this.mFragmentManager.beginTransaction();
        }

        while(this.mSavedState.size() <= position) {
            this.mSavedState.add((Object)null);
        }

        this.mSavedState.set(position, fragment.isAdded() ? this.mFragmentManager.saveFragmentInstanceState(fragment) : null);
        this.mFragments.set(position, (Object)null);
        
        
        //'能夠看到都是經過remove的方式移除了'
        this.mCurTransaction.remove(fragment);
    }

    

}
複製代碼

因此咱們知道了FragmentStatePagerAdapter是真的會把fragment對象都銷燬,因此若是fragment數量不少的話,使用這個會更好,由於fragment存在太多,對應用性能形成很大影響,因此要remove掉fragment。


5.無UI的fragment:

5.1 使用Fragment 保持須要恢復對象

調用setRetainInstance(true)方法可保留fragment,以下:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
    ...........
}
複製代碼

好比旋轉屏幕,已保留的fragment不會隨着activity一塊兒被銷燬(但會銷燬fragment的視圖); 相反,它會一直保留(進程不消亡的前提下),並在須要時原封不動地傳遞給新的Activity。

因此咱們好比一些對象能夠保持在fragment中,這時候Activity從新恢復後,其餘對象能夠從fragment中找回。

能夠大概看下其餘做者文章介紹:

Fragment調用setRetainInstance的原理

5.2 相似RxPermission用於處理回調

RxPermission裏有一個Fragment用於分發權限回調。這個是什麼意思??

咱們知道原生請求權限:

//發出權限請求:
int requestCode = 1;
requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE},requestCode);


//權限處理結果回調
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
複製代碼

是否是以爲在要看複寫這個回調方法很麻煩???並且沒有美感。

而RxPermission是這樣申請權限的:

RxPermissions rxPermissions = new RxPermissions(this);

rxPermissions.requestEach(
        //請求的權限
        Manifest.permission.CAMERA,
        Manifest.permission.READ_PHONE_STATE)
        .subscribe(new Consumer<Permission>() {
        @Override
        public void accept(@io.reactivex.annotations.NonNull Permission permission) throws Exception {
            //權限通知回調
        }
});

複製代碼

感受就是一步呵成的感受,很棒。可是RxPermission只是對系統的原生權限申請作了封裝而已,那系統的本來的回調函數:onRequestPermissionsResult去哪裏了呢???

public class RxPermissionsFragment extends Fragment {
    
    .......
    .......
    .......
    
    //'申請權限'
    @TargetApi(23)
    void requestPermissions(@NonNull String[] permissions) {
        this.requestPermissions(permissions, 42);
    }
    
    //'申請權限後結果回調'
    @TargetApi(23)
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 42) {
            boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];

            for(int i = 0; i < permissions.length; ++i) {
                shouldShowRequestPermissionRationale[i] = this.shouldShowRequestPermissionRationale(permissions[i]);
            }

            this.onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale);
        }
    }
    
    //'回調後的具體處理方法'
    void onRequestPermissionsResult(String[] permissions, int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
        int i = 0;

        for(int size = permissions.length; i < size; ++i) {
            this.log("onRequestPermissionsResult " + permissions[i]);
            PublishSubject<Permission> subject = (PublishSubject)this.mSubjects.get(permissions[i]);
            if (subject == null) {
                Log.e(RxPermissions.TAG, "RxPermissions.onRequestPermissionsResult invoked but didn't find the corresponding permission request.");
                return;
            }

            this.mSubjects.remove(permissions[i]);
            boolean granted = grantResults[i] == 0;
            
            //'subject主動調用onNext方法發送結果'
            subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));
            subject.onComplete();
        }

    }

    
    ......
    ......
    ......  

 
}
複製代碼

咱們能夠到這個fragment內部已經幫咱們複寫了請求權限的原生方法和權限回調通知的原生方法。而後再經過subject在結果處發送通知便可。

這裏我不會細講整個RxPermission源碼,我之前寫過的相關文章,你們能夠具體看下:

項目需求討論 - 動態權限申請分析及相關第三方庫源碼分析

項目需求討論 — 手把手帶你寫RxPermission


6.構造函數和數據傳遞

6.1 構造函數傳遞數據

咱們知道fragment也就是普通的對象,能夠經過new的方式,咱們日常使用對象傳遞值都是能夠直接在構造函數裏面定義參數值,直接賦值進去,那fragment是否能夠這樣??答案是能夠的,可是不推薦。

public class FragmentOne extends Fragment {
    
    //'在其餘地方直接FragmentOne one = new FragmentOne("青蛙要fly");進行值傳遞'
    //'可是咱們不推薦這樣'
    public FragmentOne(String value) {

    }
    
    
    //'而是經過bundle來傳遞,Fragment.setArguments(Bundle)賦值進去'
    public static FragmentOne newInstance(Bundle args) {
        FragmentOne fragment = new FragmentOne();
        if(args != null){
            fragment.setArguments(args);    
        }
        return fragment;
    }
    
}
複製代碼

緣由:咱們能夠知道Activity從新建立時,會從新構建它所管理的Fragment,原先的Fragment的字段值將會所有丟失(由於當切換橫豎屏時,Fragment會調用本身的無參構造函數,那麼在構造函數傳參就會失效),可是經過 Fragment.setArguments(Bundle bundle)方法設置的bundle會保留下來,從而數據又能夠恢復,因此儘可能使用 Fragment.setArguments(Bundle bundle)方式來傳遞參數

6.2 其餘數據傳遞方式

Activity 與 Fragment 數據傳遞:

Fragment 與 Fragment 數據傳遞

重點說下setTargetFragment,由於不少人都不知道。

咱們的目標:FragmentA 啓動FragmentB ,而後FragmentB作完事情,返回結果給FragmentA

FragmentB.setTargetFragment(FragmentA);

而後在B中:
getTargetFragment().onActivityResult(getTargetRequestCode(), resultOK, i);


而後再FragmentA中:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
}

複製代碼

7. Fragment重建恢復數據

推薦下面這篇文章:

[譯] 保存/恢復 Activity 和 Fragment 狀態的最佳實踐

引用一段話:

徹底分開處理Fragment狀態和view狀態 爲了使你的代碼變得乾淨和可擴展,你最好把Fragment狀態和View狀態分開處理。若是這裏有任何屬性是屬於View的,在View內部進行保存和恢復.若是這裏有任何屬性是屬於Fragment的,在Fragment內部進行保存和恢復。


8.經常使用監聽Fragment顯示方法

這塊比較基礎,就不細講了。


9.監聽Fragment發生變化

回退棧(back stack)狀態改變監聽:

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
    
    }
});
複製代碼

註冊fragment的生命監聽:

List<Fragment> fragmentList = new ArrayList<>();

getActivity().getSupportFragmentManager().registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {
    @Override
    public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull View v, @Nullable Bundle savedInstanceState) {
        super.onFragmentViewCreated(fm, f, v, savedInstanceState);
        fragmentList.add(f);
    }

    @Override
    public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {
        super.onFragmentViewDestroyed(fm, f);
        fragmentList.remove(f);
    }
}, false);

//'這裏咱們注意下最後的代碼裏面的那個false,這個false的意思是:不遞歸碎片中的碎片了,就是碎片棧中的碎片'

複製代碼

10. DialogFragment:

咱們知道如今你們已經不多使用了Dialog類,而是使用了DialogFragment,其本質就是個Fragment。

其實這個原本也想多寫點,可是我估計這個基本安卓開發都使用過,因此就直接用網上其餘做者的基礎介紹文章:

Android 必知必會 - DialogFragment 使用總結

同時具體的自定義DialogFragment我之前文章也有寫過:

項目需求討論-仿ios底部彈框實現及分析


結語:

好久沒寫文章了。一看竟然都快半年了......後面準備慢慢的補起本身的博客。有錯誤的地方歡迎你們指出

相關文章
相關標籤/搜索