wake-up-neo.net

Android Jetpack Navigation, BottomNavigationView mit Youtube oder Instagram wie die richtige Rückwärtsnavigation (Fragment Back Stack)?

Android Jetpack Navigation, BottomNavigationView mit automatischem Fragment-Back-Stack beim Klicken auf die Schaltfläche "Zurück"?

Was ich wollte, nachdem mehrere Benutzer nacheinander mehrere Registerkarten ausgewählt haben, klicken Sie auf die Schaltfläche "Zurück". Die App muss auf die zuletzt geöffnete Seite umleiten.

Dasselbe habe ich mit Android ViewPager erreicht, indem ich das aktuell ausgewählte Element in einer ArrayList speichere. Gibt es nach dem Android Jetpack Navigation Release einen automatischen Stapelwechsel? Ich möchte es mit einer Navigationsgrafik erreichen

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<Android.support.constraint.ConstraintLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:id="@+id/container"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    tools:context=".main.MainActivity">

    <fragment
        Android:id="@+id/my_nav_Host_fragment"
        Android:name="androidx.navigation.fragment.NavHostFragment"
        Android:layout_width="match_parent"
        Android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />

    <Android.support.design.widget.BottomNavigationView
        Android:id="@+id/navigation"
        Android:layout_width="0dp"
        Android:layout_height="wrap_content"
        Android:layout_marginStart="0dp"
        Android:layout_marginEnd="0dp"
        Android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/navigation" />

</Android.support.constraint.ConstraintLayout>

navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:Android="http://schemas.Android.com/apk/res/Android">

    <item
        Android:id="@+id/navigation_home"
        Android:icon="@drawable/ic_home"
        Android:title="@string/title_home" />

    <item
        Android:id="@+id/navigation_people"
        Android:icon="@drawable/ic_group"
        Android:title="@string/title_people" />

    <item
        Android:id="@+id/navigation_organization"
        Android:icon="@drawable/ic_organization"
        Android:title="@string/title_organization" />

    <item
        Android:id="@+id/navigation_business"
        Android:icon="@drawable/ic_business"
        Android:title="@string/title_business" />

    <item
        Android:id="@+id/navigation_tasks"
        Android:icon="@drawable/ic_dashboard"
        Android:title="@string/title_tasks" />

</menu>

auch hinzugefügt 

bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_Host_fragment))

Ich habe eine Antwort von Levi Moreira erhalten, wie folgt

navigation.setOnNavigationItemSelectedListener {item ->

            onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_Host_fragment))

        }

Auf diese Weise geschieht jedoch nur, dass die zuletzt geöffnete Instanz des Fragments erneut erstellt wird.

Bereitstellung der richtigen Rückwärtsnavigation für BottomNavigationView

34
Bincy Baby

Sie benötigen nicht wirklich eine ViewPager, um mit BottomNavigation und der neuen Navigationsarchitekturkomponente zu arbeiten. Ich habe in einer Beispiel-App gearbeitet, die genau beide verwendet, siehe hier .

Das Grundkonzept lautet: Sie haben die Hauptaktivität, die die Variable BottomNavigationView hostet, und das ist der Navigationshost für Ihr Navigationsdiagramm. So sieht die XML-Datei aus:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<Android.support.constraint.ConstraintLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:id="@+id/container"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    tools:context=".main.MainActivity">

    <fragment
        Android:id="@+id/my_nav_Host_fragment"
        Android:name="androidx.navigation.fragment.NavHostFragment"
        Android:layout_width="match_parent"
        Android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />

    <Android.support.design.widget.BottomNavigationView
        Android:id="@+id/navigation"
        Android:layout_width="0dp"
        Android:layout_height="wrap_content"
        Android:layout_marginStart="0dp"
        Android:layout_marginEnd="0dp"
        Android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/navigation" />

</Android.support.constraint.ConstraintLayout>

Das Navigationsmenü (Registerkartenmenü) für die BottomNavigationView sieht folgendermaßen aus:

navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:Android="http://schemas.Android.com/apk/res/Android">

    <item
        Android:id="@+id/navigation_home"
        Android:icon="@drawable/ic_home"
        Android:title="@string/title_home" />

    <item
        Android:id="@+id/navigation_people"
        Android:icon="@drawable/ic_group"
        Android:title="@string/title_people" />

    <item
        Android:id="@+id/navigation_organization"
        Android:icon="@drawable/ic_organization"
        Android:title="@string/title_organization" />

    <item
        Android:id="@+id/navigation_business"
        Android:icon="@drawable/ic_business"
        Android:title="@string/title_business" />

    <item
        Android:id="@+id/navigation_tasks"
        Android:icon="@drawable/ic_dashboard"
        Android:title="@string/title_tasks" />

</menu>

All dies ist nur die BottomNavigationView-Einstellung. Damit es mit der Navigations-Arch-Komponente funktioniert, müssen Sie in den Navigationsdiagramm-Editor gehen, alle Ihre Fragment-Ziele hinzufügen (in meinem Fall habe ich 5 davon, eines für jede Registerkarte) und die ID des Ziels mit derselben festlegen Name wie der in der navigation.xml-Datei:

enter image description here

Dadurch wird Android angewiesen, eine Verknüpfung zwischen der Registerkarte und dem Fragment herzustellen. Der Benutzer klickt jetzt auf die Registerkarte "Home". Android sorgt dafür, dass das richtige Fragment geladen wird. Es gibt auch ein Stück Kotlin-Code, der benötigt wird zu Ihrem NavHost (der Hauptaktivität) hinzugefügt werden, um die Dinge mit der BottomNavigationView zu verbinden:

Sie müssen in Ihrem onCreate Folgendes hinzufügen:

bottomNavigation.setupWithNavController(Navigation.findNavController(this, R.id.my_nav_Host_fragment))

Dadurch wird Android angewiesen, die Verbindung zwischen der Navigationsarchitekturkomponente und der BottomNavigationView herzustellen. Sehen Sie mehr in der docs .

Um den gleichen Beahvior zu erhalten, den Sie auch bei YouTube haben, fügen Sie einfach folgendes hinzu:

navigation.setOnNavigationItemSelectedListener {item ->

            onNavDestinationSelected(item, Navigation.findNavController(this, R.id.my_nav_Host_fragment))

        }

Dadurch werden Ziele in den Backstack verschoben. Wenn Sie auf die Zurück-Schaltfläche klicken, wird das zuletzt besuchte Ziel angezeigt.

26
Levi Moreira

Sie müssen die Hostnavigation wie folgt einstellen:

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:orientation="vertical">

    <Android.support.v7.widget.Toolbar
        Android:id="@+id/toolbar"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:background="@color/colorPrimary" />

    <fragment
        Android:id="@+id/navigation_Host_fragment"
        Android:name="androidx.navigation.fragment.NavHostFragment"
        Android:layout_width="match_parent"
        Android:layout_height="0dp"
        Android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

    <Android.support.design.widget.BottomNavigationView
        Android:id="@+id/bottom_navigation_view"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        app:itemIconTint="@drawable/color_state_list"
        app:itemTextColor="@drawable/color_state_list"
        app:menu="@menu/menu_bottom_navigation" />
</LinearLayout>

Setup mit dem Navigationscontroller:

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_Host_fragment);
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.getNavController());

menu_bottom_navigation.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <item
        Android:id="@id/tab1"  // Id of navigation graph 
        Android:icon="@mipmap/ic_launcher"
        Android:title="@string/tab1" />
    <item
        Android:id="@id/tab2" // Id of navigation graph
        Android:icon="@mipmap/ic_launcher"
        Android:title="@string/tab2" />

    <item
        Android:id="@id/tab3" // Id of navigation graph
        Android:icon="@mipmap/ic_launcher"
        Android:title="@string/tab3" />
</menu>

nav_graph.xml:

<navigation xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:id="@+id/nav_graph"
    app:startDestination="@id/tab1">
    <fragment
        Android:id="@+id/tab1"
        Android:name="com.navigationsample.Tab1Fragment"
        Android:label="@string/tab1"
        tools:layout="@layout/fragment_tab_1" />

    <fragment
        Android:id="@+id/tab2"
        Android:name="com.navigationsample.Tab2Fragment"
        Android:label="@string/tab2"
        tools:layout="@layout/fragment_tab_2"/>

    <fragment
        Android:id="@+id/tab3"
        Android:name="com.simform.navigationsample.Tab3Fragment"
        Android:label="@string/tab3"
        tools:layout="@layout/fragment_tab_3"/>
</navigation>

Durch Einrichten der gleichen ID von "nav_graph" auf "menu_bottom_navigation" wird der Klick der unteren Navigation ausgeführt.

Sie können die Rückaktion mit der popUpTo-Eigenschaft im action-Tag ausführen  enter image description here

14
SANAT

Sie können ein Viewpager-Setup mit der unteren Navigationsansicht einrichten. Jedes Fragment im Viewpager ist ein Containerfragment. Es enthält untergeordnete Fragmente mit eigenem Backstack. Sie können den Backstack für jede Registerkarte im Viewpager auf diese Weise verwalten

7
Suhaib Roomy

Ich habe eine App wie diese gemacht (die noch nicht im PlayStore veröffentlicht wurde), die die gleiche Navigation hat. Möglicherweise unterscheidet sich die Implementierung von der von Google in ihren Apps verwendeten Funktionen.

die Struktur beinhaltet, dass ich Hauptaktivität habe, dass ich den Inhalt davon umschalte, indem Fragmente angezeigt/verborgen werden:

public void switchTo(final Fragment fragment, final String tag /*Each fragment should have a different Tag*/) {

// We compare if the current stack is the current fragment we try to show
if (fragment == getSupportFragmentManager().getPrimaryNavigationFragment()) {
  return;
}

// We need to hide the current showing fragment (primary fragment)
final Fragment currentShowingFragment = getSupportFragmentManager().getPrimaryNavigationFragment();

final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (currentShowingFragment != null) {
  fragmentTransaction.hide(currentShowingFragment);
}

// We try to find that fragment if it was already added before
final Fragment alreadyAddedFragment = getSupportFragmentManager().findFragmentByTag(tag);
if (alreadyAddedFragment != null) {
  // Since its already added before we just set it as primary navigation and show it again
  fragmentTransaction.setPrimaryNavigationFragment(alreadyAddedFragment);
  fragmentTransaction.show(alreadyAddedFragment);
} else {
  // We add the new fragment and then show it
  fragmentTransaction.add(containerId, fragment, tag);
  fragmentTransaction.show(fragment);
  // We set it as the primary navigation to support back stack and back navigation
  fragmentTransaction.setPrimaryNavigationFragment(fragment);
}

fragmentTransaction.commit();
}
4
Mohammad Ersan

Wenn Sie eine bottomNavigationView mit 3 Elementen haben, die 3 Fragments entspricht: FragmentA, FragmentB und FragmentC, wobei FragmentA die startDestination in Ihrer Navigationsgrafik ist, werden Sie zu FragmentB oder FragmentC umgeleitet, wenn Sie sich im Hintergrund befinden FragmentA, dieses Verhalten wird von Google empfohlen und standardmäßig implementiert.

Wenn Sie jedoch dieses Verhalten ändern möchten, müssen Sie entweder eine ViewPager verwenden, wie dies in einigen anderen Antworten vorgeschlagen wurde, oder die Fragmente backStack- und back-Transaktionen selbst handhaben, was die Verwendung der Navigation in gewisser Weise beeinträchtigen würde Komponente insgesamt-.

2
Husayn Hakeem

Kurzer und guter Code in Kotlin zum Verbinden von unteren Navigationselementen mit Fragmenten innerhalb des Navigationsdiagramms:

    val navControl = findNavController( R.id.nav_Host_frag_main)
    bottomNavigationView?.setupWithNavController(navControl)

* Denken Sie nur daran: Die unteren Navigations-IDs und Fragmente im Navigationsdiagramm müssen dieselbe ID haben. Auch dank guter Erklärung von @sanat Answer

2
Hamed Jaliliani

Lassen Sie mich zunächst klären, wie Youtube und Instagram die Fragmentnavigation handhaben.

  • Wenn sich der Benutzer auf einem Detailfragment befindet, können Sie den Stapel einmal hoch- oder zurückstellen, wobei der Status ordnungsgemäß wiederhergestellt wird. Ein zweiter Klick auf das bereits ausgewählte Element in der unteren Leiste bringt den gesamten Stapel in den Stamm, wodurch er aktualisiert wird
  • Wenn sich der Benutzer auf einem Stammfragment befindet, wechselt zurück zum zuletzt ausgewählten Menü in der unteren Leiste, wobei das letzte Detailfragment angezeigt wird und der Status ordnungsgemäß wiederhergestellt wird (JetPack nicht).
  • Wenn sich der Benutzer im Startzielfragment befindet, ist back mit der Aktivität fertig

Keine der oben genannten Antworten löst alle diese Probleme mit der Jetpack-Navigation.

Für die JetPack-Navigation gibt es hierfür keine Standardmethode. Ich fand es einfacher, den XML-Navigationsdiagramm für jedes untere Navigationselement in einen zu unterteilen und den hinteren Stapel zwischen den Navigationselementen selbst zu verwalten, indem ich die Aktivität FragmentManager verwende und den JetPack NavController verwende um die interne Navigation zwischen Stamm- und Detailfragmenten zu handhaben (ihre Implementierung verwendet den childFragmentManager-Stack).

Angenommen, Sie haben in Ihrem navigation-Ordner diese 3 xmls:

res/navigation/
    navigation_feed.xml
    navigation_explore.xml
    navigation_profile.xml

Halten Sie Ihre Ziel-IDs in den Navigations-XMLs wie in den Menü-IDs bottomNavigationBar. Legen Sie für jede XML-Datei den app:startDestination auf das Fragment fest, das Sie als Stamm des Navigationselements verwenden möchten.

Erstellen Sie eine Klasse BottomNavController.kt:

class BottomNavController(
        val context: Context,
        @IdRes val containerId: Int,
        @IdRes val appStartDestinationId: Int
) {
    private val navigationBackStack = BackStack.of(appStartDestinationId)
    lateinit var activity: Activity
    lateinit var fragmentManager: FragmentManager
    private var listener: OnNavigationItemChanged? = null
    private var navGraphProvider: NavGraphProvider? = null

    interface OnNavigationItemChanged {
        fun onItemChanged(itemId: Int)
    }

    interface NavGraphProvider {
        @NavigationRes
        fun getNavGraphId(itemId: Int): Int
    }

    init {
        var ctx = context
        while (ctx is ContextWrapper) {
            if (ctx is Activity) {
                activity = ctx
                fragmentManager = (activity as FragmentActivity).supportFragmentManager
                break
            }
            ctx = ctx.baseContext
        }
    }

    fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
        this.listener = object : OnNavigationItemChanged {
            override fun onItemChanged(itemId: Int) {
                listener.invoke(itemId)
            }
        }
    }

    fun setNavGraphProvider(provider: NavGraphProvider) {
        navGraphProvider = provider
    }

    fun onNavigationItemReselected(item: MenuItem) {
        // If the user press a second time the navigation button, we pop the back stack to the root
        activity.findNavController(containerId).popBackStack(item.itemId, false)
    }

    fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {

        // Replace fragment representing a navigation item
        val fragment = fragmentManager.findFragmentByTag(itemId.toString())
                ?: NavHostFragment.create(navGraphProvider?.getNavGraphId(itemId)
                        ?: throw RuntimeException("You need to set up a NavGraphProvider with " +
                                "BottomNavController#setNavGraphProvider")
                )
        fragmentManager.beginTransaction()
                .setCustomAnimations(
                        R.anim.nav_default_enter_anim,
                        R.anim.nav_default_exit_anim,
                        R.anim.nav_default_pop_enter_anim,
                        R.anim.nav_default_pop_exit_anim
                )
                .replace(containerId, fragment, itemId.toString())
                .addToBackStack(null)
                .commit()

        // Add to back stack
        navigationBackStack.moveLast(itemId)

        listener?.onItemChanged(itemId)

        return true
    }

    fun onBackPressed() {
        val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
                .childFragmentManager
        when {
            // We should always try to go back on the child fragment manager stack before going to
            // the navigation stack. It's important to use the child fragment manager instead of the
            // NavController because if the user change tabs super fast commit of the
            // supportFragmentManager may mess up with the NavController child fragment manager back
            // stack
            childFragmentManager.popBackStackImmediate() -> {
            }
            // Fragment back stack is empty so try to go back on the navigation stack
            navigationBackStack.size > 1 -> {
                // Remove last item from back stack
                navigationBackStack.removeLast()

                // Update the container with new fragment
                onNavigationItemSelected()
            }
            // If the stack has only one and it's not the navigation home we should
            // ensure that the application always leave from startDestination
            navigationBackStack.last() != appStartDestinationId -> {
                navigationBackStack.removeLast()
                navigationBackStack.add(0, appStartDestinationId)
                onNavigationItemSelected()
            }
            // Navigation stack is empty, so finish the activity
            else -> activity.finish()
        }
    }

    private class BackStack : ArrayList<Int>() {
        companion object {
            fun of(vararg elements: Int): BackStack {
                val b = BackStack()
                b.addAll(elements.toTypedArray())
                return b
            }
        }

        fun removeLast() = removeAt(size - 1)
        fun moveLast(item: Int) {
            remove(item)
            add(item)
        }
    }
}

// Convenience extension to set up the navigation
fun BottomNavigationView.setUpNavigation(bottomNavController: BottomNavController, onReselect: ((menuItem: MenuItem) -> Unit)? = null) {
    setOnNavigationItemSelectedListener {
        bottomNavController.onNavigationItemSelected(it.itemId)
    }
    setOnNavigationItemReselectedListener {
        bottomNavController.onNavigationItemReselected(it)
        onReselect?.invoke(it)
    }
    bottomNavController.setOnItemNavigationChanged { itemId ->
        menu.findItem(itemId).isChecked = true
    }
}

Machen Sie Ihr Layout main.xml so:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent">

    <FrameLayout
        Android:id="@+id/container"
        Android:layout_width="match_parent"
        Android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.Android.material.bottomnavigation.BottomNavigationView
        Android:id="@+id/bottomNavigationView"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:layout_marginStart="0dp"
        Android:layout_marginEnd="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:menu="@menu/navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

Verwenden Sie für Ihre Aktivität wie folgt:

class MainActivity : AppCompatActivity(),
        BottomNavController.NavGraphProvider  {

    private val navController by lazy(LazyThreadSafetyMode.NONE) {
        Navigation.findNavController(this, R.id.container)
    }

    private val bottomNavController by lazy(LazyThreadSafetyMode.NONE) {
        BottomNavController(this, R.id.container, R.id.navigation_feed)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)

        bottomNavController.setNavGraphProvider(this)
        bottomNavigationView.setUpNavigation(bottomNavController)
        if (savedInstanceState == null) bottomNavController
                .onNavigationItemSelected()

        // do your things...
    }

    override fun getNavGraphId(itemId: Int) = when (itemId) {
        R.id.navigation_feed -> R.navigation.navigation_feed
        R.id.navigation_explore -> R.navigation.navigation_explore
        R.id.navigation_profile -> R.navigation.navigation_profile
        else -> R.navigation.navigation_feed
    }

    override fun onSupportNavigateUp(): Boolean = navController
            .navigateUp()

    override fun onBackPressed() = bottomNavController.onBackPressed()
}
0
Allan Veloso