4.4: User navigation

Contents:

Providing users with a path through your app

In the early stages of developing an app, you should determine the path you want users to take through your app to do each task. (The tasks are things like placing an order or browsing content.) Each path enables users to navigate across, into, and out of the tasks and pieces of content within the app.

Often you need several paths through your app that offer the following types of navigation:

  • Back navigation, where users navigate to the previous screen using the Back button.
  • Hierarchical navigation, where users navigate through a hierarchy of screens. The hierarchy is organized with a parent screen for every set of child screens.

Back-button navigation

Back-button navigation—navigation back through the history of screens—is deeply rooted in the Android system. Android users expect the Back button in the bottom left corner of every screen to take them to the previous screen. The set of historical screens always starts with the user's Launcher (the device's Home screen), as shown in the figure below. Pressing Back enough times should return the user back to the Launcher.  Back navigation

In the figure above:

  1. Starting from Launcher.
  2. Clicking the Back button to navigate to the previous screen.

You don't have to manage the Back button in your app. The system handles tasks and the back stack—the list of previous screens—automatically. The Back button by default simply traverses this list of screens, removing the current screen from the list as the user presses it.

There are, however, cases where you may want to override the behavior for the Back button. For example, if your screen contains an embedded web browser in which users can interact with page elements to navigate between web pages, you may wish to trigger the embedded browser's default back behavior when users press the device's Back button.

The onBackPressed() method of the Activity class is called whenever the Activity detects the user's press of the Back key. The default implementation simply finishes the current Activity, but you can override this to do something else:

@Override
public void onBackPressed() {
    // Add the Back key handler here.
    return;
}

If your code triggers an embedded browser with its own behavior for the Back key, you should return the Back key behavior to the system's default behavior if the user uses the Back key to go beyond the beginning of the browser's internal history.

Hierarchical navigation patterns

To give the user a path through the full range of an app's screens, the best practice is to use some form of hierarchical navigation. An app's screens are typically organized in a parent-child hierarchy, as shown in the figure below:  App screen hierarchy

In the figure above:

  1. Parent screen
  2. First-level child screen siblings
  3. Second-level child screen siblings

Parent screen

A parent screen (such as a news app's home screen) enables navigation down to child screens.

  • The main Activity of an app is usually the parent screen.
  • Implement a parent screen as an activity with descendant navigation to one or more child screens.

First-level child screen siblings

Siblings are screens in the same position in the hierarchy that share the same parent screen (like brothers and sisters).

  • In the first level of siblings, the child screens may be collection screens that collect the headlines of stories, as shown above.
  • Implement each child screen as an Activity or Fragment.
  • Implement lateral navigation to navigate from one sibling to another on the same level.
  • If there is a second level of screens, the first level child screen is the parent to the second level child screen siblings. Implement descendant navigation to the second-level child screens.

Second-level child screen siblings

In news apps and others that offer multiple levels of information, the second level of child screen siblings might offer content, such as stories.

  • Implement each second-level child screen sibling as another Activity or Fragment.
  • Stories at this level may include embedded story elements such as videos, maps, and comments, which might be implemented as fragments.

You can enable the user to navigate up to and down from a parent, and sideways among siblings:

  • Descendant navigation: Navigating down from a parent screen to a child screen.
  • Ancestral navigation: Navigating up from a child screen to a parent screen.
  • Lateral navigation: Navigating from one sibling to another sibling (at the same level).

You can use the main Activity of the app as a parent screen, and then add an Activity or Fragment for each child screen.

Main Activity with an activity for each child

If the first-level child screen siblings have another level of child screens under them, you should implement each first-level screen as an activity, so that the lifecycle of each screen is managed properly before calling any second-level child screens.

For example, in the figure above, the parent screen is most likely the main activity. An app's main activity (usually MainActivity.java) is typically the parent screen for all other screens in your app. You implement a navigation pattern in the main activity to enable the user to go to another activity or fragment. For example, you can implement navigation using an Intent that starts an Activity.

Tip: Using an Intent in the current activity to start another activity adds the current activity to the call stack, so that the Back button in the other activity (described in the previous section) returns the user to the current activity.

As you've learned, the Android system initiates code in an Activity with callback methods that manage the Actactivityivity lifecycle for you. (A previous lesson covers the activity lifecycle; for more information, see Activities in the Android developer documentation.)

The declaration of each child activity is defined in the AndroidManifest.xml file with its parent activity. For example, the following defines OrderActivity as a child of the parent MainActivity:

<activity android:name=".OrderActivity"
   android:label="@string/title_activity_order"
   android:parentActivityName=
                        "com.example.android.droidcafe.MainActivity">
   <meta-data
      android:name="android.support.PARENT_ACTIVITY"
      android:value=".MainActivity"/>
</activity>

Main Activity with a Fragment for each child

If the child screen siblings do not have another level of child screens under them, you can define each one as a Fragment, which represents a behavior or portion of a UI within in an activity. Think of a fragment as a modular section of an activity which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.

You can add more than one fragment in a single activity. For example, in a section sibling screen showing a news story and implemented as an Activity, you might have a child screen for a video clip implemented as a Fragment. You would implement a way for the user to navigate to the video clip Fragment, and then back to the Activity that shows the story.

Ancestral navigation (the Up button)

With ancestral navigation in a multitier hierarchy, you enable the user to go up from a section sibling to the collection sibling, and then up to the parent screen.  Ancestral navigation

In the figure above:

  1. Up button for ancestral navigation from the first-level siblings to the parent.
  2. Up button for ancestral navigation from second-level siblings to the first-level child screen acting as a parent screen.

The Up button is used to navigate within an app based on the hierarchical relationships between screens. For example (referring to the figure above):

  • If a first-level child screen offers headlines to navigate to second-level child screens, the second-level child screen siblings should offer Up buttons that return to the first-level child screen, which is their shared parent.
  • If the parent screen offers navigation to first-level child siblings, then the first-level child siblings should offer an Up button that returns to the parent screen.
  • If the parent screen is the topmost screen in an app (that is, the app's home screen), it should not offer an Up button.

Tip: The Back button below the screen differs from the Up button. The Back button provides navigation to whatever screen you viewed previously. If you have several children screens that the user can navigate through using a lateral navigation pattern (as described later in this chapter), the Back button would send the user back to the previous child screen, not to the parent screen. Use an Up button if you want to provide ancestral navigation from a child screen back to the parent screen. For more information about Up navigation, see Providing Up Navigation. See the concept chapter on menus and pickers for details on how to implement the app bar.

To provide the Up button for a child screen Activity, declare the parent of the Activity to be the main Activity in the AndroidManifest.xml file:

<activity android:name="com.example.android.droidcafeinput.OrderActivity"
    android:label="Order Activity"
    android:parentActivityName=".MainActivity">
    <meta-data android:name="android.support.PARENT_ACTIVITY"
        android:value=".MainActivity"/>
</activity>

The snippet above in AndroidManifest.xml declares the parent for the child screen OrderActivity to be MainActivity. It also sets the android:label to a title for the Activity screen to be "Order Activity". The child screen now includes the Up button in the app bar (highlighted in the figure below), which the user can tap to navigate back to the parent screen.  Up Button for Up Navigation

Descendant navigation

With descendant navigation, you enable the user to go from the parent screen to a first-level child screen, and from a first-level child screen down to a second-level child screen.  Descendant navigation

In the figure above:

  1. Descendant navigation from parent to first-level child screen
  2. Descendant navigation from headline in a first-level child screen to a second-level child screen

Buttons or targets

The best practice for descendant navigation from the parent screen to collection siblings is to use buttons or simple targets such as an arrangement of images or iconic buttons (also known as a dashboard). When the user touches a button, the collection sibling screen opens, replacing the current context (screen) entirely.

Tip: Buttons and simple targets are rarely used for navigating to section siblings within a collection. See lists, carousels, and cards in the next section.  Descendant navigation using buttons

In the figure above:

  1. Buttons on a parent screen
  2. Targets (Image buttons or icons) on a parent screen
  3. Descendant navigation pattern from parent screen to first-level child siblings

A dashboard usually has either two or three rows and columns, with large touch targets to make it easy to use. Dashboards are best when each collection sibling is equally important. You can use a LinearLayout, RelativeLayout, or GridLayout. See Layouts for an overview of how layouts work.

A navigation drawer is a panel that usually displays navigation options on the left edge of the screen, as shown on the right side of the figure below. It is hidden most of the time, but is revealed when the user swipes a finger from the left edge of the screen or touches the navigation icon in the app bar, as shown on the left side of the figure below.  A navigation drawer

In the figure above:

  1. Navigation icon in the app bar
  2. Navigation drawer
  3. Navigation drawer menu item

A good example of a navigation drawer is in the Gmail app, which provides access to the inbox, labeled email folders, and settings. The best practice for employing a navigation drawer is to provide descendant navigation from the parent Activity to all of the other child screens in an app. It can display many navigation targets at once—for example, it can contain buttons (like a dashboard), tabs, or a list of items (like the Gmail drawer).

To make a navigation drawer in your app, you need to create the following layouts:

  • A navigation drawer as the Activity layout root ViewGroup
  • A navigation View for the drawer itself
  • An app bar layout that includes room for a navigation icon button
  • A content layout for the Activity that displays the navigation drawer
  • A layout for the navigation drawer header

Follow these general steps:

  1. Populate the navigation drawer menu with item titles and icons.
  2. Set up the navigation drawer and item listeners in the Activity code.
  3. Handle the navigation menu item selections.

Creating the navigation drawer layout

To create a navigation drawer layout, use the DrawerLayout APIs available in the Support Library. For design specifications, follow the design principles for navigation drawers in the Navigation Drawer design guide.

To add a navigation drawer, use a DrawerLayout as the root ViewGroup of your Activity layout. Inside the DrawerLayout, add one View that contains the main content for the screen (your primary layout when the drawer is hidden) and another View, typically a NavigationView, that contains the contents of the navigation drawer.

Tip: To make your layouts simpler to understand, use the include tag to include an XML layout within another XML layout.

For example, the following layout uses:

  • A DrawerLayout as the root of the Activity layout in activity_main.xml.
  • The main content of screen defined in the app_bar_main.xml layout file.
  • A NavigationView that represents a standard navigation menu that can be populated by a menu resource XML file.

Refer to the figure below that corresponds to this layout:

<android.support.v4.widget.DrawerLayout 
    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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" />

</android.support.v4.widget.DrawerLayout>

 Layout for Implementing a Navigation Drawer

In the figure above:

  1. DrawerLayout is the root ViewGroup of the Activity layout.
  2. The included app_bar_main.xml uses a CoordinatorLayout as its root, and defines the app bar layout with a Toolbar which will include the navigation icon to open the drawer.
  3. The NavigationView defines the navigation drawer layout and its header, and adds menu items to it.

Note the following in the activity_main.xml layout:

  • The android:id for the DrawerLayout is drawer_layout. You will use this id to instantiate a drawer object in your code.
  • The android:id for the NavigationView is nav_view. You will use this id to instantiate a navigationView object in your code.
  • The NavigationView must specify its horizontal gravity with the android:layout_gravity attribute. Use the "start" value for this attribute (rather than "left"), so that if the app is used with right-to-left (RTF) languages, the drawer appears on the right rather than the left side.

android:layout_gravity="start"

  • Use the android:fitsSystemWindows="true" attribute to set the padding of the DrawerLayout and the NavigationView to ensure the contents don't overlay the system windows. DrawerLayout uses fitsSystemWindows as a sign that it needs to inset its children (such as the main content ViewGroup), but still draw the top status bar background in that space. As a result, the navigation drawer appears to be overlapping, but not obscuring, the translucent top status bar. The insets you get from fitsSystemWindows will be correct on all platform versions to ensure that your content does not overlap with system-provided UI components.

The navigation drawer header

The NavigationView specifies the layout for the header of the navigation drawer with the attribute app:headerLayout="@layout/nav_header_main". The nav_header_main.xml file defines the layout of this header to include an ImageView and a TextView, which is typical for a navigation drawer, but you could also include other View elements.

Tip: The header's height should be 160dp, which you should extract into a dimension resource (nav_header_height).

The following is the code for the nav_header_main.xml file:

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/nav_header_height"
    android:background="@drawable/side_nav_bar"
    android:gravity="bottom"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/nav_header_vertical_spacing"
        android:src="@android:drawable/sym_def_app_icon" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/nav_header_vertical_spacing"
        android:text="@string/my_app_title"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

</LinearLayout>

The app bar layout

The include tag in the activity_main.xml layout file includes the app_bar_main.xml layout file, which uses a CoordinatorLayout as its root. The app_bar_main.xml file defines the app bar layout with the Toolbar class as shown previously in the chapter about menus and pickers. It also defines a floating action button, and uses an include tag to include the content_main.xml layout.

The following is the code for the app_bar_main.xml file:

<android.support.design.widget.CoordinatorLayout 
    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"
    android:fitsSystemWindows="true"
    tools:context="com.example.android.navigationexperiments.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

Note the following:

  • The app_bar_main.xml layout uses a CoordinatorLayout as its root, and includes the content_main.xml layout.
  • The app_bar_main.xml layout uses the android:fitsSystemWindows="true" attribute to set the padding of the app bar to ensure that it doesn't overlay the system windows such as the status bar.

The content layout for the main activity screen

The layout above uses an include tag to include the content_main.xml layout, which defines the layout of the main Activity screen. In the example layout below, the main Activity screen shows a TextView that displays the string "Hello World!". The following is the code for the content_main.xml file:

<RelativeLayout 
    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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.example.android.navigationexperiments.MainActivity"
    tools:showIn="@layout/app_bar_main">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
</RelativeLayout>

Note the following:

  • The content_main.xml layout must be the first child in the DrawerLayout because the drawer must be on top of the content. In our layout above, the content_main.xm layout is included in the app_bar_main.xml layout, which is the first child.
  • The content_main.xml layout uses a RelativeLayout ViewGroup set to match the parent view's width and height, because it represents the entire UI when the navigation drawer is hidden.
  • The layout behavior for the RelativeLayout is set to the string resource @string/appbar_scrolling_view_behavior, which controls the scrolling behavior of the screen in relation to the app bar at the top. The AppBarLayout.ScrollingViewBehavior class defines this behavior. View elements that scroll vertically should use this behavior, because it supports nested scrolling to automatically scroll any AppBarLayout siblings.

Populating the navigation drawer menu

The NavigationView in the activity_main.xml layout specifies the menu items for the navigation drawer using the following statement:

app:menu="@menu/activity_main_drawer"

The menu items are defined in the activity_main_drawer.xml file, which is located under app > res > menu in the Project > Android pane. The <group></group> tag defines a menu group—a collection of items that share traits, such as whether they are visible, enabled, or checkable. A group must contain one or more <item></> elements and be a child of a <menu> element, as shown below. In addition to defining each menu item's title with the android:title attribute, the file also defines each menu item's icon with the android:icon attribute.

The group is defined with the android:checkableBehavior attribute. This attribute lets you put interactive elements within the navigation drawer, such as toggle switches that can be turned on or off, and checkboxes and radio buttons that can be selected. The choices for this attribute are:

  • single: Only one item from the group can be selected. Use for radio buttons.
  • all: All items can be selected. Use for checkboxes.
  • none: No items can be selected.

The following XML code snippet shows how to define a menu group:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <group android:checkableBehavior="none">
        <item
            android:id="@+id/nav_camera"
            android:icon="@drawable/ic_menu_camera"
            android:title="@string/import_camera" />
        <item
            android:id="@+id/nav_gallery"
            android:icon="@drawable/ic_menu_gallery"
            android:title="@string/gallery" />
        <item
            android:id="@+id/nav_slideshow"
            android:icon="@drawable/ic_menu_slideshow"
            android:title="@string/slideshow" />
        <item
            android:id="@+id/nav_manage"
            android:icon="@drawable/ic_menu_manage"
            android:title="@string/tools" />
    </group>

    <item android:title="@string/communicate">
        <menu>
            <item
                android:id="@+id/nav_share"
                android:icon="@drawable/ic_menu_share"
                android:title="@string/share" />
            <item
                android:id="@+id/nav_send"
                android:icon="@drawable/ic_menu_send"
                android:title="@string/send" />
        </menu>
    </item>

</menu>

Setting up the navigation drawer and item listeners

To use a listener for the navigation drawer's menu items, the Activity hosting the navigation drawer must implement the OnNavigationItemSelectedListener interface:

  1. Implement NavigationView.OnNavigationItemSelectedListener in the class definition:

     public class MainActivity extends AppCompatActivity implements 
                   NavigationView.OnNavigationItemSelectedListener {
    

    This interface offers the onNavigationItemSelected() method, which is called when an item in the navigation drawer menu item is tapped. As you enter OnNavigationItemSelectedListener, the red light bulb appears on the left margin.

  2. Click the light bulb, choose Implement methods, and choose the onNavigationItemSelected(item:MenuItem):boolean method.

    Android Studio adds a stub for the method:

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
         return false;
    }
    

    You learn how to use this stub in the next section.

  3. Before setting up the navigation item listener, add code to the onCreate() method for the Activity to instantiate the DrawerLayout and NavigationView objects (drawer and navigationView in the code below):

     @Override
     protected void onCreate(Bundle savedInstanceState) {
        // ... Rest of onCreate code.
        DrawerLayout drawer = (DrawerLayout) 
                              findViewById(R.id.drawer_layout);
        ActionBarDrawerToggle toggle = 
                    new ActionBarDrawerToggle(this, drawer, toolbar, 
                    R.string.navigation_drawer_open, 
                    R.string.navigation_drawer_close);
        if (drawer != null) {
           drawer.addDrawerListener(toggle);
        }
        toggle.syncState();
    
        NavigationView navigationView = (NavigationView) 
                              findViewById(R.id.nav_view);
        if (navigationView != null) {
           navigationView.setNavigationItemSelectedListener(this);
        }
     }
    

    The code above instantiates an ActionBarDrawerToggle, which substitutes a special drawable for the Up button in the app bar, and links the Activity to the DrawerLayout. The special drawable appears as a "hamburger" navigation icon when the drawer is closed, and animates into an arrow as the drawer opens.

Note: Be sure to use the ActionBarDrawerToggle in support-library-v7.appcompact, not the version in support-library-v4.

Tip: You can customize the animated toggle by defining the drawerArrowStyle in your ActionBar theme. For more detailed information about the ActionBar theme, see Adding the App Bar in the Android Developer documentation.

The code above implements addDrawerListener() to listen for drawer open and close events, so that when the user taps custom drawable button, the navigation drawer slides out.

You must also use the syncState() method of ActionBarDrawerToggle to synchronize the state of the drawer indicator. The synchronization must occur after the DrawerLayout instance state has been restored, and any other time when the state may have diverged in such a way that the ActionBarDrawerToggle was not notified.

The code above ends by setting a listener, setNavigationItemSelectedListener(), to the navigation drawer to listen for item clicks.

The ActionBarDrawerToggle also lets you specify the strings to use to describe the open/close drawer actions for accessibility services. Define the strings in your strings.xml file:

<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>

Handling navigation menu item selections

Add code to the onNavigationItemSelected() method stub to handle menu item selections. This method is called when an item in the navigation drawer menu is tapped. You can use switch case statements to take the appropriate action based on the menu item's id, which you can retrieve using the getItemId() method:

@Override
public boolean onNavigationItemSelected(MenuItem item) {
   DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
   // Handle navigation view item clicks here.
   switch (item.getItemId()) {
      case R.id.nav_camera:
         // Handle the camera import action (for now display a toast).
         drawer.closeDrawer(GravityCompat.START);
         displayToast(getString(R.string.chose_camera));
         return true;
      case R.id.nav_gallery:
         // Handle the gallery action (for now display a toast).
         drawer.closeDrawer(GravityCompat.START);
         displayToast(getString(R.string.chose_gallery));
         return true;
      case R.id.nav_slideshow:
         // Handle the slideshow action (for now display a toast).
         drawer.closeDrawer(GravityCompat.START);
         displayToast(getString(R.string.chose_slideshow));
         return true;
      case R.id.nav_manage:
         // Handle the tools action (for now display a toast).
         drawer.closeDrawer(GravityCompat.START);
         displayToast(getString(R.string.chose_tools));
         return true;
      case R.id.nav_share:
         // Handle the share action (for now display a toast).
         drawer.closeDrawer(GravityCompat.START);
         displayToast(getString(R.string.chose_share));
         return true;
      case R.id.nav_send:
         // Handle the send action (for now display a toast).
         drawer.closeDrawer(GravityCompat.START);
         displayToast(getString(R.string.chose_send));
         return true;
      default:
         return false;
   }
}

After the user taps a navigation drawer selection or taps outside the drawer, the DrawerLayout closeDrawer() method closes the drawer.

Lists and carousels

Use a scrolling list, such as a RecyclerView, to provide navigation targets for descendant navigation. Vertically scrolling lists are often used for a screen that lists stories, with each list item acting as a button to each story. For more visual or media-rich content items such as photos or videos, you may want to use a horizontally scrolling list (also known as a carousel). These UI elements are good for presenting items in a collection (for example, a list of news stories).

You learn about RecyclerView in another chapter.

Master/detail navigation flow

In a master/detail navigation flow, a master screen contains a list of items, and a detail screen shows detailed information about one item. You usually implement descendant navigation using one of the following techniques:

  • Use an intent to starts an activity that represents the detail screen. For more information about intents, see Intents and Intent Filters in the Android developer documentation.
  • When adding a Settings Activity, extend PreferenceActivity to create a two-pane master/detail layout to support large screens. Replace the activity content with a Settings Fragment. This is a useful pattern if you have multiple groups of settings and need to support tablet-sized screens as well as smartphones. You learn about the Settings activity and PreferenceActivity in another chapter. For more information about using fragments, see Fragments in the Android developer documentation.

Smartphones are best suited for displaying one screen at a time—for example a master screen (on the left side of the figure below) and a detail screen (on the right side of the figure below).  Smartphone master screen (left) and detail screen (right).

On the other hand, tablet displays, especially when viewed in the landscape orientation, are best suited for showing multiple content panes at a time: the master on the left, and the detail to the right, as shown below.  Master/detail layout for tablets

Options menu in the app bar

The app bar typically contains the options menu, which is most often used for navigation patterns for descendant navigation. It may also contain an Up button for ancestral navigation, a nav icon for opening a navigation drawer, and a filter icon to filter page views. You learn how to set up the options menu and the app bar in another chapter.

Lateral navigation with tabs and swipes

With lateral navigation, you enable the user to go from one sibling to another (at the same level in a multitier hierarchy). For example, if your app provides several categories of stories (such as Top Stories, Tech News, and Cooking, as shown in the figure below), you would want to provide your users the ability to navigate from one category to the next, or from one top story to the next, without having to navigate back up to the parent screen.  Lateral navigation with tabs

In the figure above:

  1. Lateral navigation from one category screen to another
  2. Lateral navigation from one story screen to another

Another example of lateral navigation is the ability to swipe left or right in a Gmail conversation to view a newer or older email in the same inbox.

You can implement lateral navigation with tabs that represent each screen. Tabs appear across the top of a screen, as shown on the left side of the figure above, providing navigation to other screens. Tab navigation is a common solution for lateral navigation from one child screen to another child screen that is a sibling—in the same position in the hierarchy and sharing the same parent screen.

Tabs are most appropriate for small sets (four or fewer) of sibling screens. You can combine tabs with swipe views, so that the user can swipe across from one screen to another as well as tap a tab.

Tabs offer two benefits:

  • Because there is a single, initially selected tab, users already have access to that tab's content from the parent screen without any further navigation.
  • Users can navigate quickly between related screens, without needing to first revisit the parent.

Keep in mind the following best practices when using tabs:

  • Tabs are usually laid out horizontally.
  • Tabs should always run along the top of the screen, and should not be aligned to the bottom of the screen.
  • Tabs should be persistent across related screens. Only the designated content region should change when tapping a tab, and tab indicators should remain available at all times.
  • Switching to another tab should not be treated as history. For example, if a user switches from tab A to tab B, pressing the Up button in the app bar should not reselect tab A but should instead return the user to the parent screen.

The key steps for implementing tabs are as follows:

  1. Define the tab layout. The main class used for displaying tabs is TabLayout. It provides a horizontal layout to display tabs. You can show the tabs below the app bar.
  2. Implement a Fragment for each tab content screen. A Fragment is a behavior or a portion of a UI within an Activity. It's like a mini-Activity within the main Activity, with its own lifecycle. One benefit of using a Fragment for each tabbed content is that you can isolate the code for managing the tabbed content inside the Fragment. To learn about Fragment, see Fragments in the API Guide.
  3. Add a pager adapter. Use the PagerAdapter class to populate "pages" (screens) inside of a ViewPager, which is a layout manager that lets the user flip left and right through screens of data. You supply an implementation of a PagerAdapter to generate the screens that the View shows. ViewPager is most often used in conjunction with Fragment, which is a convenient way to supply and manage the lifecycle of each screen.
  4. Create an instance of the tab layout, and set the text for each tab.
  5. Use PagerAdapter to manage screens ("pages"). Each screen is represented by its own Fragment.
  6. Set a listener to determine which tab is tapped.

There are standard adapters for using a Fragment with the ViewPager:

  • FragmentPagerAdapter: Designed for navigating between sibling screens (pages) representing a fixed, small number of screens.
  • FragmentStatePagerAdapter: Designed for paging across a collection of screens (pages) for which the number of screens is undetermined. It destroys each Fragment as the user navigates to another screen, minimizing memory usage.

Defining tab layout

To use a TabLayout, you can design the main Activity layout to use a Toolbar for the app bar, a TabLayout for the tabs below the app bar, and a ViewPager within the root layout to switch child elements. The layout should look similar to the following, assuming each child element fills the screen:

<android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

<android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/toolbar"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>

<android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_below="@id/tab_layout"/>

For each child view, create a layout for each Fragment such as tab_fragment1.xml, tab_fragment2.xml, and so on.  Tab navigation

Implementing each fragment

A Fragment is a behavior or a portion of a UI within an Activity. It's like a mini-Activity within the main Activity, with its own lifecycle. To learn about Fragment, see Fragments in the API Guide.

Add a class for each Fragment (such as TabFragment1.java, TabFragment2.java, and TabFragment3.java) representing each screen the user can visit by clicking a tab. Each class should extend Fragment and inflate the layout associated with the screen (tab_fragment1.xml, tab_fragment2.xml, and tab_fragment3.xml). For example, TabFragment1.java looks like this:

public class TabFragment1 extends Fragment {

   @Override
   public View onCreateView(LayoutInflater inflater, 
                          ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.tab_fragment1, container, false);
   }
}

Adding a pager adapter

Add a PagerAdapter that extends FragmentStatePagerAdapter. The code should do the following:

  1. Define the number of tabs.
  2. Use the getItem() method of the Adapter class to determine which tab is clicked.
  3. Use a switch case block to return the screen (page) to show based on which tab is clicked.

The following is an example:

public class PagerAdapter extends FragmentStatePagerAdapter {
    int mNumOfTabs;

    public PagerAdapter(FragmentManager fm, int NumOfTabs) {
        super(fm);
        this.mNumOfTabs = NumOfTabs;
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0: return new TabFragment1();
            case 1: return new TabFragment2();
            case 2: return new TabFragment3();
            default: return null;
        }
    }

    @Override
    public int getCount() {
        return mNumOfTabs;
    }
}

Creating an instance of the tab layout

In the onCreate() method of the main Activity, create an instance of the tab layout from the tab_layout element in the layout, and set the text for each tab using addTab():

@Override
protected void onCreate(Bundle savedInstanceState) {
   // ... Rest of onCreate code
   // Create an instance of the tab layout from the view.
   TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
   // Set the text for each tab.
   tabLayout.addTab(tabLayout.newTab().setText("Top Stories"));
   tabLayout.addTab(tabLayout.newTab().setText("Tech News"));
   tabLayout.addTab(tabLayout.newTab().setText("Cooking"));
   // Set the tabs to fill the entire layout.
   tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
   // Use PagerAdapter to manage page views in fragments.
}

Managing screen views in fragments with a listener

Use PagerAdapter in the onCreate() method of the main Activity to manage screen ("page") views in each Fragment. Each screen is represented by its own Fragment. You also need to set a listener to determine which tab is tapped. The following code should appear after the code from the previous section in the onCreate() method:

@Override
protected void onCreate(Bundle savedInstanceState) {
   // ... Rest of onCreate code
   // Use PagerAdapter to manage page views in fragments.
   final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
   final PagerAdapter adapter = new PagerAdapter
                (getSupportFragmentManager(), tabLayout.getTabCount());
   viewPager.setAdapter(adapter);
   // Setting a listener for clicks.
   viewPager.addOnPageChangeListener(new 
                TabLayout.TabLayoutOnPageChangeListener(tabLayout));
   tabLayout.addOnTabSelectedListener(new 
                                  TabLayout.OnTabSelectedListener() {
      @Override
      public void onTabSelected(TabLayout.Tab tab) {
         viewPager.setCurrentItem(tab.getPosition());
      }

      @Override
         public void onTabUnselected(TabLayout.Tab tab) {

      }

      @Override
         public void onTabReselected(TabLayout.Tab tab) {
      }
   });
}

Using ViewPager for swipe views (horizontal paging)

ViewPager is a layout manager that lets the user flip left and right through "pages" (screens) of content. ViewPager is most often used in conjunction with Fragment, which is a convenient way to supply and manage the lifecycle of each "page". ViewPager also provides the ability to swipe "pages" horizontally.

In the previous example, you used a ViewPager within the root layout to switch child screens. This provides the ability for the user to swipe from one child screen to another. Users are able to navigate to sibling screens by touching and dragging the screen horizontally in the direction of the desired adjacent screen.

Swipe views are most appropriate where there is some similarity in content type among sibling pages, and when the number of siblings is relatively small. In these cases, this pattern can be used along with tabs above the content region to indicate the current page and available pages, to aid discoverability and provide more context to the user.

Tip: It's best to avoid horizontal paging when child screens contain horizontal panning surfaces (such as maps), as these conflicting interactions may deter your screen's usability.

The related practical is 4.4: User navigation.

Learn more

Android developer documentation:

Material Design spec:

Android Developers Blog: Android Design Support Library

Other:

results matching ""

    No results matching ""