125

我想知道如何使用导航控制器正确处理系统后退按钮操作。在我的应用程序中,我有两个片段(例如,片段 1 和片段 2),我在片段 1 中有一个动作,目的地是片段 2。除了一件事之外,一切都运行良好 - 当用户在 fragment2 中按下系统后退按钮时,我想显示一个对话框(例如使用 DialogFragment)来确认退出。实现此行为的最佳方法是什么?如果我app:defaultNavHost="true"在我的主机片段中使用,那么它会自动返回忽略我的规则。另外,这个组件是干什么用的?

在此处输入图像描述

我应该使用“pop to”可能吗?

4

30 回答 30

216

最新更新 - 2019 年 4 月 25 日

新版本androidx.activity 版本。1.0.0-alpha07带来了一些变化

android官方指南中的更多解释:提供自定义后退导航

例子:

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;
    }

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

既然这样,它就可以在您的片段中使用JetPack 实现来实现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}'
于 2019-02-04T19:05:35.627 回答
24

所以,我创建了一个界面

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()方法,在其他地方我只是简单地弹出堆栈并在返回堆栈为空时关闭应用程序。

于 2018-10-25T20:16:25.043 回答
22

对于任何寻找 Kotlin 实现的人,请参见下文。

请注意,OnBackPressedCallback唯一似乎可以为内置软件/硬件后退按钮提供自定义后退行为,而不是将后退箭头按钮/主页作为操作栏/工具栏中的向上按钮。为了还覆盖操作栏/工具栏后退按钮的行为,我提供了适合我的解决方案。如果这是一个错误,或者您知道针对这种情况的更好解决方案,请发表评论。

构建.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()
    }
}

我的片段.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查看

于 2019-07-27T02:13:06.430 回答
21

推荐的方法是OnBackPressedCallback在活动的OnBackPressedDispatcher.

requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { 
    // handle back event
}
于 2019-05-29T08:59:20.363 回答
14

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

在您的活动中覆盖“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()
        }
    }
} 
于 2018-06-26T14:05:32.280 回答
9

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

override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
    }   
于 2019-08-30T16:27:39.933 回答
7

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-01-03T20:33:29.880 回答
7

2.1.0-alpha06

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

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

对于整个活动

requireActivity().onBackPressedDispatcher.addCallback() {
    // handle back event
}
于 2019-07-16T05:52:42.300 回答
6

这是 2 行代码,可以从片段[TESTED and WORKING]中监听回压

  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();
     }
于 2019-10-06T19:07:08.860 回答
3

推荐的方法对我有用,但在更新我的库之后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

于 2019-10-12T18:26:22.110 回答
3

您可以使用 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

于 2020-04-22T11:51:49.353 回答
3

只需添加这些行

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

如果这不是您的最后一个片段, navController.popBackStack() 只会弹出您的片段

于 2020-07-23T17:07:20.117 回答
3

如果您使用片段或将其添加到按钮单击侦听器中,请使用此选项。这对我有用。

requireActivity().onBackPressed()

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

于 2020-08-11T12:03:25.217 回答
3

片段扩展.kt

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

YourPrettyFragment.kt

onBackPressedCustomAction {
  // Your custom action here
}
于 2021-09-09T12:48:25.103 回答
2

如果您使用导航组件,请在 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);
于 2020-03-25T06:34:56.727 回答
2

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

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

安卓文档

于 2021-06-28T15:56:24.983 回答
2

科特林答案

使用popBackStack()示例:

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

    mButton.setOnClickListener {
        Navigation.findNavController(view).popBackStack() // You need this line.
    }
}
于 2021-10-08T19:53:07.507 回答
1

试试这个。我想这会对你有所帮助。

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()
    }
于 2018-12-22T12:46:27.033 回答
1

如果您为您的应用程序使用 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.
}

}

于 2019-08-22T19:51:40.693 回答
1

如果您希望工具栏后退按钮也具有相同的行为,只需在您的活动中添加:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == android.R.id.home) {
        getOnBackPressedDispatcher().onBackPressed();
        return true;
    }
    return super.onOptionsItemSelected(item);
}
于 2020-06-05T11:03:07.360 回答
1

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

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 才能调用活动 onBackPressed)

onBackPressedAction { //do something }
于 2020-11-03T15:31:25.840 回答
1

如果您实际上是在尝试专门处理后退按钮,那么您可以使用@Jurij Pitulja 回答。

但是,如果你想要 pop SecondFragment(开始片段FirstFragment)而不返回FirstFragment,那么你可以使用:

Navigation.findNavController(view).popBackStack()

SecondFragment。这样,当您从 FirstFragment 按下返回按钮时,您将弹出返回堆栈的SecondFragmetn并且不会返回到SecondFragment

于 2021-04-14T02:24:37.050 回答
0

这是我的解决方案

用于androidx.appcompat.app.AppCompatActivity包含NavHostFragment片段的活动。

定义如下接口并在所有导航目的地片段中实现

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
}

这种解决方案的优点是导航目的地片段在分离后无需担心其侦听器的注销。

于 2019-03-25T20:00:46.900 回答
0

我尝试了 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()

这样,您可以在片段中决定活动是否应该控制后按。

或者,您的所有活动都有 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()
    }
}
于 2019-05-15T13:10:02.460 回答
0

根据您的逻辑,如果您只想关闭必须通过 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
        }
    })
于 2019-09-13T02:11:42.220 回答
0

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

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();
}
于 2020-09-17T14:23:07.800 回答
0

我需要同时支持真正的后退按钮和工具栏后退按钮,并且能够在这两种情况下覆盖“后退”单击(以显示对话框或其他内容)。我在片段中创建了一个额外的方法和相应的布尔检查(在我的例子中是'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
    }
}
于 2020-12-25T01:16:37.103 回答
0

简单地说,在onCreate()Fragment使用此代码的方法之后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
}
于 2021-04-10T01:32:19.607 回答
0

ktx它的版本:

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

你可以简单地在Fragmnet.

于 2021-07-10T17:29:05.417 回答
-1

我的意见 requireActivity().onBackPressed()

requireActivity().onBackPressed()
于 2020-06-17T18:47:02.090 回答