Skip to content

Latest commit

 

History

History
254 lines (227 loc) · 12.1 KB

navigation.md

File metadata and controls

254 lines (227 loc) · 12.1 KB

First ...

Up button versus back button

Analogies

  • NavHostFragment … a TV
  • NavController … a remote control
  • NavDestination … a television channel
  • NavigationView ... Menu for DrawerLayout, which exists
    • is not part of the Navigation component and exists before the Navigation component.
  • NavigationUI … outside a TV

NavHostFragment

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/fragment_container_view"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_graph" />
  • has a NavController.
  • is like a window to swap in and out different fragment destinations.
  • If there are more than two NavHostFragment in a layout, only one NavHostFragment must have "app:defaultNavHost="true"", which intercepts the Back button.
  • How to connect NavHostFragment with BottomNavigationView
val navController = (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
findViewById<BottomNavigationView>(R.id.bottom_navigation_view)?.setupWithNavController(navController)
  • Don't use <fragment>. Use <androidx.fragment.app.FragmentContainerView>.

NavController

  • is in a NavHostFragment.
  • shows different destinations in a NavHostFragment.
  • There are three ways to get a NavController
    • Fragment.findNavController()
    • View.findNavController()
    • Activity.findNavController(viewId: Int)
  • Sample usages:
findNavController().navigate(MyOriginatingFragmentDirections.myAction1(key1 = value1))
findNavController().navigate(R.id.action1 or R.id.destination1 or R.id.nav_graph1)
findNavController().navigate(deepLink: Uri)

NavDestination

How to save and store NavController's state during a configuration change or a system-initiated process death

class MainActivity : AppCompatActivity() {
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        navController = (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        savedInstanceState.getBundle(NAV_STATE)?.let {
            navController.restoreState(it)
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putBundle(NAV_STATE, navController.saveState())
    }

    companion object {
        private const val NAV_STATE = "nav_state"
    }
}

NavGraph

Nested graphs

is a nested <navigation>.

<!-- @navigatoin/outer_navigation -->
<navigation ...>
    <include app:graph="@navigation/inner_navigation" … />
    <fragment android:id="@+id/mainFragment" ...>
        <action
            android:id="@+id/action_mainFragment_to_innerGraph"
            app:destination="@id/inner_navigation" />
    </fragment>
</navigation>
<!-- @navigatoin/inner_navigation -->
<navigation android:id="@+id/inner_navigation" … >...</navigation>
findNavController().navigate(R.id.action_mainFragment_to_innerGraph)

Destinations in the outer graph cannot directly navigate to any destination, except the start destination, in the inner graph.

<action>

  • is represented as an arrow in a visual navigation graph.
  • is a connection from one destination to another.

Global action

  • is available from any destination in the navigation graph.
  • can be accessed in a type-safe way as <NavigationId>Directions.globalAction1.

BottomNavigationView

BottomNavigationView.setOnNavigationItemReselectedListener(...)

  • is called when the currently selected bottom navigation destination is selected again.

BottomNavigationView.setOnNavigationItemSelectedListener(...)

  • is called when any bottom navigation destination is selected, if BottomNavigationView.setOnNavigationItemReselectedListener is NOT set.
  • is called only when a not-currently-selected bottom navigation is selected, if BottomNavigationView.setOnNavigationItemReselectedListener is set.

How BottomNavigationView works

  1. Suppose there are three items in a Bottom Navigation.
    • A (startDestination), B, C
  2. When you touch A
    • fragmentManager's fragments == [A]
    • fragmentManager's backStackEntries == (empty)
  3. When you touch B
    • If A was selected previously, A will call onDestroyView().
    • If C was selected previously, C will call onDestroy().
    • fragmentManager's fragments == [B]
    • fragmentManager's backStackEntries == [A]
  4. When you touch C
    • If A was selected previously, A will call onDestroyView().
    • If B was selected previously, B will call onDestroy().
    • fragmentManager's fragments == [C]
    • fragmentManager's backStackEntries == [A]
  5. When you navigate from C to "C-Detail" for example.
    • fragmentManager's fragments == [C-Detail]
    • B will call onDestroyView().
    • fragmentManager's backStackEntries == [A, C]

Navigation drawer

SafeArgs plugin

  • generates ...
    • <OriginatingDestination>Directions.
    • <ReceivingDestination>Args.
    • <NavigationId>Directions.globalAction1.

How to forcibly generate <OriginatingDestination>Directions classes

Android Studio's toolbar > View > Tool Windows > Gradle > <app name> > Tasks > build > (again) build

AppCompatActivity.onSupportNavigateUp()

Animation

  • app:enterAnim … How Fragment2 appears when you move from Fragment1 to Fragment2.
    • If app:enterAnim="@anim/slide_in_right" is specified, Fragment2 slides in from the left.
    • If app:enterAnim=... is not specified, Fragment2 shows up without animation.
  • app:exitAnim … How Fragment1 disappears when you move from Fragment1 to Fragment2.
    • If app:exitAnim="@anim/slide_out_left" is specified, Fragment1 slides out to the right.
    • If app:exitAnim=... is not specified, Fragment1 stays still and Fragment2 shows up over the Fragment1, with the animation app:enterAnim.
  • app:popEnterAnim … How Fragment1 appears when you move back from Fragment2 to Fragment1, by the pop action.
  • app:popExitAnim … How Fragment2 disappears when you move back from Fragment2 to Fragment1, by the pop action.

Template

<fragment
    android:id="@+id/firstFragment"
    android:name="com.example.FirstFragment"
    tools:layout="@layout/fragment_first">
    <argument
        android:name="myData"
        android:defaultValue="@null"
        app:argType="com.example.MyData"
        app:nullable="true" />
    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/second_fragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left"
        app:popEnterAnim="@anim/slide_in_left"
        app:popExitAnim="@anim/slide_out_right"
        app:popUpTo="@id/start_fragment"
        app:popUpToInclusive="true" />
</fragment>

Misc

  • "Simulated back stack" and "Synthetic back stack" are the same thing.

Task