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

Kir*_*ach 33 navigation android fragment android-jetpack

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

在此输入图像描述

我应该使用"pop to"吗?

Jur*_*lja 53

最新更新-2019年4月25日

新版本androidx.activity ver。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()
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

旧更新

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;
    }
Run Code Online (Sandbox Code Playgroud)

不推荐使用(自1.0.0-alpha06版本2019年4月3日起):

既然这样,就可以使用片段中的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

UPD: 您的活动应扩展为AppCompatActivityFragmentActivity在Gradle文件中:

 implementation 'androidx.appcompat:appcompat:{lastVersion}'
Run Code Online (Sandbox Code Playgroud)

  • 使用AndroidX AppCompatActivity和Androidx Fragment,我也不认为这是可用选项 (3认同)
  • OnBackPressedCallback 现在是一个抽象类!所以这个解决方案对我不起作用。 (3认同)
  • 在对handleIOnBackPressed()执行一些逻辑后如何进行正常的后退操作? (3认同)
  • Jetpack是否仍然存在? (2认同)

Abt*_*ian 17

对于任何正在寻找 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"
...
Run Code Online (Sandbox Code Playgroud)

主活动.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()
    }
}
Run Code Online (Sandbox Code Playgroud)

我的片段.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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这是最好的答案。您应该能够处理工具栏上的后按和正常的后按。竖起大拇指 (2认同)

Kir*_*ach 15

所以,我创建了一个界面

public interface OnBackPressedListener {
    void onBackPressed();
}
Run Code Online (Sandbox Code Playgroud)

并由需要处理后退按钮的所有片段实现它.在主要活动中我覆盖了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();

}
Run Code Online (Sandbox Code Playgroud)

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

  • 一个体面的解决方 虽然我总是想知道导航组件有什么意义,如果它缺乏开箱即用的功能. (7认同)

Atu*_*waj 10

只需添加这些行

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

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


Sai*_*Sai 9

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

override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
    }   
Run Code Online (Sandbox Code Playgroud)


小智 9

FragmentExtensions.kt

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

YourPrettyFragment.kt

onBackPressedCustomAction {
  // Your custom action here
}
Run Code Online (Sandbox Code Playgroud)


小智 8

建议的方法是一种添加OnBackPressedCallback到活动的OnBackPressedDispatcher

requireActivity().onBackPressedDispatcher.addCallback { 
    // handle back event
}
Run Code Online (Sandbox Code Playgroud)

  • 如果回调的所有者也像这样`.addCallback(viewLifecycleOwner) {}`一样传递,那就更好了,否则即使在片段被销毁之后你也会继续接收回调。 (2认同)

sol*_*aza 7

In 2.1.0-alpha06

If you want to handle backpress only in current fragment

requireActivity().onBackPressedDispatcher.addCallback(this@LoginFragment) {
    // handle back event
}
Run Code Online (Sandbox Code Playgroud)

For whole Activity

requireActivity().onBackPressedDispatcher.addCallback() {
    // handle back event
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*lex 6

这是应该执行您想要的解决方案,但我认为这是一个糟糕的解决方案,因为它与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()
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)


Onu*_* D. 6

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()
    }

 }
Run Code Online (Sandbox Code Playgroud)

原始答案 19 年 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


Bip*_*Das 6

这是 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();
     }
Run Code Online (Sandbox Code Playgroud)