我正在使用Android的导航体系结构组件。
对于我的片段之一,我希望截取“后退”和“上移”导航,以便在用户放弃任何未保存的更改之前可以显示确认对话框。 (在编辑活动详细信息后按BACK / 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)
}
对于“后退”导航,我在片段及其活动之间创建了一个自定义界面和回调系统:
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
}
}
...
}
对于这样一个简单的事情,这一切都是非常粗略的。有更好的方法吗?
答案 0 :(得分:3)
从androidx.appcompat:appcompat:1.1.0-alpha05
开始,为了拦截带有导航组件的后退按钮,您需要向OnBackPressedDispatcher
添加回调。此回调必须扩展OnBackPressedCallback
并覆盖handleOnBackPressed
。 OnBackPressedDispatcher
遵循责任链模式来处理回调。换句话说,如果您将回调设置为已启用,则只会执行您的回调。否则,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.addOnNavigatedListener { navController, destination ->
if (destination.id == R.id.destination) {
// do your thing
}
}
无论如何,这有一个警告,允许您在添加侦听器的活动或片段上了解其可能不应该到达的目的地。
编辑:
显然,如this comment中所指出的那样,当您按下“主页”按钮时似乎存在一个错误:当您返回到应用程序时,在{的顶部会添加一个新的(已启用的)回调{1}},从而导致调度程序忽略您的回调。解决方法是,您可以更改代码以在onStart
中添加回调,但是请注意,先前的回调仍然存在。如果这样做,您可能想在没有生命周期所有者的情况下使用onResume
,然后在离开Fragment时调用addCallback
。
答案 1 :(得分:2)
借助导航体系结构组件,您可以执行以下操作:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressedDispatcher.onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
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()
}
}
}
答案 2 :(得分:1)
向上导航只需覆盖onOptionsItemSelected()
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
android.R.id.home -> {
showDialog() // show your dialog here
true
}
else -> super.onOptionsItemSelected(item)
}
答案 3 :(得分:0)
如果将其与AppBarConfiguration一起使用,则在最新版本中,现在有一个AppBarConfiguration.OnNavigateUpListener。请参阅下面的链接以获取更多信息
答案 4 :(得分:0)
在导航组件的帮助下,您可以在片段的 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()
}
}
}
}
}
)