兩步搞定Fragment返回鍵

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)有更不可能有onKeyDownonKeyUp這樣的方法住拭,那么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);
    }
}

圖示

Fragment的back鍵處理原理

圖中紅色部分為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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绍填,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子栖疑,更是在濱河造成了極大的恐慌讨永,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遇革,死亡現(xiàn)場(chǎng)離奇詭異卿闹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)萝快,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門锻霎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人揪漩,你說(shuō)我怎么就攤上這事旋恼。” “怎么了氢拥?”我有些...
    開(kāi)封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵蚌铜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我嫩海,道長(zhǎng)冬殃,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任叁怪,我火速辦了婚禮审葬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己涣觉,他們只是感情好痴荐,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著官册,像睡著了一般生兆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膝宁,一...
    開(kāi)封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天鸦难,我揣著相機(jī)與錄音,去河邊找鬼员淫。 笑死合蔽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的介返。 我是一名探鬼主播拴事,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼圣蝎!你這毒婦竟也來(lái)了刃宵?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤捅彻,失蹤者是張志新(化名)和其女友劉穎组去,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體步淹,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡从隆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缭裆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片键闺。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖澈驼,靈堂內(nèi)的尸體忽然破棺而出辛燥,到底是詐尸還是另有隱情,我是刑警寧澤缝其,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布挎塌,位于F島的核電站,受9級(jí)特大地震影響内边,放射性物質(zhì)發(fā)生泄漏榴都。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一漠其、第九天 我趴在偏房一處隱蔽的房頂上張望嘴高。 院中可真熱鬧竿音,春花似錦、人聲如沸拴驮。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)套啤。三九已至宽气,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纲岭,已是汗流浹背抹竹。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工线罕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留止潮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓钞楼,卻偏偏與公主長(zhǎng)得像喇闸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子询件,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容