处理 Android 导航组件中的后退按钮
Handling back button in Android Navigation Component
我想知道如何使用导航控制器正确处理系统后退按钮操作。在我的应用程序中,我有两个片段(例如片段 1 和片段 2),我在片段 1 中有一个动作,目标是片段 2。除了一件事,一切都很好——当用户在 fragment2 中按下系统后退按钮时,我想显示一个对话框(例如使用 DialogFragment)来确认退出。实现此行为的最佳方法是什么?如果我在主机片段中使用 app:defaultNavHost="true"
,那么它会自动返回并忽略我的规则。另外,这个组件是做什么用的?
我应该使用 "pop to" 吗?
这是一个应该做你想做的解决方案,但我认为这是一个糟糕的解决方案,因为它违背了 Android 导航组件的想法(让 android 处理导航)。
在您的 activity
中覆盖 "onBackPressed"
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();
}
并由所有需要处理后退按钮的片段实现。在主要 activity 中,我重写了 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()
}
更新
21 年 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()
}
}
原回答
2019 年 1 月 3 日
晚会有点晚了,但是有了最新版本的导航组件 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 ver. 1.0.0-alpha07带来了一些变化
更多解释见android官方指南:Provide custom back navigation
示例:
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 日
现在简化了。更多信息 here
示例:
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 日):
自从 this, it can be implemented just using JetPack 在您的片段中实施 OnBackPressedCallback
并将其添加到 activity:
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);
}
}
更新:
您的 activity 应该扩展 AppCompatActivity
或 FragmentActivity
并且在 Gradle 文件中:
implementation 'androidx.appcompat:appcompat:{lastVersion}'
这是我的解决方案
对包含 NavHostFragment
片段的 activity 使用 androidx.appcompat.app.AppCompatActivity
。
定义如下接口并在所有导航目标片段中实现
interface InterceptionInterface {
fun onNavigationUp(): Boolean
fun onBackPressed(): Boolean
}
在你的 activity 覆盖 onSupportNavigateUp
和 onBackPressed
:
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 解决方案,但我就是找不到 getOnBackPressedDispatcher 或 addOnBackPressedCallback
同样使用 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()
这样你可以在片断中决定 activity 是否应该控制后退。
或者,您的所有活动都有 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()
}
}
recommended 方法是将 OnBackPressedCallback
添加到 activity 的 OnBackPressedDispatcher
。
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
// handle back event
}
在2.1.0-alpha06
如果你只想在当前片段中处理 backpress
requireActivity().onBackPressedDispatcher.addCallback(this@LoginFragment) {
// handle back event
}
整个Activity
requireActivity().onBackPressedDispatcher.addCallback() {
// handle back event
}
对于寻找 Kotlin 实现的任何人,请参见下文。
请注意,OnBackPressedCallback
似乎只能为内置 software/hardware 后退按钮提供自定义后退行为,而不是 button/home 作为向上按钮的后退箭头 button/home =29=]。为了同时覆盖 actionbar/toolbar 后退按钮的行为,我提供了适合我的解决方案。如果这是一个错误,或者您知道针对该情况的更好解决方案,请发表评论。
build.gradle
...
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()
}
}
MyFragment.kt
...
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,则可以将 onBackPressedDispatcher 添加到您的基本片段。
//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() {
}
}
通过扩展 basefragment
覆盖片段中的 onBackPressed()
//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.
}
}
我在main里写的activity是这样的,
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}
根据您的逻辑,如果您只想关闭当前片段,则必须传递viewLifecycleOwner,代码如下所示:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
requireActivity().finish()
}
})
但是,如果您想在 backPressed 上关闭应用程序,无论从哪个片段(可能您不希望这样!),请不要传递 viewLifecycleOwner。此外,如果您想禁用后退按钮,请不要在 handleOnBackPressed() 中执行任何操作,请参见下文:
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();
}
推荐的方法对我有用,但在更新我的库后 implementation 'androidx.appcompat:appcompat:1.1.0'
执行如下
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Handle the back button event
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
使用 Kotlin
如果您使用导航组件,请在您的 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);
您可以使用 OnBackPressedDispatcher 提供自定义后退导航
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
}
}
更多解释见android官方指南:https://developer.android.com/guide/navigation/navigation-custom-back
如果您希望工具栏后退按钮也有相同的行为,只需将其添加到 activity:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
我的意见
requireActivity().onBackPressed()
requireActivity().onBackPressed()
只需添加这些行
override fun onBackPressed() {
if(navController.popBackStack().not()) {
//Last fragment: Do your operation here
finish()
}
navController.popBackStack() 如果这不是您的最后一个片段,则只会弹出您的片段
如果您使用片段或将其添加到您的按钮点击侦听器中,请使用此选项。这对我有用。
requireActivity().onBackPressed()
当 activity 检测到用户按下后退键时调用。在调用 android.app.Activity#onBackPressed()} 的默认行为之前,getOnBackPressedDispatcher() OnBackPressedDispatcher} 将有机会处理后退按钮。
我搜索了很多话题,其中 none 个有效。终于找到一个:
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(操作必须 return false 才能调用 activity onBackPressed)
onBackPressedAction { //do something }
我需要同时支持真正的后退按钮和工具栏后退按钮,并且能够在这两种情况下覆盖“后退”单击(以显示对话框或其他内容)。我在 activity 中创建了一个附加方法,并在 fragments:
中创建了相应的布尔检查(在我的例子中为 'onBackPressed')
// 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
}
}
简单地说,在 Fragment
的 onCreate()
方法中,在 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 的回答。
但是,如果您想要弹出 SecondFragment(开始片段 FirstFragment)而不是 return 到 FirstFragment,然后你可以使用:
Navigation.findNavController(view).popBackStack()
来自 SecondFragment。这样,当您从 FirstFragment.
使用导航组件
这对我有好处:
Navigation.findNavController(requireView()).popBackStack()
ktx
版本:
fun Fragment.handleBackButtonEvent(
onBackPressed: OnBackPressedCallback.() -> Unit
) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
onBackPressed()
}
}
您可以简单地在 Fragmnet
中使用它。
FragmentExtenstions.kt
fun Fragment.onBackPressedCustomAction(action: () -> Unit) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override
fun handleOnBackPressed() {
action()
}
})
}
YourPrettyFragment.kt
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.
}
}
我想知道如何使用导航控制器正确处理系统后退按钮操作。在我的应用程序中,我有两个片段(例如片段 1 和片段 2),我在片段 1 中有一个动作,目标是片段 2。除了一件事,一切都很好——当用户在 fragment2 中按下系统后退按钮时,我想显示一个对话框(例如使用 DialogFragment)来确认退出。实现此行为的最佳方法是什么?如果我在主机片段中使用 app:defaultNavHost="true"
,那么它会自动返回并忽略我的规则。另外,这个组件是做什么用的?
我应该使用 "pop to" 吗?
这是一个应该做你想做的解决方案,但我认为这是一个糟糕的解决方案,因为它违背了 Android 导航组件的想法(让 android 处理导航)。
在您的 activity
中覆盖 "onBackPressed"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();
}
并由所有需要处理后退按钮的片段实现。在主要 activity 中,我重写了 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()
}
更新 21 年 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()
}
}
原回答 2019 年 1 月 3 日
晚会有点晚了,但是有了最新版本的导航组件 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 ver. 1.0.0-alpha07带来了一些变化
更多解释见android官方指南:Provide custom back navigation
示例:
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 日
现在简化了。更多信息 here
示例:
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 日):
自从 this, it can be implemented just using JetPack 在您的片段中实施 OnBackPressedCallback
并将其添加到 activity:
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);
}
}
更新:
您的 activity 应该扩展 AppCompatActivity
或 FragmentActivity
并且在 Gradle 文件中:
implementation 'androidx.appcompat:appcompat:{lastVersion}'
这是我的解决方案
对包含 NavHostFragment
片段的 activity 使用 androidx.appcompat.app.AppCompatActivity
。
定义如下接口并在所有导航目标片段中实现
interface InterceptionInterface {
fun onNavigationUp(): Boolean
fun onBackPressed(): Boolean
}
在你的 activity 覆盖 onSupportNavigateUp
和 onBackPressed
:
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 解决方案,但我就是找不到 getOnBackPressedDispatcher 或 addOnBackPressedCallback 同样使用 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()
这样你可以在片断中决定 activity 是否应该控制后退。
或者,您的所有活动都有 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()
}
}
recommended 方法是将 OnBackPressedCallback
添加到 activity 的 OnBackPressedDispatcher
。
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
// handle back event
}
在2.1.0-alpha06
如果你只想在当前片段中处理 backpress
requireActivity().onBackPressedDispatcher.addCallback(this@LoginFragment) {
// handle back event
}
整个Activity
requireActivity().onBackPressedDispatcher.addCallback() {
// handle back event
}
对于寻找 Kotlin 实现的任何人,请参见下文。
请注意,OnBackPressedCallback
似乎只能为内置 software/hardware 后退按钮提供自定义后退行为,而不是 button/home 作为向上按钮的后退箭头 button/home =29=]。为了同时覆盖 actionbar/toolbar 后退按钮的行为,我提供了适合我的解决方案。如果这是一个错误,或者您知道针对该情况的更好解决方案,请发表评论。
build.gradle
...
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()
}
}
MyFragment.kt
...
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,则可以将 onBackPressedDispatcher 添加到您的基本片段。
//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() {
}
}
通过扩展 basefragment
覆盖片段中的 onBackPressed()//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.
}
}
我在main里写的activity是这样的,
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}
根据您的逻辑,如果您只想关闭当前片段,则必须传递viewLifecycleOwner,代码如下所示:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
requireActivity().finish()
}
})
但是,如果您想在 backPressed 上关闭应用程序,无论从哪个片段(可能您不希望这样!),请不要传递 viewLifecycleOwner。此外,如果您想禁用后退按钮,请不要在 handleOnBackPressed() 中执行任何操作,请参见下文:
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();
}
推荐的方法对我有用,但在更新我的库后 implementation 'androidx.appcompat:appcompat:1.1.0'
执行如下
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Handle the back button event
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
使用 Kotlin
如果您使用导航组件,请在您的 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);
您可以使用 OnBackPressedDispatcher 提供自定义后退导航
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
}
}
更多解释见android官方指南:https://developer.android.com/guide/navigation/navigation-custom-back
如果您希望工具栏后退按钮也有相同的行为,只需将其添加到 activity:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
我的意见 requireActivity().onBackPressed()
requireActivity().onBackPressed()
只需添加这些行
override fun onBackPressed() {
if(navController.popBackStack().not()) {
//Last fragment: Do your operation here
finish()
}
navController.popBackStack() 如果这不是您的最后一个片段,则只会弹出您的片段
如果您使用片段或将其添加到您的按钮点击侦听器中,请使用此选项。这对我有用。
requireActivity().onBackPressed()
当 activity 检测到用户按下后退键时调用。在调用 android.app.Activity#onBackPressed()} 的默认行为之前,getOnBackPressedDispatcher() OnBackPressedDispatcher} 将有机会处理后退按钮。
我搜索了很多话题,其中 none 个有效。终于找到一个:
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(操作必须 return false 才能调用 activity onBackPressed)
onBackPressedAction { //do something }
我需要同时支持真正的后退按钮和工具栏后退按钮,并且能够在这两种情况下覆盖“后退”单击(以显示对话框或其他内容)。我在 activity 中创建了一个附加方法,并在 fragments:
中创建了相应的布尔检查(在我的例子中为 'onBackPressed')// 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
}
}
简单地说,在 Fragment
的 onCreate()
方法中,在 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 的回答。
但是,如果您想要弹出 SecondFragment(开始片段 FirstFragment)而不是 return 到 FirstFragment,然后你可以使用:
Navigation.findNavController(view).popBackStack()
来自 SecondFragment。这样,当您从 FirstFragment.
使用导航组件 这对我有好处:
Navigation.findNavController(requireView()).popBackStack()
ktx
版本:
fun Fragment.handleBackButtonEvent(
onBackPressed: OnBackPressedCallback.() -> Unit
) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
onBackPressed()
}
}
您可以简单地在 Fragmnet
中使用它。
FragmentExtenstions.kt
fun Fragment.onBackPressedCustomAction(action: () -> Unit) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override
fun handleOnBackPressed() {
action()
}
})
}
YourPrettyFragment.kt
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.
}
}