onBackPressed
即可,但Fragment可就没有这么幸运了,你可能和我一样,最开始有这样的需求的时候都会想去覆盖Fragment的onBackPressed
方法,但是事与愿违,Fragment中并没有这样的方法,不仅如此,Fragment也没有更不可能有onKeyDown
、onKeyUp
这样的方法,那么Fragment如何处理back键成难题。在此之前先卖个关子看看别人都是怎么实现的,看过的该方式的同学可以直接到最后。
别人的实现方式
注:出自优雅的让Fragment监听返回键
1、定义一个BackHandledInterface
public interface BackHandledInterface { public abstract void setSelectedFragment(BackHandledFragment selectedFragment); }
2、定义一个BackHandledFragment 抽象类继承Fragment并提供一个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实现第一步中定义的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显示时通知到Activity,并由Activity保持。
2、当用户按下Acitivity时,首先将back键请求交给Fragment处理,如果处理返回true
,未处理时返回false
。
3、如果Fragment没有处理则由Activity处理。
存在的问题
1、只适用于一个Activity上只有一个Fragment的情况。
2、只适用于没有Fragment嵌套的情况。
改进方式
1、将Activity中的BackHandledFragment 改为List<BackHandledFragment> 。
2、为保证Fragment存在嵌套的情况下也能正常使用,Fragment本身也要用List<BackHandledFragment> 持有 子可见Fragment的引用集合。
3、Fragment不可见时通知Activity或父Fragment移除。
4、当用户按下back键时遍历所有的可见Fragment,同样为了支持嵌套的情况Fragment本身也要遍历所有的
子可见Fragment。
虽然这样可以,但是这样太麻烦了,还得自己持有Fragment实例,难道就没有更好的方法?
新实现方式
其实我们根本不用去持有各个Fragment的实例,FragmentManager已经帮我们做了。
Activity中的有的Fragment由FragmentManager管理,Fragment嵌套的子Fragment也由FragmentManager处理,那只要拿到FragmentManager就可以用递归的方式处理了,等等,我好像发现了什么。
1、同样的先定义一个FragmentBackHandler
接口。
public interface FragmentBackHandler { boolean onBackPressed(); }
2、定义一个BackHandlerHelper
工具类,用于实现分发back事件,Fragment和Activity的外理逻辑是一样,所以两者都需要调用该类的方法。
public class BackHandlerHelper { /** * 将back事件分发给 FragmentManager 中管理的子Fragment,如果该 FragmentManager 中的所有Fragment都 * 没有处理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、当然 Fragment
也要实现 FragmentBackHandler
接口(按需)
//没有处理back键需求的Fragment不用实现 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(); } } }
BackHandledFragment
也可以让自己的BaseFragment
实现FragmentBackHandler接口(只在需要Fragmen中实现就行),并在onBackPressed
中用填入return BackHandlerHelper.handleBackPressed(this);
。allprojects { repositories { ... maven { url "https://jitpack.io" } } } dependencies { compile 'com.github.ikidou:FragmentBackHandler:2.1' }
当你需要自己处理back事件时覆盖onBackPressed
方法,如:
@Override public boolean onBackPressed() { // 当确认没有子Fragmnt时可以直接return false if (backHandled) { Toast.makeText(getActivity(), toastText, Toast.LENGTH_SHORT).show(); return true; } else { return BackHandlerHelper.handleBackPress(this); }
图示
![](http://upload-images.jianshu.io/upload_images/1724103-1576f745cc6bfb9d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
图中红色部分为BackHandledFragment
或其它实现了 FragmentBackHandler
的Fragment。
back事件由下往上传递,当中间有未实现FragmentBackHandler
的Fragment作为其它Fragment的容器时,或该Fragment拦截了事件时,其子Fragment无法处理back事件。
有没有一种似曾相识的感觉?其实这和View的事件分发机制是一个道理。
原理
1、不管是Activity
也好,Fragment
也好,其中内部包含的Fragment都是通过FragmentManager
来管理的。
2、FragmentManager.getFragments()
可以获取当前Fragment/Activity
中处于活动状态的所有Fragment
3、事件由Activity交给当前Fragment处理,如果Fragment有子Fragment的情况同样可以处理。
这么做的好处
1、Activity不必实现接口,仅需在onBackPressed
中调用BackHandlerHelper.handleBackPress(this)
即可,Fragment同理。
2、支持多个Fragment
3、支持Fragment嵌套
4、改动小,只修改有拦截back键需求的Fragment及其父Fragment,其它可以不动。
结语
本人不善言辞,也是第一次写博文,如有不对的地方请多指正,如果你有更好的办法请给我留言交流。
部分代码有删减,完整版请见Github:FragmentBackHandler