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的时候,会被自动注销,避免泄露

扫描二维码关注公众号,回复: 12648776 查看本文章

Chain of Responsibility

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

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

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

猜你喜欢

转载自blog.csdn.net/vitaviva/article/details/112134042