Ich möchte wissen, wie die Aktion der System-Zurück-Schaltfläche mit Navigation Controller richtig gehandhabt wird. In meiner App habe ich zwei Fragmente (zum Beispiel Fragment1 und Fragment2) und ich habe eine Aktion in Fragment1 mit dem Ziel für Fragment2. Alles funktioniert gut, bis auf eine Sache: Wenn der Benutzer die System-Zurück-Taste in Fragment2 drückt, möchte ich einen Dialog anzeigen (zum Beispiel mit DialogFragment), um das Beenden zu bestätigen. Was ist der beste Weg, um dieses Verhalten zu implementieren? Wenn ich es app:defaultNavHost="true"
in meinem Hostfragment verwende, werden meine Regeln automatisch ignoriert. Und wofür ist diese Komponente zusätzlich?
Soll ich vielleicht "Pop to" verwenden?
android
navigation
fragment
android-jetpack
Kiryl Tkach
quelle
quelle
Antworten:
Neuestes Update - 25. April 2019
Neue Version androidx.activity ver. 1.0.0-alpha07 bringt einige Änderungen mit sich
Weitere Erklärungen im offiziellen Android-Handbuch: Benutzerdefinierte Rückennavigation
Beispiel:
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() } ... }
Alte Updates
UPD: 3. April 2019
Jetzt ist es vereinfacht. Mehr Infos hier
Beispiel:
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; }
Veraltet (seit Version 1.0.0-alpha06 3. April 2019):
Da dies kann nur implementiert werden JetPack Implementierung
OnBackPressedCallback
in Ihrem Fragment und an Aktivität hinzufügen:getActivity().addOnBackPressedCallback(getViewLifecycleOwner(),this);
Ihr Fragment sollte folgendermaßen aussehen:
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: Ihre Aktivität sollte erweitert werden und
AppCompatActivity
oderFragmentActivity
in der Gradle-Datei:implementation 'androidx.appcompat:appcompat:{lastVersion}'
quelle
Also habe ich eine Schnittstelle erstellt
public interface OnBackPressedListener { void onBackPressed(); }
Und implementiert von allen Fragmenten, die die Schaltfläche "Zurück" verarbeiten müssen. In der Hauptaktivität habe ich die
onBackPressed()
Methode überschrieben :@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(); }
Wenn also das oberste Fragment meines Navigationshosts die
OnBackPressedListener
Schnittstelle implementiert , rufe ich seineonBackPressed()
Methode auf. An anderer Stelle stelle ich einfach den Stapel zurück und schließe die Anwendung, wenn der hintere Stapel leer ist.quelle
Der empfohlene Ansatz besteht darin
OnBackPressedCallback
, der Aktivität eine hinzuzufügenOnBackPressedDispatcher
.requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { // handle back event }
quelle
.addCallback(viewLifecycleOwner) {}
, andernfalls erhalten Sie auch nach der Zerstörung des Fragments weiterhin Rückrufe.Für alle, die nach einer Kotlin-Implementierung suchen, siehe unten.
Beachten Sie, dass die
OnBackPressedCallback
einzige Funktion zum Bereitstellen eines benutzerdefinierten Zurückverhaltens für die integrierte Software- / Hardware-Zurück-Schaltfläche und nicht für die Zurück-Pfeil-Schaltfläche / Home als Aufwärts-Schaltfläche in der Aktionsleiste / Symbolleiste zu funktionieren scheint. Um auch das Verhalten für die Schaltfläche zum Zurücksetzen der Aktionsleiste / Symbolleiste zu überschreiben, biete ich die Lösung an, die für mich funktioniert. Wenn dies ein Fehler ist oder Sie eine bessere Lösung für diesen Fall kennen, kommentieren Sie bitte.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) } }
Die offizielle Dokumentation kann unter https://developer.android.com/guide/navigation/navigation-custom-back eingesehen werden
quelle
OnBackPressedCallback
aber nach dem Hinzufügentoolbar.setNavigationOnClickListener { onBackPressed() }
funktionierte sie, sodass jetzt die Hardware-Rückseite und die Symbolleisten-Rückseite gleich funktionieren. Danke für die klare detaillierte Antwort!Hier ist eine Lösung, die tun sollte, was Sie wollen, aber ich denke, es ist eine schlechte Lösung, weil sie gegen die Idee der Android-Navigationskomponente verstößt (den Android die Navigation übernehmen lassen).
Überschreiben Sie "onBackPressed" in Ihrer Aktivität
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() } } }
quelle
Ich schrieb in Hauptaktivität wie folgt:
override fun onSupportNavigateUp(): Boolean { return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration) }
quelle
Dies sind 2 Codezeilen, die auf Zurückdrücken warten können, aus Fragmenten, [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(); }
quelle
Im
2.1.0-alpha06
Wenn Sie Backpress nur im aktuellen Fragment verarbeiten möchten
requireActivity().onBackPressedDispatcher.addCallback(this@LoginFragment) { // handle back event }
Für die ganze Aktivität
requireActivity().onBackPressedDispatcher.addCallback() { // handle back event }
quelle
Ein bisschen spät zur Party, aber mit der neuesten Version von Navigation Component 1.0.0-alpha09 haben wir jetzt einen AppBarConfiguration.OnNavigateUpListener.
Weitere Informationen finden Sie unter diesen Links: https://developer.android.com/reference/androidx/navigation/ui/AppBarConfiguration.OnNavigateUpListener https://developer.android.com/jetpack/docs/release-notes
quelle
Die empfohlene Methode hat bei mir funktioniert, aber nach dem Aktualisieren meiner Bibliothek
implementation 'androidx.appcompat:appcompat:1.1.0'
Implementieren Sie wie unten
val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { // Handle the back button event } } requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
mit Kotlin
quelle
Mit OnBackPressedDispatcher können Sie Ihre benutzerdefinierte Rückennavigation bereitstellen
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 } }
Weitere Erklärungen im offiziellen Android-Handbuch: https://developer.android.com/guide/navigation/navigation-custom-back
quelle
Fügen Sie einfach diese Zeilen hinzu
override fun onBackPressed() { if(navController.popBackStack().not()) { //Last fragment: Do your operation here finish() }
navController.popBackStack () lässt Ihr Fragment nur platzen, wenn dies nicht Ihr letztes Fragment ist
quelle
Wenn Sie die Navigationskomponente verwenden, befolgen Sie die folgenden Codes in Ihrer onCreateView () -Methode (in diesem Beispiel möchte ich meine App nur durch dieses Fragment schließen).
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);
quelle
Und wenn Sie dasselbe Verhalten auch für die Schaltfläche "Zurück in der Symbolleiste" wünschen, fügen Sie dies einfach in Ihre Aktivität ein:
@Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { getOnBackPressedDispatcher().onBackPressed(); return true; } return super.onOptionsItemSelected(item); }
quelle
Hier ist meine Lösung
Verwenden Sie diese Option
androidx.appcompat.app.AppCompatActivity
für die Aktivität, die dasNavHostFragment
Fragment enthält .Definieren Sie die folgende Schnittstelle und implementieren Sie sie in allen Navigationszielfragmenten
interface InterceptionInterface { fun onNavigationUp(): Boolean fun onBackPressed(): Boolean }
In Ihrer Aktivität überschreiben
onSupportNavigateUp
undonBackPressed
: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 }
Diese Lösung hat den Vorteil, dass sich die Navigationszielfragmente nicht um die Abmeldung ihrer Listener kümmern müssen, sobald sie getrennt werden.
quelle
Ich habe die Jurij Pitulja-Lösung ausprobiert, konnte jedoch getOnBackPressedDispatcher oder addOnBackPressedCallback nicht finden, auch wenn Kiryl Tkachs Lösung das aktuelle Fragment nicht finden konnte.
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()
Auf diese Weise können Sie fragmentarisch entscheiden, ob die Aktivität die Kontrolle über das Zurückdrücken übernehmen soll oder nicht.
Alternativ haben Sie BaseActivity für alle Ihre Aktivitäten, die Sie wie folgt implementieren können
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() } }
quelle
Wenn Sie BaseFragment für Ihre App verwenden, können Sie Ihrem Basisfragment onBackPressedDispatcher hinzufügen.
//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() { } }
Überschreiben Sie onBackPressed () in Ihrem Fragment, indem Sie das Basisfragment erweitern
//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. }
}}
quelle
Abhängig von Ihrer Logik wird der folgende Code angezeigt, wenn Sie nur das aktuelle Fragment schließen möchten, das Sie viewLifecycleOwner übergeben müssen:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { requireActivity().finish() } })
Wenn Sie jedoch die Anwendung auf backPressed schließen möchten, egal aus welchem Fragment (wahrscheinlich möchten Sie das nicht!), Übergeben Sie den viewLifecycleOwner nicht. Auch wenn Sie die Zurück-Schaltfläche deaktivieren möchten, tun Sie nichts im handleOnBackPressed (), siehe unten:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { // do nothing it will disable the back button } })
quelle
Versuche dies. Ich denke, das wird dir helfen.
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() }
quelle
Verwenden Sie diese Option, wenn Sie ein Fragment verwenden oder es Ihrem Listen-Click-Listener hinzufügen. Das funktioniert bei mir.
requireActivity().onBackPressed()
Wird aufgerufen, wenn die Aktivität das Drücken der Zurück-Taste durch den Benutzer erkannt hat. Der getOnBackPressedDispatcher () OnBackPressedDispatcher} erhält die Möglichkeit, die Zurück-Schaltfläche zu verarbeiten, bevor das Standardverhalten von android.app.Activity # onBackPressed ()} aufgerufen wird.
quelle
Ich habe viele Threads durchsucht und keiner von ihnen funktioniert. Endlich habe ich eins gefunden:
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(); }
quelle
Erstellen Sie einfach eine Erweiterungsfunktion für das Fragment
fun Fragment.onBackPressedAction(action: () -> Boolean) { requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { this.isEnabled = action() if (!this.isEnabled) { requireActivity().onBackPressed() } } }) }
und nachdem Sie im Fragment den Code in onCreateView eingefügt haben (die Aktion muss false zurückgeben, um die Aktivität onBackPressed aufzurufen).
onBackPressedAction { //do something }
quelle
Meine Meinung erfordertAktivität (). OnBackPressed ()
requireActivity().onBackPressed()
quelle