2022更新: 請(qǐng)使用官方最新的解決方案 OnBackPressedDispatcher
Fragment可以說(shuō)是在Android開(kāi)發(fā)必需要使用到技術(shù)鹿驼,項(xiàng)目中的界面基本上都是使用Fragment來(lái)實(shí)現(xiàn)合武,而Activity只是作為Fragment的載體淮野,但有些特殊情況下Fragment也不得不處理Back鍵,如果是Activity的話還好說(shuō)苛吱,直接覆蓋 Activity的
onBackPressed
即可,但Fragment可就沒(méi)有這么幸運(yùn)了器瘪,你可能和我一樣翠储,最開(kāi)始有這樣的需求的時(shí)候都會(huì)想去覆蓋Fragment的onBackPressed
方法绘雁,但是事與愿違,F(xiàn)ragment中并沒(méi)有這樣的方法援所,不僅如此庐舟,F(xiàn)ragment也沒(méi)有更不可能有onKeyDown
、onKeyUp
這樣的方法住拭,那么Fragment如何處理back鍵成難題挪略。
在此之前先賣個(gè)關(guān)子看看別人都是怎么實(shí)現(xiàn)的,看過(guò)的該方式的同學(xué)可以直接到最后滔岳。
別人的實(shí)現(xiàn)方式
注:出自優(yōu)雅的讓Fragment監(jiān)聽(tīng)返回鍵
1杠娱、定義一個(gè)BackHandledInterface
public interface BackHandledInterface {
public abstract void setSelectedFragment(BackHandledFragment selectedFragment);
}
2、定義一個(gè)BackHandledFragment 抽象類繼承Fragment并提供一個(gè)onBackPressed
方法,所有的Fragment都派生自該類
public abstract class BackHandledFragment extends Fragment {
protected BackHandledInterface mBackHandledInterface;
protected abstract boolean onBackPressed();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!(getActivity() instanceof BackHandledInterface)){
throw new ClassCastException("Hosting Activity must implement BackHandledInterface");
}else{
this.mBackHandledInterface = (BackHandledInterface)getActivity();
}
}
@Override
public void onStart() {
super.onStart();
mBackHandledInterface.setSelectedFragment(this);
}
}
3谱煤、Activity實(shí)現(xiàn)第一步中定義的BackHandledInterface
接口
public class MainActivity extends FragmentActivity implements BackHandledInterface{
private BackHandledFragment mBackHandedFragment;
private boolean hadIntercept;
@Override
public void setSelectedFragment(BackHandledFragment selectedFragment) {
this.mBackHandedFragment = selectedFragment;
}
@Override
public void onBackPressed() {
if(mBackHandedFragment == null || !mBackHandedFragment.onBackPressed()){
if(getSupportFragmentManager().getBackStackEntryCount() == 0){
super.onBackPressed();
}else{
getSupportFragmentManager().popBackStack();
}
}
}
}
原理分析
1摊求、利用Fragment的生命周期,在Fragment顯示時(shí)通知到Activity刘离,并由Activity保持室叉。
2、當(dāng)用戶按下Acitivity時(shí)硫惕,首先將back鍵請(qǐng)求交給Fragment處理茧痕,如果處理返回true
,未處理時(shí)返回false
。
3恼除、如果Fragment沒(méi)有處理則由Activity處理凿渊。
存在的問(wèn)題
1、只適用于一個(gè)Activity上只有一個(gè)Fragment的情況缚柳。
2埃脏、只適用于沒(méi)有Fragment嵌套的情況。
改進(jìn)方式
1秋忙、將Activity中的BackHandledFragment 改為L(zhǎng)ist<BackHandledFragment> 彩掐。
2、為保證Fragment存在嵌套的情況下也能正常使用灰追,F(xiàn)ragment本身也要用List<BackHandledFragment> 持有 子可見(jiàn)Fragment的引用集合堵幽。
3、Fragment不可見(jiàn)時(shí)通知Activity或父Fragment移除弹澎。
4朴下、當(dāng)用戶按下back鍵時(shí)遍歷所有的可見(jiàn)Fragment,同樣為了支持嵌套的情況Fragment本身也要遍歷所有的
子可見(jiàn)Fragment苦蒿。
雖然這樣可以殴胧,但是這樣太麻煩了,還得自己持有Fragment實(shí)例,難道就沒(méi)有更好的方法?
新實(shí)現(xiàn)方式
其實(shí)我們根本不用去持有各個(gè)Fragment的實(shí)例团滥,
FragmentManager
已經(jīng)幫我們做了竿屹。
Activity中的有的Fragment由FragmentManager
管理,F(xiàn)ragment嵌套的子Fragment也由FragmentManager
處理灸姊,那只要拿到FragmentManager
就可以用遞歸的方式處理了拱燃,等等,我好像發(fā)現(xiàn)了什么力惯。
1碗誉、同樣的先定義一個(gè)FragmentBackHandler
接口。
public interface FragmentBackHandler {
boolean onBackPressed();
}
2父晶、定義一個(gè)BackHandlerHelper
工具類哮缺,用于實(shí)現(xiàn)分發(fā)back事件,Fragment和Activity的外理邏輯是一樣,所以兩者都需要調(diào)用該類的方法诱建。
public class BackHandlerHelper {
/**
* 將back事件分發(fā)給 FragmentManager 中管理的子Fragment蝴蜓,如果該 FragmentManager 中的所有Fragment都
* 沒(méi)有處理back事件,則嘗試 FragmentManager.popBackStack()
*
* @return 如果處理了back鍵則返回 <b>true</b>
* @see #handleBackPress(Fragment)
* @see #handleBackPress(FragmentActivity)
*/
public static boolean handleBackPress(FragmentManager fragmentManager) {
List<Fragment> fragments = fragmentManager.getFragments();
if (fragments == null) return false;
for (int i = fragments.size() - 1; i >= 0; i--) {
Fragment child = fragments.get(i);
if (isFragmentBackHandled(child)) {
return true;
}
}
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
return true;
}
return false;
}
public static boolean handleBackPress(Fragment fragment) {
return handleBackPress(fragment.getChildFragmentManager());
}
public static boolean handleBackPress(FragmentActivity fragmentActivity) {
return handleBackPress(fragmentActivity.getSupportFragmentManager());
}
/**
* 判斷Fragment是否處理了Back鍵
*
* @return 如果處理了back鍵則返回 <b>true</b>
*/
public static boolean isFragmentBackHandled(Fragment fragment) {
return fragment != null
&& fragment.isVisible()
&& fragment.getUserVisibleHint() //for ViewPager
&& fragment instanceof FragmentBackHandler
&& ((FragmentBackHandler) fragment).onBackPressed();
}
}
3俺猿、當(dāng)然 Fragment
也要實(shí)現(xiàn) FragmentBackHandler
接口(按需)
//沒(méi)有處理back鍵需求的Fragment不用實(shí)現(xiàn)
public abstract class BackHandledFragment extends Fragment implements FragmentBackHandler {
@Override
public boolean onBackPressed() {
return BackHandlerHelper.handleBackPress(this);
}
}
4茎匠、Activity覆蓋onBackPressed
方法(必須)
public class MyActivity extends FragmentActivity {
//.....
@Override
public void onBackPressed() {
if (!BackHandlerHelper.handleBackPress(this)) {
super.onBackPressed();
}
}
}
不是說(shuō)好的兩步么,這TM是4步把号邸诵冒!大哥不要生氣,第一步和第二步我都給你做了谊惭,你只要在Gradle中加入以下的話以及第3汽馋、4步即可。你可以使用我提供的BackHandledFragment
也可以讓自己的BaseFragment
實(shí)現(xiàn)FragmentBackHandler接口(只在需要Fragmen中實(shí)現(xiàn)就行)圈盔,并在onBackPressed
中用填入return BackHandlerHelper.handleBackPressed(this);
豹芯。
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
dependencies {
compile 'com.github.ikidou:FragmentBackHandler:2.1'
}
當(dāng)你需要自己處理back事件時(shí)覆蓋onBackPressed
方法,如:
@Override
public boolean onBackPressed() {
// 當(dāng)確認(rèn)沒(méi)有子Fragmnt時(shí)可以直接return false
if (backHandled) {
Toast.makeText(getActivity(), toastText, Toast.LENGTH_SHORT).show();
return true;
} else {
return BackHandlerHelper.handleBackPress(this);
}
}
圖示
圖中紅色部分為BackHandledFragment
或其它實(shí)現(xiàn)了 FragmentBackHandler
的Fragment。
back事件由下往上傳遞驱敲,當(dāng)中間有未實(shí)現(xiàn)FragmentBackHandler
的Fragment作為其它Fragment的容器時(shí)铁蹈,或該Fragment攔截了事件時(shí),其子Fragment無(wú)法處理back事件众眨。
有沒(méi)有一種似曾相識(shí)的感覺(jué)?其實(shí)這和View的事件分發(fā)機(jī)制是一個(gè)道理握牧。
原理
1、不管是Activity
也好娩梨,Fragment
也好沿腰,其中內(nèi)部包含的Fragment都是通過(guò)FragmentManager
來(lái)管理的。
2狈定、FragmentManager.getFragments()
可以獲取當(dāng)前Fragment/Activity
中處于活動(dòng)狀態(tài)的所有Fragment
3颂龙、事件由Activity交給當(dāng)前Fragment處理,如果Fragment有子Fragment的情況同樣可以處理。
這么做的好處
1厘托、Activity不必實(shí)現(xiàn)接口友雳,僅需在onBackPressed
中調(diào)用BackHandlerHelper.handleBackPress(this)
即可稿湿,F(xiàn)ragment同理铅匹。
2、支持多個(gè)Fragment
3饺藤、支持Fragment嵌套
4包斑、改動(dòng)小,只修改有攔截back鍵需求的Fragment及其父Fragment涕俗,其它可以不動(dòng)罗丰。
結(jié)語(yǔ)
本人不善言辭,也是第一次寫博文再姑,如有不對(duì)的地方請(qǐng)多指正萌抵,如果你有更好的辦法請(qǐng)給我留言交流。
部分代碼有刪減元镀,完整版請(qǐng)見(jiàn)Github:FragmentBackHandler