亲宝软件园·资讯

展开

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

Andme 人气:6
想了解Android Fragment监听返回键的一种合理方式的相关内容吗,Andme在本文为您仔细讲解Android Fragment监听返回键的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:fragment怎样使用,android,fragment监听返回键,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地址

加载全部内容

相关教程
猜你喜欢
用户评论