常见的方案:
方案一:
activity中包含fragment实例的引用,在activity中的onBackPressed()中调用fragment的方法,传递返回键事件。
缺点:增加了activity和fragment之间的耦合性;当有子fragment时,一级级的向下传递也稍显麻烦。
方案二:
Google官方提供的onBackPressedDispatcher,该类支持直接在fragment中监听返回键事件。
缺点:
-
需要使用isEnable()方法来手动管理对返回键的监听,稍显麻烦.
-
每一个fragment都需要监听返回键逻辑,然而监听的顺序决定了,返回键事件的分发顺序,所以事件的分发顺序得不到保证。
-
fragment再订阅后,即使页面不可见了,也能收到返回键事件。
理想中的方案:
- activity和fragment,fragment之间拒绝耦合。
- 返回键事件的分发顺序,应该让子fragment先处理,如果不处理再传递给父fragment处理,类似View触摸事件的分发顺序。
- 当fragment不可见时,不应该接受到返回键事件。
- 交互上要和activity的onBackPressed()类似,并且通过返回值表示要不要消费该事件。
那么要实现上述效果,有两种可行的封装方案:
方案一(继承):
在BaseFragemnt中使用onBackPressedDispatcher监听返回键,然后留出 onFragmentBackPressed()方法给子类实现,通过返回值判断是否消费事件。同时控制不可见的fragment不分发事件。
使用时只需要继承BaseFragemnt类,实现onFragmentBackPressed()就可以。
上面的封装基本能解决需求啦,不过由于使用的是继承,所以也有一些不太完美的地方 比如:
-
需要对原有项目的BaseFragemnt做较大的改动,如果有多个BaseFragemnt都要同步修改;
-
项目中需要处理返回键的fragment属于少数,所以也就让其他Fragment被迫继承了它们用不着的功能。
所以就有了一个经典的设计原则:多用组合,少用继承
那么再尝试使用组合方式进行封装,把逻辑都封装到一个新类中,哪里需要哪里引用,更为灵活。
方案二(组合):
主要逻辑步骤:
- 获取和监听activity中所有的fragment变动。
- 把获得的fragment列表转化为fragment树(根据fragment间的父子关系)。
- 从树的顶节点(activity)开始向可见的fragment分发返回键事件。
先看下怎么使用
kotlin
//MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_backpress_test)
BackPressDispatcher(this).init() //再activity中调用初始化方法
}
kotlin
//再需要处理返回键的fragment中继承接口FragmentBackPressListener,实现方法onFragmentBackPressed
class BackPressTestFragment01: Fragment(), FragmentBackPressListener {
override fun onFragmentBackPressed(): Boolean {
Log.e(TAG, "${TAG} 收到了返回键事件")
return false //返回值代表是否消费事件
}
}
使用起来挺简单吧!
代码实现也很简单 如下:
kotlin
import android.content.Context
import android.util.Log
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
class BackPressDispatcher(val hostActivity: FragmentActivity) {
private val TAG = "BackPressDispatcher"
private val rootPageNode = PageNode(null)
private val fragmentPageNodeMap = hashMapOf<Fragment, PageNode>()
fun init(){
val fragmentManager = hostActivity.supportFragmentManager
transferFragments2PageNodeTree(getAllFragments(fragmentManager))
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
hostActivity.onBackPressedDispatcher.addCallback(hostActivity, backStackChangeListener)
}
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) {
super.onFragmentAttached(fm, f, context)
Log.e(TAG, "onFragmentAttached")
transferFragments2PageNodeTree(arrayListOf(f))
}
override fun onFragmentDetached(fm: FragmentManager, f: Fragment) {
super.onFragmentDetached(fm, f)
removeFromPageNodeTree(f)
}
}
private val backStackChangeListener = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if(!dispatchBackPressEvent(rootPageNode)){
isEnabled = false
hostActivity.onBackPressed()
isEnabled = true
}
}
}
private fun dispatchBackPressEvent(pageNode: PageNode): Boolean{
//不可见的fragment不参与事件分发
if(pageNode.currFragment?.isResumed == false) return false
//先派发给子fragment处理
pageNode.getChildFragments().forEach {
fragmentPageNodeMap[it]?.let { childPageNode ->
if(dispatchBackPressEvent(childPageNode)) return true
}
}
//再派发给当前fragment处理
return (pageNode.currFragment as? FragmentBackPressListener)?.onFragmentBackPressed()?:false
}
private fun transferFragments2PageNodeTree(fragments: List<Fragment>){
Log.e(TAG, "fragments size: ${fragments.size}")
if(fragments.isEmpty()) return
for (item in fragments){
val currPageNode = fragmentPageNodeMap[item]?:PageNode(item)
item.parentFragment?.let { parentFragment ->
val parentPageNode = fragmentPageNodeMap[parentFragment]?:PageNode(parentFragment)
parentPageNode.addChildFragment(item)
fragmentPageNodeMap.put(parentFragment, parentPageNode)
}?: run { //没有父Fragment,说明上级就是activity啦
rootPageNode.addChildFragment(item)
}
fragmentPageNodeMap.put(item, currPageNode)
}
}
private fun removeFromPageNodeTree(fragment: Fragment){
fragment.parentFragment?.let { parentFragment ->
fragmentPageNodeMap[parentFragment]?.removeChildFragment(fragment)
}?: run{//没有父Fragment,说明上级就是activity啦
rootPageNode.removeChildFragment(fragment)
}
fragmentPageNodeMap.remove(fragment)
}
private fun getAllFragments(fm: FragmentManager): List<Fragment> {
val result = mutableListOf<Fragment>()
for (fragment in fm.fragments) {
result.add(fragment)
result.addAll(getAllFragments(fragment.childFragmentManager))
}
return result
}
}
kotlin
import androidx.fragment.app.Fragment
//一个fragment对应一个这样的节点;activity节点的currFragment == null
class PageNode(var currFragment: Fragment?) {
private var childFragments = LinkedHashSet<Fragment>()
fun addChildFragment(fragment: Fragment){
childFragments.add(fragment)
}
fun removeChildFragment(fragment: Fragment){
childFragments.remove(fragment)
}
fun getChildFragments(): LinkedHashSet<Fragment>{
return childFragments
}
}
kotlin
interface FragmentBackPressListener {
fun onFragmentBackPressed(): Boolean
}
完整项目地址:github.com/High-Power-...
注意:上述代码未经过充分测试