Android New Navigation Framework introduced in io18

See https://codelabs.developers.google.com/codelabs/android-navigation/

Ideally, use activities as entry points for your app, which contains global navigation, such as the bottom nav. Use fragments as the actual destination-specific layouts.

A destination is somewhere the user can go. Ideally, a destination is usually a fragment, you can also make your own custom destination types if needed.

Navigation graph is a new resource type where you define all the possible paths a user could take through the app. Android Studio represents this visually for you using the new Navigation Editor.

A NavHostFragment is a widget that is meant to swap in and out different fragment destinations as you navigate through the navigation graph.

Each destination must be added to the navigation graph before you can navigate to it.

NavigationController is powerful because you'll call actions like navigate() or popBackStack() and it will translate that into the appropriate framework operations based on the type of destination you are navigating to or from.

NavOptions uses a Builder pattern which allows you to only override and set the options, for example, to change the default transition animation. we can also set the options in navigation editor with <action> which contains many attributes: app:destination, app:enterAnim, … We can also modify these attributes in design:

view.findViewById<Button>(R.id.navigate_dest_bt)?.setOnClickListener {
    findNavController(it).navigate(R.id.flow_step_one, null, options)
}

Actions, the lines shown in the navigation graph are visual representations of actions.

  • A single centralized resource that shows all destinations in your app and the paths (actions) between them;
  • Point to different destinations depending on the context.
view.findViewById<Button>(R.id.navigate_action_bt)?.setOnClickListener {
    findNavController(it).navigate(R.id.next_action)
}

Actions are scoped to the destination they are attached to. Therefore an action on a different destination can have different behavior. This allows you to build reusable code that refers to a generic action that is valid on multiple destinations, for example(注:FlowStepFragment 很适合于内容页面展示,除了点击 Next button向下浏览外,没有其它和用户的交互,因此只需要为不同的页面创建对应的 layout,就可以复用这个 Fragment):

/** Presents how multiple steps flow could be implemented. */
class FlowStepFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        setHasOptionsMenu(true)

        val step = arguments?.let {
            val safeArgs = FlowStepFragmentArgs.fromBundle(it)
            safeArgs.step
        }

        return when (step) {
            2 -> inflater.inflate(R.layout.flow_step_two_fragment, container, false)
            else -> inflater.inflate(R.layout.flow_step_one_fragment, container, false)
        }
    }

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

        // a generic action that is valid on multiple destinations
        view.findViewById<View>(R.id.next_button).setOnClickListener(
            Navigation.createNavigateOnClickListener(R.id.next_action)
        )
    }
}

对应的 navigation graph

<fragment
    android:id="@+id/flow_step_one"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment"
    tools:layout="@layout/flow_step_one_fragment">
    <argument
        android:name="step"
        app:type="integer"
        android:defaultValue="1"/>

    <action
        android:id="@+id/next_action"
        app:destination="@+id/flow_step_two">
    </action>
</fragment>

<fragment
    android:id="@+id/flow_step_two"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment"
    tools:layout="@layout/flow_step_two_fragment">

    <argument
        android:name="step"
        type="integer"
        android:defaultValue="2"/>
    <action
        android:id="@+id/next_action"
        app:destination="@id/launcher_home"/>

</fragment>

The Navigation Components include a NavigationUI class and the navigation-ui-ktx kotlin extensions. NavigationUI has static methods that associate menu items with navigation destinations, and navigation-ui-ktx are extension functions that do the same. If NavigationUI finds a menu item with the same ID as a destination on the current graph, it configures the menu item to navigate to that destination.

NavigationViewis a widget for drawer navigation. 即侧滑菜单。 BottomNavigationView is a widget for bottom navigation. 即底部导航条。 overflow menu is created by the callback onCreateOptionsMenu. 即浮动菜单。

private fun setupBottomNavMenu(navController: NavController) {
    findViewById<BottomNavigationView>(R.id.bottom_nav_view)?.let { bottomNavView ->
        NavigationUI.setupWithNavController(bottomNavView, navController)
    }
}

private fun setupNavigationMenu(navController: NavController) {
    findViewById<NavigationView>(R.id.nav_view)?. let { navigationView ->
        NavigationUI.setupWithNavController(navigationView, navController)
    }
}

private fun setupActionBar(navController: NavController) {
    drawerLayout = findViewById(R.id.drawer_layout)
    NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
    val retValue = super.onCreateOptionsMenu(menu)
    val navigationView = findViewById<NavigationView>(R.id.nav_view)
    if (navigationView == null) {
        menuInflater.inflate(R.menu.menu_overflow, menu)
        return true
    }
    return retValue
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
    // Have the NavHelper look for an action or destination matching the menu
    // item id and navigate there if found.
    // Otherwise, bubble up to the parent.
    return NavigationUI.onNavDestinationSelected(item,
            Navigation.findNavController(this, R.id.my_nav_host_fragment))
            || super.onOptionsItemSelected(item)
}

override fun onSupportNavigateUp(): Boolean {
    return NavigationUI.navigateUp(drawerLayout,
            Navigation.findNavController(this, R.id.my_nav_host_fragment))
}

Using type safe argument bundles for navigation

apply plugin: ‘androidx.navigation.safeargs'

// val step = arguments?.getInt("step")

// STEP 9 - Use type-safe arguments - remove previous line!
val step = arguments?.let {
    val safeArgs = FlowStepFragmentArgs.fromBundle(it)
    safeArgs.step
}

<fragment
    android:id="@+id/flow_step_one"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment"
    tools:layout="@layout/flow_step_one_fragment">
 <argument
     android:name="step"
     app:type="integer"
     android:defaultValue="1"/>
</fragment>

Deep links are a way to jump into the middle of your app's navigation, whether that's from an actual url link or a pending intent from a notification.

One benefit of using the navigation library to handle deep links is that it ensures users start on the appropriate destination with the appropriate back stack from other entry points such as app widgets, notifications, or web links (covered in the next step).

val args = Bundle()
args.putString("myarg", "From Widget")
// Step 11 - construct and set a PendingIntent using DeepLinkBuilder
val pendingIntent = NavDeepLinkBuilder(context)
    .setGraph(R.navigation.mobile_navigation)
    .setDestination(R.id.deep_link_fragment)
    .setArguments(args)
    .createPendingIntent()

<deepLink> is an element you can add to destination in your graph. Each <deepLink> element has a single required attribute: app:uri.

In addition to a direct Uri match, the following features are supported:

  • Uris without a scheme are assumed as http and https.
  • Placeholders in the form of {placeholder_name} match 1 or more characters. The String value of the placeholder is available in the arguments Bundle with a key of the same name. For example, http://www.example.com/users/{id} will match http://www.example.com/users/weiyi.
  • The .* wildcard can be used to match 0 or more characters.
  • NavController will automatically handle ACTION_VIEW intents and look for matching deep links.
<fragment
    android:id="@+id/deep_link_fragment"
    android:name="com.example.android.codelabs.navigation.DeepLinkFragment"
    android:label="@string/deeplink"
    tools:layout="@layout/deeplink_fragment">
    <argument
        android:name=“userId"
       android:defaultValue=“weiyi"/>

    <deepLink app:uri=“www.example.com/users/{id}" />
</fragment>