在 Android 导航组件中处理后退按钮

我想知道如何正确处理系统后退按钮的行动使用导航控制器。在我的应用程序中,我有两个片段(例如。而且我在片段中有一个动作,目的地是片段。除了一件事之外,其他的都很好用——当用户按碎片2中的系统返回按钮时,我想显示一个对话框(例如使用 DialogFragment)来确认退出。实现这种行为的最佳方式是什么?如果我在主机片段中使用 app:defaultNavHost="true",那么它会自动回到忽略我的规则的状态。另外,这个组件是用来做什么的?

enter image description here

我应该用“砰”可能吗?

189194 次浏览

这里有一个解决方案,应该做你想要的,但我认为这是一个坏的解决方案,因为它违背了 Android 导航组件的想法(让 Android 处理导航)。

在活动中重写“ onBackPress”

override fun onBackPressed() {
when(NavHostFragment.findNavController(nav_host_fragment).currentDestination.id) {
R.id.fragment2-> {
val dialog=AlertDialog.Builder(this).setMessage("Hello").setPositiveButton("Ok", DialogInterface.OnClickListener { dialogInterface, i ->
finish()
}).show()
}
else -> {
super.onBackPressed()
}
}
}

所以,我创建了一个接口

public interface OnBackPressedListener {
void onBackPressed();
}

在主活动中,我重写了 onBackPressed()方法:

@Override
public void onBackPressed() {
final Fragment currentFragment = mNavHostFragment.getChildFragmentManager().getFragments().get(0);
final NavController controller = Navigation.findNavController(this, R.id.nav_host_fragment);
if (currentFragment instanceof OnBackPressedListener)
((OnBackPressedListener) currentFragment).onBackPressed();
else if (!controller.popBackStack())
finish();


}

因此,如果我的导航主机的顶部片段实现了 OnBackPressedListener接口,我调用它的 onBackPressed()方法,其他地方我只是弹出回栈并关闭应用程序,如果回栈是空的。

试试这个,我觉得这个对你有帮助。

override fun onBackPressed() {
when (mNavController.getCurrentDestination()!!.getId()) {


R.id.loginFragment -> {
onWarningAlertDialog(this, "Alert", "Do you want to close this application ?")
}
R.id.registerFragment -> {
super.onBackPressed()
}
}
}






private fun onWarningAlertDialog(mainActivity: MainActivity, s: String, s1: String) {


val dialogBuilder = AlertDialog.Builder(this)
dialogBuilder.setMessage(/*""*/s1)
.setCancelable(false)
.setPositiveButton("Proceed", DialogInterface.OnClickListener { dialog, id ->
finish()
})
.setNegativeButton("Cancel", DialogInterface.OnClickListener { dialog, id ->
dialog.cancel()
})


// create dialog box
val alert = dialogBuilder.create()
// set title for alert dialog box
alert.setTitle("AlertDialogExample")
// show alert dialog
alert.show()
}

更新 1921年4月22日

我正在更新我的答案,以展示一个样本的推荐方法,也是 公认的答案以上。

class MyFragment : Fragment() {


...
    

private val backPressedDispatcher = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Redirect to our own function
this@MyFragment.onBackPressed()
}
}


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


setHasOptionsMenu(true) //Set this to true in order to trigger callbacks to Fragment#onOptionsItemSelected


(requireActivity() as AppCompatActivity).apply {
// Redirect system "Back" press to our dispatcher
onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedDispatcher)


// Set toolbar if it is in Fragment's layout. If you have a global toolbar that lives in Activity layout, then you don't need this line.
setSupportActionBar(view.findViewById(R.id.toolbar))


// Setup action bar to work with NavController
setupActionBarWithNavController(findNavController())
}
}


override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == android.R.id.home) {
// Redirect "Up/Home" button clicks to our own function
this@MyFragment.onBackPressed()
true
} else {
super.onOptionsItemSelected(item)
}
}


private fun onBackPressed() {
// Work your magic! Show dialog etc.
}


override fun onDestroyView() {
// It is optional to remove since our dispatcher is lifecycle-aware. But it wouldn't hurt to just remove it to be on the safe side.
backPressedDispatcher.remove()
super.onDestroyView()
}


}

原始答案 1月3日19

现在有点晚了,但是有了最新版本的导航组件1.0.0-alpha09,现在我们有了一个 AppBarConfiguration.OnNavigateUpListener。

更多信息请参考以下链接: Https://developer.android.com/reference/androidx/navigation/ui/appbarconfiguration.onnavigateuplistener Https://developer.android.com/jetpack/docs/release-notes

最新更新-2019年4月25日

新发布的 Androidx.activity 版本1.0.0-alpha07带来了一些变化

官方指南: 提供自定义返回导航

例如:

public class MyFragment extends Fragment {


@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);


// This callback will only be called when MyFragment is at least Started.
OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) {
@Override
public void handleOnBackPressed() {
// Handle the back button event
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);


// The callback can be enabled or disabled here or in handleOnBackPressed()
}
...
}

旧版本

UPD: 2019年4月3日

现在它的简化。更多信息 给你

例如:

requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), this);


@Override
public boolean handleOnBackPressed() {
//Do your job here
//use next line if you just need navigate up
//NavHostFragment.findNavController(this).navigateUp();
//Log.e(getClass().getSimpleName(), "handleOnBackPressed");
return true;
}

已弃用 (自1.0.0-alpha06版本以来) 2019年4月3日) :

自从 这个以来,只需在片段中使用 喷气背包实现 OnBackPressedCallback就可以实现它 并将其添加到活动中: getActivity().addOnBackPressedCallback(getViewLifecycleOwner(),this);

你的碎片应该是这样的:

public MyFragment extends Fragment implements OnBackPressedCallback {


@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
        

getActivity().addOnBackPressedCallback(getViewLifecycleOwner(),this);
}
    

@Override
public boolean handleOnBackPressed() {
//Do your job here
//use next line if you just need navigate up
//NavHostFragment.findNavController(this).navigateUp();
//Log.e(getClass().getSimpleName(), "handleOnBackPressed");
return true;
}


@Override
public void onDestroyView() {
super.onDestroyView();
getActivity().removeOnBackPressedCallback(this);
}
}

UPD: 你的活动应扩展到 AppCompatActivityFragmentActivity,并在 Gradle 文件:

 implementation 'androidx.appcompat:appcompat:{lastVersion}'

这是我的解决办法

对包含 NavHostFragment片段的活动使用 androidx.appcompat.app.AppCompatActivity

定义以下接口并在所有导航目标片段中实现它

interface InterceptionInterface {


fun onNavigationUp(): Boolean
fun onBackPressed(): Boolean
}

在你的活动覆盖 onSupportNavigateUponBackPressed:

override fun onSupportNavigateUp(): Boolean {
return getCurrentNavDest().onNavigationUp() || navigation_host_fragment.findNavController().navigateUp()
}


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


private fun getCurrentNavDest(): InterceptionInterface {
val currentFragment = navigation_host_fragment.childFragmentManager.primaryNavigationFragment as InterceptionInterface
return currentFragment
}

这种解决方案的优点是,导航目标片段不需要担心一旦分离它们的侦听器就会注销。

我尝试 Jurij Pitulja 解决方案,但是我就是找不到 getOnBackPresedDispatcher 或 addOnBackPresedCallback 同样使用 Kiryl Tkach 的溶液也无法找到现在的碎片,所以这是我的:

interface OnBackPressedListener {
fun onBackPressed(): Boolean
}


override fun onBackPressed() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
val currentFragment = navHostFragment?.childFragmentManager!!.fragments[0]
if (currentFragment !is OnBackPressedListener || !(currentFragment as OnBackPressedListener).onBackPressed()) super.onBackPressed()

这样你就可以在片段中决定活动是否应该控制背压。

或者,您可以为所有活动提供 BaseActivity,您可以像这样实现

override fun onBackPressed() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
if (navHostFragment != null){
val currentFragment = navHostFragment.childFragmentManager.fragments[0]
if (currentFragment !is AuthContract.OnBackPressedListener ||
!(currentFragment as AuthContract.OnBackPressedListener).onBackPressed()) super.onBackPressed()
} else {
super.onBackPressed()
}
}

建议方法是向活动的 OnBackPressedDispatcher添加一个 OnBackPressedCallback

requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
// handle back event
}

2.1.0-alpha06

如果您只想在当前片段中处理 backpress

requireActivity().onBackPressedDispatcher.addCallback(this@LoginFragment) {
// handle back event
}

整个活动

requireActivity().onBackPressedDispatcher.addCallback() {
// handle back event
}

对于任何寻找 Kotlin 实现的人,请看下面。

请注意,OnBackPressedCallback似乎只能为内置的软件/硬件后退按钮提供自定义的后退行为,而不能为活动栏/工具栏中的后退箭头按钮/home as up 按钮提供后退行为。为了覆盖操作栏/工具栏后退按钮的行为,我提供了适合我的解决方案。如果这是一个错误,或者你知道一个更好的解决方案,请评论。

建造,分级

...
implementation "androidx.appcompat:appcompat:1.1.0-rc01"
implementation "androidx.navigation:navigation-fragment-ktx:2.0.0"
implementation "androidx.navigation:navigation-ui-ktx:2.0.0"
...

MainActivity.kt

...
import androidx.appcompat.app.AppCompatActivity
...


class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)


...


val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(navController.graph)


// This line is only necessary if using the default action bar.
setupActionBarWithNavController(navController, appBarConfiguration)


// This remaining block is only necessary if using a Toolbar from your layout.
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.setupWithNavController(navController, appBarConfiguration)
// This will handle back actions initiated by the the back arrow
// at the start of the toolbar.
toolbar.setNavigationOnClickListener {
// Handle the back button event and return to override
// the default behavior the same way as the OnBackPressedCallback.
// TODO(reason: handle custom back behavior here if desired.)


// If no custom behavior was handled perform the default action.
navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}


/**
* If using the default action bar this must be overridden.
* This will handle back actions initiated by the the back arrow
* at the start of the action bar.
*/
override fun onSupportNavigateUp(): Boolean {
// Handle the back button event and return true to override
// the default behavior the same way as the OnBackPressedCallback.
// TODO(reason: handle custom back behavior here if desired.)


// If no custom behavior was handled perform the default action.
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}

我的碎片

...
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
...


class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Handle the back button event
}
}
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback)
}
}

官方文档可以在 https://developer.android.com/guide/navigation/navigation-custom-back上查看

如果你的应用程序使用的是 BaseFragment,那么你可以将 onBackPresseDispatcher 添加到你的基本片段中。

//Make a BaseFragment for all your fragments
abstract class BaseFragment : Fragment() {


private lateinit var callback: OnBackPressedCallback


/**
* SetBackButtonDispatcher in OnCreate
*/


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setBackButtonDispatcher()
}


/**
* Adding BackButtonDispatcher callback to activity
*/
private fun setBackButtonDispatcher() {
callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onBackPressed()
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}


/**
* Override this method into your fragment to handleBackButton
*/
open fun onBackPressed() {
}


}

通过扩展基片段覆盖片段中的 onBackPress ()

//How to use this into your fragment
class MyFragment() : BaseFragment(){


private lateinit var mView: View


override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
mView = inflater.inflate(R.layout.fragment_my, container, false)
return mView.rootView
}


override fun onBackPressed() {
//Write your code here on back pressed.
}

}

我在这样的主要活动中写道,

override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}

根据你的逻辑,如果你只想关闭当前的片段,你必须传递 viewLificycleOwner,代码如下所示:

   requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
requireActivity().finish()
}
})

但是,如果你想关闭应用回压不管从什么片段(可能你不想!) ,不要传递 viewLificycleOwner。另外,如果你想禁用后退按钮,不要在 handleOnBackPress ()中做任何事情,参见下面:

 requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// do nothing it will disable the back button
}
})

这是2行代码可以收听背压,从片段,[测试和工作]

  requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {


//setEnabled(false); // call this to disable listener
//remove(); // call to remove listener
//Toast.makeText(getContext(), "Listing for back press from this fragment", Toast.LENGTH_SHORT).show();
}

推荐的方法对我有效,但是在更新我的库 < code > 实现‘ androidx.appcompat: appcompat: 1.1.0’之后

执行如下

 val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Handle the back button event
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)

利用科特林

如果使用导航组件,请遵循 onCreateView ()方法中下面的代码(在这个例子中,我只想通过这个片段关闭我的应用程序)

 OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
new AlertDialog.Builder(Objects.requireNonNull(getActivity()))
.setIcon(R.drawable.icon_01)
.setTitle(getResources().getString(R.string.close_app_title))
.setMessage(getResources().getString(R.string.close_app_message))
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getActivity().finish();
}
})
.setNegativeButton(R.string.no, null)
.show();
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback);

您可以使用 OnBackPresdedDispatcher 提供自定义的返回导航

class MyFragment : Fragment() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)


// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// Handle the back button event
// and if you want to need navigate up
//NavHostFragment.findNavController(this).navigateUp()
}


// The callback can be enabled or disabled here or in the lambda
}
}

官方指南: https://developer.android.com/guide/navigation/navigation-custom-back

如果你想让工具栏的后退按钮也有同样的行为,只需要在你的活动中添加这个:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}

我的意见 必要的活动()

requireActivity().onBackPressed()

加上这些线条

     override fun onBackPressed() {
if(navController.popBackStack().not()) {
//Last fragment: Do your operation here
finish()
}

PopBackStack () 会弹出您的片段,如果这不是最后一个片段的话

如果你正在使用片段或者将它添加到你的按钮点击侦听器中,请使用这个。

requireActivity().onBackPressed()

当活动检测到用户按下后退键时调用。GetOnBackPressed Dispatcher () OnBackPressed Dispatcher }将有机会在 android.app 的默认行为之前处理后退按钮。调用 Activity # onBackPress ()}。

我搜索了很多线索,但没有一个有用,最后我找到了一个:

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


Toolbar mToolbar = findViewById(R.id.topAppBar);
setSupportActionBar(mToolbar);
}


@Override
public boolean onSupportNavigateUp() {
navController.navigateUp();
return super.onSupportNavigateUp();
}

MyFragment.java

@Override
public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) {
Toolbar mToolbar = (MainActivity) getActivity().findViewById(R.id.topAppBar);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Do something when uses presses back button (showing modals, messages,...)
// Note that this will override behaviour of back button
}
});
}


@Override
public void onStop() {
// Reset back button to default behaviour when we leave this fragment
Toolbar mToolbar = (MainActivity) getActivity().findViewById(R.id.topAppBar);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mainActivity.onBackPressed();
}
});


super.onStop();
}

只需要创建一个片段的扩展函数

fun Fragment.onBackPressedAction(action: () -> Boolean) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object :
OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
this.isEnabled = action()
if (!this.isEnabled) {
requireActivity().onBackPressed()
}
}
})
}

然后在片段中将代码放入 onCreateView (该操作必须返回 false 才能调用 onBackPress 活动)

onBackPressedAction { //do something }

我需要支持两个实际的后退按钮和工具栏后退按钮,能够覆盖“后退”点击在这两种情况下(显示对话框或其他东西)。我在 activity 中添加了一个附加方法,并在片段中对应布尔检查(在我的例子中是‘ onBackPress’) :

// Process hardware Back button
override fun onBackPressed() {
if (canCloseActivity()) {
super.onBackPressed()
}
}


// Process toobar Back and Menu button
override fun onSupportNavigateUp(): Boolean {
if (canCloseActivity()) {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
return false
}


// Do real check if has unfinished tasks, return false to override activity closing
private fun canCloseActivity(): Boolean {
val currentFragment = navHostFragment.childFragmentManager.primaryNavigationFragment


return when {
currentFragment is MyFragment && currentFragment.onBackPressed() -> false


drawerLayout.isOpen -> {
drawerLayout.close()
false
}
fullScreenPreviewLayout.visibility == View.VISIBLE -> {
closeFullscreenPreview()
false
}
else -> true
}
}

简单地说,在 FragmentonCreate()方法中,在 super.onCreate(savedInstanceState)之后使用以下代码:

// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
// Handle the back button event
}

如果你实际上正在尝试处理返回按钮,那么你可以使用@Jurij Pitulja 回答。

但是,如果希望弹出 第二个碎片(起始片段 第一个片段)而不返回到 第一个片段,那么可以使用:

Navigation.findNavController(view).popBackStack()

第二个碎片。这样,您将弹出回堆栈的 第二个碎片,而不会在从 第一个片段按回按钮时返回到 第二个碎片

使用导航组件 这对我有好处:

Navigation.findNavController(requireView()).popBackStack()

机器人文档

ktx版本:

fun Fragment.handleBackButtonEvent(
onBackPressed: OnBackPressedCallback.() -> Unit
) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
onBackPressed()
}
}

你可以简单地在 Fragmnet中使用它。

片段扩展

fun Fragment.onBackPressedCustomAction(action: () -> Unit) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override
fun handleOnBackPressed() {
action()
}
})
}

你的美丽碎片

onBackPressedCustomAction {
// Your custom action here
}

Kotlin 答案

使用 PopBackStack ()的例子:

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


mButton.setOnClickListener {
Navigation.findNavController(view).popBackStack() // You need this line.
}
}

如果您正在使用 NavController,这是一个答案:

Navigation.findNavController(view).navigateUp();

不要尝试破解其他方法,比如在事务中使用 FragmentManager 替换片段,并替换()。NavController 负责这个。