Fragment中处理OnBackPressed的正确姿势

Fragment无法像Activity那样重写OnBackPressed方法,所以拦截Back键相对比较复杂,但这又是一个常见需求,例如以下场景:

在SearchFragment中,点击back键时,关闭SearchView而非直接退出

使用FragmentManger


一个常见做法是定义一个带有OnBackPressed的方法的接口或基类给Fragment继承:

interface BackPressHandler {
    fun onBackPressed(): Boolean
}

然后,Fragment实现onBackPressed

class SearchFragment : Fragment(), BackPressHandler{
	...
	override fun onBackPressed(): Boolean {
	    if (searchBar.focusedChild == null) {
    	    updateFocus(FocusSearchAction.BACK_PRESSED)
        	return true
    	}
    	return false
	}
	...
} 

最后,在Activity中通过FragmentManager获取所有栈顶Fragment,然后分发Back事件

//MainActivity.kt

override fun onBackPressed() {
    if (!isTopFragmentConsumedBackPress()) {
        super.onBackPressed()
    }
}

private fun isTopFragmentConsumedBackPress() = getTopFragment<BackPressHandler>()?.onBackPressed() == true

getTopFragment的实现如下:

inline fun <reified T> FragmentActivity.getTopFragment(): T? 
        = supportFragmentManager.fragments.firstOrNull()?.let { it as? T }

使用OnBackPressedDispatcher


如果你的项目正在使用androdx,androidx.activity给我们提供了另一个实现方案:
ComponentActivity(FragmentActivity 和 AppCompatActivity的基类)提供了OnBackPressedDispatcher用来设置callback,然后在handleOnBackPressed中拦截处理back事件

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            // in here you can do logic when backPress is clicked
        }
    })
}

OnBackPressedCallback可以设置isEnable,当isEnable为true时才会进行事件拦截。
如下,我们只是在特定条件下做一次处理,之后不再处理,此时可以在else中设置isEnable为false:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
          if(shouldInterceptBackPress()){
            // in here you can do logic when backPress is clicked
          }else{
            isEnabled = false
            activity?.onBackPressed()
          }
        }
    })
}

使用OnBackPressedDispatcher无需再定义额外接口和基类。但是它最大的有优点是可以感知生命周期

LifecycleAware

看一下addCallback的定义就知道:

 public void addCallback(@NonNull LifecycleOwner owner,
            @NonNull OnBackPressedCallback onBackPressedCallback) {
        Lifecycle lifecycle = owner.getLifecycle();
        if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
            return;
        }

        onBackPressedCallback.addCancellable(
                new LifecycleOnBackPressedCancellable(lifecycle, onBackPressedCallback));
    }

onBackPressedCallback会被封装成LifecycleOnBackPressedCancellable,他接受一个LifecycleOwner,只有当Lifecycle.State.STARTED时候,callback才会被真正注册,反之当destroyed的时候,会被自动注销,避免泄露

Chain of Responsibility

所有的Fragment,无论嵌套层级有多深,都基于同一个Activity添加callback,这要求back事件的消费具有顺序性:
依次添加了三个callback A,B,C,则事件被消费的顺序是C,B,A,即逆序消费。
当Framgent嵌套时,能保证叶子节点的Fragment最先消费

这种设计模式是常见的责任链模式。

需要注意调用 addCallback()时,callback并非立即注册到责任链,只有当LifecycleOwner 为Lifecycle.State.STARTED 时才会添加,这在前面已经讲过。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fundroid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值