在带有导航架构组件的片段中显示 back/up 的确认

Show confirmation on back/up in Fragment with Navigation Architecture Component

我正在为 Android 使用导航架构组件。

对于我的一个片段,我希望拦截 "back" 和 "up" 导航,以便我可以在丢弃用户未保存的任何更改之前显示确认对话框。 (当您在编辑事件详细信息后按 back/up 时,与默认日历应用程序的行为相同)

我目前的做法(未经测试)如下:

对于 "up" 导航,我覆盖了片段上的 onOptionsItemSelected

override fun onOptionsItemSelected(item: MenuItem?): Boolean {
    if(item?.itemId == android.R.id.home) {
        if(unsavedChangesExist()) {
            // TODO: show confirmation dialog
            return true
        }
    }
    return super.onOptionsItemSelected(item)
}

对于 "back" 导航,我在片段及其 activity:

之间创建了自定义接口和回调系统
interface BackHandler {
    fun onBackPressed(): Boolean
}

class MainActivity : AppCompatActivity() {
    ...

    val backHandlers: MutableSet<BackHandler> = mutableSetOf()

    override fun onBackPressed() {
        for(handler in backHandlers) {
            if(handler.onBackPressed()) {
                return
            }
        }
        super.onBackPressed()
    }

    ...
}

class MyFragment: Fragment(), BackHandler {
    ...

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is MainActivity) {
            context.backHandlers.add(this)
        }
    }

    override fun onDetach() {
        (activity as? MainActivity)?.backHandlers?.remove(this)
        super.onDetach()
    }

    override fun onBackPressed(): Boolean {
        if(unsavedChangedExist()) {
            // TODO: show confirmation dialog
            return true
        }
    }

    ...
}

对于这么简单的事情,这一切都非常粗俗和样板。有没有更好的方法?

如果您将它与 AppBarConfiguration 一起使用,最新版本现在有一个 AppBarConfiguration.OnNavigateUpListener。参考下面link获取更多信息

https://developer.android.com/reference/androidx/navigation/ui/AppBarConfiguration.OnNavigateUpListener

androidx.appcompat:appcompat:1.1.0-beta01 开始,为了拦截带导航组件的后退按钮,您需要向 OnBackPressedDispatcher 添加回调。此回调必须扩展 OnBackPressedCallback 并覆盖 handleOnBackPressedOnBackPressedDispatcher 遵循责任链模式来处理回调。换句话说,如果您将回调设置为 enabled,则只会执行您的回调。否则,OnBackPressedDispatcher 将忽略它并继续进行下一个回调,依此类推,直到找到一个启用的回调(例如,当您有多个回调时,这可能很有用)。有关此 here.

的更多信息

因此,为了显示您的对话框,您必须执行与此类似的操作:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  super.onViewCreated(view, savedInstanceState)

  val callback = object : OnBackPressedCallback(true /** true means that the callback is enabled */) {
    override fun handleOnBackPressed() {
        // Show your dialog and handle navigation
    }
  }

  // note that you could enable/disable the callback here as well by setting callback.isEnabled = true/false

  requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}

至于向上按钮,似乎(至少目前)没有太多可能性。到目前为止,我能找到的唯一使用导航组件的选项是为导航本身添加一个侦听器,它将同时处理两个按钮:

navController.addOnDestinationChangedListener { navController, destination ->
  if (destination.id == R.id.destination) {
    // do your thing
  }
}

无论如何,这有一个警告,即允许 activity 或您添加侦听器的片段了解它可能不应该知道的目的地。

对于向上导航,只需覆盖 onOptionsItemSelected()

override fun onOptionsItemSelected(item: MenuItem): Boolean =
    when (item.itemId) {
        android.R.id.home -> {
            showDialog() // show your dialog here
            true
        }
        else -> super.onOptionsItemSelected(item)
}

使用导航架构组件,您可以执行以下操作:

  1. 告诉您的 activity 将主页按钮(后退箭头)上的所有向上点击发送给任何正在收听它的人。这进入你的 activity.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     if (item.itemId == android.R.id.home) {
         onBackPressedDispatcher.onBackPressed()
         return true
     }
     return super.onOptionsItemSelected(item)
}
  1. 然后在你的片段中,像这样消费事件
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requireActivity().onBackPressedDispatcher.addCallback(this) {
           if (*condition for showing dialog here*) {
               // Show dialog
           } else {
               // pop fragment by calling function below. Analogous to when the user presses the system UP button when the associated navigation host has focus.
               findNavController().navigateUp()
           }
        }
    }

您可以在片段的 onAttach 中使用以下函数,借助导航组件覆盖 onBackPressed()

requireActivity().onBackPressedDispatcher.addCallback(
    this,
    object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            if (YOUR_CONDITION) {
                // Do something here
            } else {
                if (!findNavController().navigateUp()) {
                    if (isEnabled) {
                        isEnabled = false
                        requireActivity().onBackPressedDispatcher.onBackPressed()
                    }
                }
            }
        }
    }
)

如果您在 activity 中覆盖 onBackPressed() 必须确保它应该调用 super.onBackOnBackPressed() 否则这些调度程序不会触发