Fragment之Fragmentation庫(添加轉(zhuǎn)場動畫)

部分內(nèi)容來源于別人的總結(jié),如有冒犯侵權(quán),請告知! 郵箱:simoncqhy@163.com.謝謝!我只想做一個記錄,以免自己以后出現(xiàn)不知道怎么解決.

Fragmentation庫:非常適合單Activity+多Fragment 或者 多模塊Activity+多Fragment的架構(gòu)

來自一個github上的一個開源庫:
https://github.com/YoKeyword/Fragmentation

特性

1、快速開發(fā)出各種嵌套設(shè)計的Fragment App
2、實時查看Fragment的(包括嵌套Fragment)棧視圖的對話框和Log,方便調(diào)試
3、增加啟動模式、startForResult等類似Activity方法
4、類似Android事件分發(fā)機制的Fragment回退方法:onBackPressedSupport(),輕松為每個Fragment實現(xiàn)Back按鍵事件
5、New!?。?提供onSupportVisible()等生命周期方法,簡化嵌套Fragment的開發(fā)過程; 提供統(tǒng)一的onLazyInitView()懶加載方法
6、提供靠譜的 Fragment轉(zhuǎn)場動畫 的解決方案
7、更強的兼容性, 解決多點觸控、重疊等問題
8、支持SwipeBack滑動邊緣退出(需要使用Fragmentation_SwipeBack庫,
在使用fragment的過程中,我相信大家都遇到很多坑,在開發(fā)中,雖然谷歌推出了碎片化處理,但是里面本身還有很多bug
.

在開發(fā)中最容易出現(xiàn)的bug如下:

1、getActivity()空指針
2、異常:Can not perform this action after onSaveInstanceState
3、Fragment重疊異常-----正確使用hide、show的姿勢
4、Fragment嵌套的那些坑
5、未必靠譜的出棧方法remove()
6、多個Fragment同時出棧的深坑BUG
7、深坑 Fragment轉(zhuǎn)場動畫

getActivity()空指針

可能你遇到過getActivity()返回null,或者平時運行完好的代碼,在“內(nèi)存重啟”之后,調(diào)用getActivity()的地方卻返回null,報了空指針異常。
大多數(shù)情況下的原因:你在調(diào)用了getActivity()時,當(dāng)前的Fragment已經(jīng)onDetach()了宿主Activity。
比如:你在pop了Fragment之后,該Fragment的異步任務(wù)仍然在執(zhí)行,并且在執(zhí)行完成后調(diào)用了getActivity()方法,這樣就會空指針。
解決方案:
在Fragment基類里設(shè)置一個Activity mActivity的全局變量,在onAttach(Activity activity)里賦值,使用mActivity代替getActivity(),保證Fragment即使在onDetach后,仍持有Activity的引用(有引起內(nèi)存泄露的風(fēng)險,但是異步任務(wù)沒停止的情況下,本身就可能已內(nèi)存泄漏,相比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;
}

異常:Can not perform this action after onSaveInstanceState
在你離開當(dāng)前Activity等情況下,系統(tǒng)會調(diào)用onSaveInstanceState()幫你保存當(dāng)前Activity的狀態(tài)、數(shù)據(jù)等,直到再回到該Activity之前(onResume()之前),你使用commit()提交了Fragment事務(wù),就會拋出該異常!
解決方法2個:
1、(不推薦)該事務(wù)使用commitAllowingStateLoss()方法提交,但是有可能導(dǎo)致該次提交無效?。ㄔ诖舜坞x開時恰巧Activity被強殺時)
2、(推薦)在重新回到該Activity的時候(onResumeFragments()或onPostResume()),再執(zhí)行該事務(wù)!

Fragment重疊異常-----正確使用hide、show的姿勢和replace(坑相對來說要少些)

原因是FragmentManager幫我們管理Fragment,當(dāng)發(fā)生“內(nèi)存重啟”,他會從棧底向棧頂?shù)捻樞蛞淮涡曰謴?fù)Fragment;
但是因為沒有保存Fragment的mHidden屬性,默認(rèn)為false,即show狀態(tài),所以所有Fragment都是以show的形式恢復(fù),我們看到了界面重疊。
(如果是replace,恢復(fù)形式和Activity一致,只有當(dāng)你pop之后上一個Fragment才開始重新恢復(fù),所有使用replace不會造成重疊現(xiàn)象)

還有一種場景,add和replace都有可能造成重疊: 在onCreate中加載Fragment,并且沒有判斷saveInstanceState==null,導(dǎo)致重復(fù)加載了同一個Fragment導(dǎo)致重疊。(PS:replace情況下,如果沒有加入回退棧,則不判斷也不會造成重疊,但建議還是統(tǒng)一判斷下)

Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// 在頁面重啟時,F(xiàn)ragment會被保存恢復(fù),而此時再加載Fragment會重復(fù)加載,導(dǎo)致重疊 ;
if(saveInstanceState == null){
// 正常情況下去 加載根Fragment
}
}

這里給出3個解決方案:
1、是大家比較熟悉的 findFragmentByTag:
即在add()或者replace()時綁定一個tag,一般我們是用fragment的類名作為tag,然后在發(fā)生“內(nèi)存重啟”時,通過findFragmentByTag找到對應(yīng)的Fragment,并hide()需要隱藏的fragment。

下面是個標(biāo)準(zhǔn)恢復(fù)寫法:

Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
TargetFragment targetFragment;
HideFragment hideFragment;
if (savedInstanceState != null) { // “內(nèi)存重啟”時調(diào)用
targetFragment = getSupportFragmentManager().findFragmentByTag(TargetFragment.class.getName);
hideFragment = getSupportFragmentManager().findFragmentByTag(HideFragment.class.getName);
// 解決重疊問題
getFragmentManager().beginTransaction()
.show(targetFragment)
.hide(hideFragment)
.commit();
}else{ // 正常時
targetFragment = TargetFragment.newInstance();
hideFragment = HideFragment.newInstance();
getFragmentManager().beginTransaction()
.add(R.id.container, targetFragment, targetFragment.getClass().getName())
.add(R.id,container,hideFragment,hideFragment.getClass().getName())
.hide(hideFragment)
.commit();
}
}

Fragment嵌套的那些坑
在support 23.2.0以下的支持庫中,對于在嵌套子Fragment的startActivityForResult (),會發(fā)現(xiàn)無論如何都不能在onActivityResult()中接收到返回值,只有最頂層的父Fragment才能接收到,這是一個support v4庫的一個BUG,不過在前兩天發(fā)布的support 23.2.0庫中,已經(jīng)修復(fù)了該問題,嵌套的子Fragment也能正常接收到返回數(shù)據(jù)了

未必靠譜的出棧方法remove()
如果你想讓某一個Fragment出棧,使用remove()在加入回退棧時并不靠譜。
如果你在add的同時將Fragment加入回退棧:addToBackStack(name)的情況下,它并不能真正將Fragment從棧內(nèi)移除,如果你在2秒后(確保Fragment事務(wù)已經(jīng)完成)打印getSupportFragmentManager().getFragments(),會發(fā)現(xiàn)該Fragment依然存在,并且依然可以返回到被remove的Fragment,而且是空白頁面。
如果你沒有將Fragment加入回退棧,remove方法可以正常出棧。
如果你加入了回退棧,popBackStack()系列方法才能真正出棧,這也就引入下一個深坑,popBackStack(String tag,int flags)等系列方法的BUG。
多個Fragment同時出棧的深坑BUG
在Fragment庫中如下4個方法是有BUG的:

1、popBackStack(String tag,int flags)
2、popBackStack(int id,int flags)
3、popBackStackImmediate(String tag,int flags)
4、popBackStackImmediate(int id,int flags)

深坑 Fragment轉(zhuǎn)場動畫

如果你的Fragment沒有轉(zhuǎn)場動畫,或者使用setCustomAnimations(enter, exit)的話,那么上面的那些坑解決后,你可以愉快的玩耍了。

getFragmentManager().beginTransaction()
.setCustomAnimations(enter, exit)
// 如果你有通過tag/id同時出棧多個Fragment的情況時,
// 請謹(jǐn)慎使用.setCustomAnimations(enter, exit, popEnter, popExit)
// 因為在出棧多Fragment時,伴隨出棧動畫,會在某些情況下發(fā)生異常
// 你需要搭配Fragment的onCreateAnimation()臨時取消出棧動畫,或者延遲一個動畫時間再執(zhí)行一次上面提到的Hack方法,排序
(注意:如果你想給下一個Fragment設(shè)置進(jìn)棧動畫和出棧動畫,.setCustomAnimations(enter, exit)只能設(shè)置進(jìn)棧動畫,第二個參數(shù)并不是設(shè)置出棧動畫;
請使用.setCustomAnimations(enter, exit, popEnter, popExit),這個方法的第1個參數(shù)對應(yīng)進(jìn)棧動畫,第4個參數(shù)對應(yīng)出棧動畫,所以是.setCustomAnimations(進(jìn)棧動畫, exit, popEnter, 出棧動畫))

1、一些使用建議
1、對Fragment傳遞數(shù)據(jù),建議使用setArguments(Bundle args)
,而后在onCreate
中使用getArguments()
取出,在 “內(nèi)存重啟”前,系統(tǒng)會幫你保存數(shù)據(jù),不會造成數(shù)據(jù)的丟失。和Activity的Intent恢復(fù)機制類似。
2、使用newInstance(參數(shù))
創(chuàng)建Fragment對象,優(yōu)點是調(diào)用者只需要關(guān)系傳遞的哪些數(shù)據(jù),而無需關(guān)心傳遞數(shù)據(jù)的Key是什么。
3、如果你需要在Fragment中用到宿主Activity對象,建議在你的基類Fragment定義一個Activity的全局變量,在onAttach
中初始化,這不是最好的解決辦法,但這可以有效避免一些意外Crash。詳細(xì)原因參考第一篇的“getActivity()空指針”部分。

protected Activity mActivity;@Overridepublic void onAttach(Activity activity) { super.onAttach(activity); this.mActivity = activity;}

2、add(), show(), hide(), replace()的那點事
1、區(qū)別show()
,hide()
最終是讓Fragment的View setVisibility
(true還是false),不會調(diào)用生命周期;replace()
的話會銷毀視圖,即調(diào)用onDestoryView、onCreateView等一系列生命周期;
add()
和 replace()
不要在同一個階級的FragmentManager里混搭使用。
2、使用場景如果你有一個很高的概率會再次使用當(dāng)前的Fragment,建議使用show()
,hide()
,可以提高性能。
在我使用Fragment過程中,大部分情況下都是用show()
,hide()
,而不是replace()
。
注意:如果你的app有大量圖片,這時更好的方式可能是replace,配合你的圖片框架在Fragment視圖銷毀時,回收其圖片所占的內(nèi)存。
3、onHiddenChanged的回調(diào)時機當(dāng)使用add()
+show(),hide()
跳轉(zhuǎn)新的Fragment時,舊的Fragment回調(diào)onHiddenChanged()
,不會回調(diào)onStop()
等生命周期方法,而新的Fragment在創(chuàng)建時是不會回調(diào)onHiddenChanged()
,這點要切記。
4、Fragment重疊問題使用show()
,hide()
帶來的一個問題就是,如果你不做任何額外處理,在“內(nèi)存重啟”后,F(xiàn)ragment會重疊;(該BUG在support-v4 24.0.0+以上 官方已修復(fù))
有些小伙伴可能就是為了避免Fragment重疊問題,而選擇使用replace()
,但是使用show()
,hide()
時,重疊問題很簡單解決的:
如果你在用24.0.0+的版本,需要特殊處理,官方已經(jīng)修復(fù)該BUG;
如果你在使用小于24.0.0以下的v4包,可以參考9行代碼讓你App內(nèi)的Fragment對重疊說再見
4、使用FragmentPagerAdapter+ViewPager的注意事項

使用FragmentPagerAdapter+ViewPager時,切換回上一個Fragment頁面時(已經(jīng)初始化完畢),不會回調(diào)任何生命周期方法以及onHiddenChanged(),只有setUserVisibleHint(boolean isVisibleToUser)會被回調(diào),所以如果你想進(jìn)行一些懶加載,需要在這里處理。

在給ViewPager綁定FragmentPagerAdapter時,
new FragmentPagerAdapter(fragmentManager)的FragmentManager,一定要保證正確,如果ViewPager是Activity內(nèi)的控件,則傳遞getSupportFragmentManager(),如果是Fragment的控件中,則應(yīng)該傳遞getChildFragmentManager()。只要記住ViewPager內(nèi)的Fragments是當(dāng)前組件的子Fragment這個原則即可。

你不需要考慮在“內(nèi)存重啟”的情況下,去恢復(fù)的Fragments的問題,因為FragmentPagerAdapter已經(jīng)幫我們處理啦。

5、是使用單Activity+多Fragment的架構(gòu),還是多模塊Activity+多Fragment的架構(gòu)?

單Activity+多Fragment:

一個app僅有一個Activity,界面皆是Frament,Activity作為app容器使用。
優(yōu)點:性能高,速度最快。參考:新版知乎 、google系app
缺點:邏輯比較復(fù)雜,尤其當(dāng)Fragment之間聯(lián)動較多或者嵌套較深時,比較復(fù)雜。
多模塊Activity+多Fragment:
一個模塊用一個Activity,比如
1、登錄注冊流程:
LoginActivity + 登錄Fragment + 注冊Fragment + 填寫信息Fragment + 忘記密碼Fragment
2、或者常見的數(shù)據(jù)展示流程:
DataActivity + 數(shù)據(jù)列表Fragment + 數(shù)據(jù)詳情Fragment + ...
優(yōu)點:速度快,相比較單Activity+多Fragment,更易維護(hù)。
2017年5月17日
更新內(nèi)容
Fragment 想要實現(xiàn)切換頁面的時候進(jìn)行實時更新,我們都知道Activity中想要達(dá)到這種效果,我們只需要了解其生命周期就可以,只要activity沒有被銷毀,當(dāng)我們進(jìn)行頁面切換的時候,刷新網(wǎng)絡(luò)數(shù)據(jù),只需要在onResume()方法中執(zhí)行代碼就行,但是這種缺點就是,數(shù)據(jù)量太大會出現(xiàn)加載緩慢,甚至加載不出來,超時,對于這種只是適量于先少量數(shù)據(jù).
那如果在fragment中怎么實現(xiàn)呢?當(dāng)然這也是有辦法的,Fragment既然是Activity的一個碎片自然也是能夠?qū)崿F(xiàn)的.方法就不是一樣的了.官方文檔中給出了兩個方法:
方法一:

Paste_Image.png

這個方法官方文檔的解釋如下:
/**
* Set a hint to the system about whether this fragment's UI is currently visible
* to the user. This hint defaults to true and is persistent across fragment instance
* state save and restore.
*
* <p>An app may set this to false to indicate that the fragment's UI is
* scrolled out of visibility or is otherwise not directly visible to the user.
* This may be used by the system to prioritize operations such as fragment lifecycle updates
* or loader ordering behavior.</p>
*
* <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
* and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p>
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*/
Frament里面有一個setUserVisibleHint方法,setUserVisibleHint每次fragment顯示與隱藏都會調(diào)用
由于setUserVisibleHint優(yōu)于onCreate調(diào)用,所以當(dāng)onCreate調(diào)用完畢setUserVisibleHint就不會觸發(fā),使用這個方法可能會有一些問題,因為這個方法是在onCreate方法之后運行的,一進(jìn)來有時候可能看不到第一頁的數(shù)據(jù),這個時候
你可以在onCreate 或者是onCreateView里面進(jìn)行判斷
if (getUserVisibleHint()) {
//加載數(shù)據(jù)相當(dāng)于Fragment的onPause
}
這樣就能看到第一頁的數(shù)據(jù)了!
方法二:
public void onHiddenChanged(boolean hidden) {
}

官方解釋:
/**
* Called when the hidden state (as returned by {@link #isHidden()} of
* the fragment has changed. Fragments start out not hidden; this will
* be called whenever the fragment changes state from that.
* @param hidden True if the fragment is now hidden, false otherwise.
*/
意思就是說: 如果該Fragment對象已經(jīng)被隱藏,那么它返回true。默認(rèn)情況下,F(xiàn)ragment是被顯示的。能夠用onHiddenChanged(boolean)回調(diào)方法獲取該Fragment對象狀態(tài)的改變,要注意的是隱藏狀態(tài)與其他狀態(tài)是正交的---也就是說,要把該Fragment對象顯示給用戶,F(xiàn)ragment對象必須是被啟動并不被隱藏。
方法區(qū)別:
當(dāng)fragment結(jié)合viewpager使用時 setUserVisibleHint方法會調(diào)用,而onHiddenChanged方法不會調(diào)用.
如果沒有使用到viewpager setUserVisibleHint方法不會調(diào)用,而onHiddenChanged方法會調(diào)用.

重點部分———添加轉(zhuǎn)場動畫

   Fragment的轉(zhuǎn)場動畫一般有兩種,F(xiàn)ragment的設(shè)置需要在transaction.add 或transaction.remove之前。一種android提供了默認(rèn)方法,一種自定義動畫
      //淡入淡出的默認(rèn)動
       transaction = getSupportFragmentManager().beginTransaction();  transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);

//設(shè)置自定義過場動畫
transaction.setCustomAnimations(
R.anim.push_left_in,
R.anim.push_left_out,
R.anim.push_left_in,
R.anim.push_left_out);

動畫文件放置位置: res/anim: 這是兼容API-11以下的,只能有四種補間動畫方式
//push_left_in_no_alpha,acitivity轉(zhuǎn)場的時候用alpha會不好看
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="200"
android:fromXDelta="100%p"
android:toXDelta="0" />
</set>

//push_left_out_no_alpha
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="200"
android:fromXDelta="0"
android:toXDelta="-100%p" />
</set>

//push_right_in_no_alpha
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="200"
android:fromXDelta="-100%p"
android:toXDelta="0" />
</set>

//push_right_out_no_alpha
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="200"
android:fromXDelta="0"
android:toXDelta="100%p" />
</set>
用set標(biāo)簽的意思是可以集合多個動畫一起執(zhí)行,也可以自行選擇單個動畫,如alpha等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容