Android Fragment监听返回键的一种合理方式

开场

以下场景为杜撰:

产品经理:“小罗,这个信息发送界面,如果用户输入了内容,点击返回键的时候,要先询问用户是否保存草稿箱哈”。

小罗:“收到,这问题简单。”

说完小罗就准备着手处理,然后却发现信息编辑界面是一个Fragment,然而Fragment并没有提供返回键点击的直接处理;小罗虽菜,但是摸鱼也摸了些年头了,这问题难不倒小罗。

小罗心想,反正Activity提供了onBackPressed方法,再不济的情况把这个操作分发到Fragment中去就好,可是对于处女座的小罗来说,在解决问题的基础上,起码代码要写的漂亮一点,写的漂亮一点心里就舒服一点,心里舒服一点就...(此处内容很长)。

小罗坚信“条条大路通罗马”,我们不仅要到罗马,还要风风光光的去,所以对于“Fragment如何监听返回键的点击”,小罗决定下点功夫;

为什么关注的点是Fragment去监听返回键,而不是其他?其实在现在的开发过程中,Fragment的使用比重是非常大的,对于个人而言,几乎整个工程的界面实现都是基于Fragment而非Activity。

一、最lowB的方式(不推荐)

这就是小罗心里的预备方案,在实在没有办法的时候会采用此方法,也就是前面提到的,我们可以在Activity执行onBackPressed时,分发到Fragment中去;那我们用什么来分发呢?这个分发就好比是连接Activity和Fragment之间的一个纽带,双方均能够访问到这个对象就可以了,所以一个可以的选择之一是使用ViewModel,当然还可以有其他选择,在此就不细聊了。

二、使用OnKeyListener(不推荐)

这种方式可能不常用,不容易想到这方面,所以这种方式也不推荐,简单做个了解;

通过设置View的OnKeyListener来监听返回键的处理,此方法也没什么大的弊端,只是要注意以下两点:

1、如果把这个功能封装在Fragment基类中的话,可能存在被覆盖的问题;比如在基类中设置了OnKeyListener,而子类也需要设置OnKeyListener,此时设置的监听则会替换默认设置的监听,从而导致意想不到的可能,不过此问题几乎不太可能发生。

2、需要注意这种方式将会改变返回键处理的顺序,也就是会先处理OnKeyListener的回调,再处理Activity的onBackPressed,所以要注意这个关系。

三、Jetpack提供的方式

其实对于返回键的分发,官方已经做了支持,在Activity中提供了一个用于分发返回键事件的对象,通过调用Activity的getOnBackPressedDispatcher()方法得到这个对象,由于这个对象是在比较底层的androidx.activity.ComponentActivity中提供的(AppCompatActivity->FragmentAcitivty->androidx.activity.ComponentActivity),所以在Fragment中可以直接拿到这个对象添加回调;

官方资料入口

//官方使用示例
public class FormEntryFragment extends Fragment {
 @Override
 public void onAttach(@NonNull Context context) {
  super.onAttach(context);
  //定义回调
  OnBackPressedCallback callback = new OnBackPressedCallback(
   true // default to enabled
  ) {
   @Override
   public void handleOnBackPressed() {
    showAreYouSureDialog();
   }
  };

  //获取Activity的返回键分发器添加回调
  requireActivity().getOnBackPressedDispatcher().addCallback(
   this, // LifecycleOwner
   callback);
 }
}

简单明了,这个事情好像到此为止了~~

但随着深入了解,事情似乎没有这么简单,经过源码分析和资料收集,发现如果直接使用会存在以下弊端:

1、Fragment回调处理时,无法向上传递

2、回调是否可用需要主动标记,而非运行时确定

简单说一下OnBackPressedDispatcher分发返回键的流程:

	//官方源码
 @MainThread
 public void onBackPressed() {
  Iterator<OnBackPressedCallback> iterator =
    mOnBackPressedCallbacks.descendingIterator();
  while (iterator.hasNext()) {
   OnBackPressedCallback callback = iterator.next();
   if (callback.isEnabled()) {
    callback.handleOnBackPressed();
    return;
   }
  }
  if (mFallbackOnBackPressed != null) {
   mFallbackOnBackPressed.run();
  }
 }

当分发返回键事件时,会倒序循环遍历已经注册的回调,如果回调isEnabled设置为true,则执行回调的方法,分发结束;

那前面提到的弊端是怎么产生的呢?假如一个Activity有两个Fragment A和B,均注册了返回键点击事件(有童鞋会说了,这种场景不太可能存在,确实,这种场景是不多,但不代表没有,做一些了解也不是坏事),并且两个回调的isEnabled均设置为true,那么当分发事件时,会将事件分发给B,但是B此时并不需要处理返回键事件,但是B又没有办法再继续将事件传递给A了;

“你傻啊,你B不执行返回键事件,就设置isEnable为false啊”

“是啊,B不执行事件是该设置为false,可是我怎么知道什么时候去把它设置成false?难道动态绑定判断条件的值进行设置么?”

转头一想“咦,好像确实可以动态修改回调的isEnabled值呢,将回调的值跟一个LiveData绑定不就可以了么!”
理是这个理,但是我不愿意做额外的工作,我不愿这么干,谁知道动态判断条件到底有多复杂呢,难道我不可以在返回键点击的时候去判断么?

四、灵机一动,官方升级版(推荐方式)

官方的方式不是存在上面两个弊端么,解决这两个问题不就好了;所以结合官方OnBackPressedDispatcher和OnKeyListener两者的优点,创建了andme.arch.activity.AMBackPressedDispatcher,在保留官方原有的功能的同时,更改事件分发流程,并将返回键持有者一并传入,用于解决一些更复杂一点的需求;

 @MainThread
 fun onBackPressed(): Boolean {
  if (!hasRegisteredCallbacks())
   return false

  val iterator = mOnBackPressedCallbacks.descendingIterator()
  while (iterator.hasNext()) {
   val callback = iterator.next()
   //判断回调是否需要消耗事件在决定是否继续传递
   if (callback.handleOnBackPressed(owner)) {
    return true
   }
  }
  return false
 }

五、官方使用技巧版

这种方法其实是我在发布文章之后,群友提供的一种思路,说实话,非常有技巧,刚开始看到的时候眼前一亮;其核心原理是默认注册的回调是可用的,在回调执行中,先判断自己是否需要执行回调,如果不需要执行回调,则将自己的isEnabled设置为false,然后再调用OnBackPressedDispatcher重新分发返回键事件(由于此时已将自己设置为false,此时便不会响应回调),调用方法之后再将isEnabled设置为true,巧用了递归,该方式不错的;

最开始群友提供的代码有一丢丢瑕疵,以下为修正之后的代码,在Fragment中定义这两个方法,在需要绑定返回键监听的时候调用这个两个方法之一即可(推荐调用与生命周期相关的方法);

fun addOnBackPressed(onBackPressed: () -> Boolean): OnBackPressedCallback {
  val callback = object : OnBackPressedCallback(true) {
   override fun handleOnBackPressed() {
    if (!onBackPressed()) {
     isEnabled = false
     requireActivity().onBackPressedDispatcher.onBackPressed()
     isEnabled = true
    }
   }
  }
  requireActivity().onBackPressedDispatcher.addCallback(callback)
  return callback
 }

 fun addOnBackPressed(owner: LifecycleOwner, onBackPressed: () -> Boolean): OnBackPressedCallback {
  val callback = object : OnBackPressedCallback(true) {
   override fun handleOnBackPressed() {
    if (!onBackPressed()) {
     isEnabled = false
     requireActivity().onBackPressedDispatcher.onBackPressed()
     isEnabled = true
    }
   }
  }
  requireActivity().onBackPressedDispatcher.addCallback(owner,callback)
  return callback
 }

但是经过慎重思考,最终我还是没有用这种方法,虽然这种方法在几乎百分之八九十的情况下是没有问题的,但是我认为可能还是有场景无法满足;

举个例子,一个Activity添加了一个Fragment,这个Fragment又顺序添加了A和B两个ChildFragment,那在B执行返回处理的时候,是想回到A还是finish呢?或者是其他呢,也是就是说我们无法确定,在Fragment执行返回键处理时,是否需要直接调用Activity.super.onBackPressed方法的可能。

我们永远无法预估用户的场景到底有多复杂,需求有多变态,所以尽可能的考虑把。

总结

综上所述,我目前还是会继续使用第四种我写的方案,第五种方案也推荐,毕竟在绝大部分场景中都是没有问题的
那么我们考虑第四种方案到底是否可行?

1、功能性

满足了功能需求,并且至少目前是没有想到有任何可能出现问题的场景

2、侵入性

几乎对用户场景没什么影响吧,只是对用户提供了一个可见的处理返回键事件的方法而已

3、替换性

如果采用第四种方案,要更换成第五种方案,容易么?一两句代码的事情而已

或者更换成其他方案容易么?也是一两句代码的的事情而已

并且即便替换成其他方案,也不会对现有系统造成任何影响,因为对于Fragment监听返回键这个需求来讲,这个需求的核心就是需要一个在Fragment中处理返回键事件的方法而已,其他东西对用户来讲都是无感的
所以总体觉得没什么毛病;

如果你有更好的思路,欢迎沟通,不胜感激;

另外,上述功能其实并不仅仅支持在Fragment中处理返回键事件,理论上来说任何想要监听返回键处理的都可以通过Activity获取AMBackPressedDispatcher对象添加回调即可。

Andme Github地址

到此这篇关于Android Fragment监听返回键的一种合理方式的文章就介绍到这了,更多相关Android Fragment监听返回键内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android Fragment实现底部通知栏

    Android Fragment实现底部通知栏,供大家参考,具体内容如下 截图如下: 1. 第一步先要创建fragment(动态注册) 然后将两个勾选取消掉(还有一种是自己手动创建) 会自动生成相对应的layout布局,剩下的要根据自己的需求了 2.在Activity的布局里写好四个按钮 这里不是重点- <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android=&quo

  • 详解Android studio 动态fragment的用法

    fragment的使用时Android的基础,它有两种用法,第一个就是静态的fragment.第二个则是动态的fragment. 静态fragment直接在layout创建你想要的fragment的XML的文件,然后在你的Java包里面创建对应fragment的class文件 布局代码如下所示 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http:

  • Android底部菜单栏(RadioGroup+Fragment)美化

    众所周知,android的底部菜单栏太重要,平时项目一般都是需要用到的,但是网上关于这方面的demo做得太丑了,实在惨不忍睹,所以这里便用RadioGroup+Fragment的方式写了一个,顺便美化了一下,需要的可以看下. 效果图: 项目结构 MainActivity.java public class MainActivity extends AppCompatActivity { private FrameLayout frameLayout; private RadioGroup rad

  • AndroidX下使用Activity和Fragment的变化详解

    过去的一段时间,AndroidX 软件包下的 Activity/Fragmet 的 API 发生了很多变化.让我们看看它们是如何提升Android 的开发效率以及如何适应当下流行的编程规则和模式. 本文中描述的所有功能现在都可以在稳定的 AndroidX 软件包中使用,它们在去年均已发布或移至稳定版本. 在构造器中传入布局 ID 从 AndroidX  AppCompat 1.1.0 和 Fragment 1.1.0 ( 译者注:AppCompat 包含 Fragment,且 Fragment

  • Android实现面包屑功能的代码(支持Fragment联动)

    由于UI小姐姐给的设计图中包含了面包屑效果,去github逛了一圈,没有特别合适的,只能自己实现了. 先看下效果图: 先看下逐个添加Fragment,然后按返回键挨个回退的场景: 接着看下逐个添加Fragment,直接点击选择tab的场景: demo地址 BreadCrumbsView 使用方式 1.布局中引入BreadCrumbsView <com.tinytongtong.breadcrumbs.BreadCrumbsView android:id="@+id/breadCrumbs&

  • Android 之BottomsheetDialogFragment仿抖音评论底部弹出对话框效果(实例代码)

    实现的效果图: 自定义Fragment继承BottomSheetDialogFragment 重写它的三个方法: onCreateDialog() onCreateView() onStart() 他们的执行顺序是从上到下 import android.app.Dialog; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable;

  • Android在fragment中编写toobar的步骤详解

    第一步的话就是首先导入我们的依赖的包: compile 'com.android.support:appcompat-v7:23.3.0' 第二步的话就是准备我们的布局文件和我们的item 在这的话我是将我们的toobar单独的放在一个布局文件中的方便以后的调用以及将我们的主题改为 我们noactionbar,同时在我们的主文件中进行引用 修改为nopactionbar 引用 设置单独的xml文件 然后的话就是我们在我们的这个位置设置的是我们的啊就是toobar的单独的一个文件代码如下: <?x

  • Android Fragment使用全解

    我们都知道,Android上的界面展示都是通过Activity实现的,Activity实在是太常用了,我相信大家都已经非常熟悉了,这里就不再赘述. 但是Activity也有它的局限性,同样的界面在手机上显示可能很好看,在平板上就未必了,因为平板的屏幕非常大,手机的界面放在平板上可能会有过分被拉长.控件间距过大等情况.这个时候更好的体验效果是在Activity中嵌入"小Activity",然后每个"小Activity"又可以拥有自己的布局.因此,我们今天的主角Frag

  • Android使用Fragment实现兼容手机和平板的程序

    一 记得我之前参与开发过一个华为的项目,要求程序可以支持好几种终端设备,其中就包括 Android 手机和 Android Pad.然后为了节省人力,公司无节操地让 Android 手机和 Android Pad 都由我们团队开发.当时项目组定的方案是,制作两个版本的 App,一个手机版,一个 Pad 版.由于当时手机版的主体功能已经做的差不多了,所以 Pad 版基本上就是把手机版的代码完全拷过来,然后再根据平板的特性部分稍作修改就好了. 但是,从此以后我们就非常苦逼了.每次要添加什么新功能,同

  • androidx下的fragment的lazy懒加载问题详解

    网上关于androidx的fragment懒加载文章已经有很多,各有侧重.几乎都点到了sexMaxLifecycle和修改FragmentPagerAdapter.很少看到经过实践的文章,谨以此文,更加详尽的把实践后的结果记录下来,赠予有缘人. 一.前置准备工作 几个关于androidx的fragment懒加载方案,都离不开如下几个包: androidx.fragment:fragment:1.1.0-alpha07 以上,支持setMaxLifecycle方法即可 androidx.viewp

随机推荐