Sunday, October 18, 2015

Fragment Navigation Drawer in android

In Common Navigation Paradigms cliffnotes, we discuss the various navigational structures available within Android apps. One of the most flexible is the Navigation Drawer. During the I/O Conference 2015, Google released NavigationView, which makes it far easier to create it than the previously documented instructions.
With the release of Android 5.0 Lollipop, the new material design style navigation drawer spans the full height of the screen and is displayed over the ActionBar and overlaps the translucent StatusBar. Read the material design style navigation drawer document for specs on styling your navigation drawer.

Usage

This guide explains how to setup a basic material design style drawer filled with navigation items that switch different fragments into the content area. In this way, you can define multiple fragments, and then define the list of options which will display in the drawers items list. Each item when clicked will switch the relevant fragment into the activity's container view.

Setup

Make sure to setup the Google Design Support Library before using Google's newNavigationView, announced as part of the Android M release. The NavigationView should be backwards compatible with all versions down to Android 2.1.

Download Nav Drawer Item icons

Download the following icons and add them to your drawable folders by copying and pasting them into the drawable folder or using the New Image Asset dialog to create versions for each density.
If you use the New Image Asset dialog, choose a black foreground color and change the resource name.

Setup Drawer Resources

Create a menu/drawer_view.xml file:
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_first_fragment"
            android:icon="@drawable/ic_one"
            android:title="First" />
        <item
            android:id="@+id/nav_second_fragment"
            android:icon="@drawable/ic_two"
            android:title="Second" />
        <item
            android:id="@+id/nav_third_fragment"
            android:icon="@drawable/ic_three"
            android:title="Third" />
    </group>
</menu>
Note that you can set one of these elements to be default selected by usingandroid:checked="true".
You can also create subheaders too and group elements together:
 <item android:title="Sub items">
        <menu>
            <item
                android:icon="@drawable/ic_dashboard"
                android:title="Sub item 1" />
            <item
                android:icon="@drawable/ic_forum"
                android:title="Sub item 2" />
        </menu>
    </item>

Define Fragments

Next, you need to define your fragments that will be displayed within the drawer. These can be any support fragments you define within your application. Make sure that all the fragments extend from android.support.v4.app.Fragment.

Setup Toolbar

In order to slide our navigation drawer over the ActionBar, we need to use the newToolbar widget as defined in the AppCompat v21 library. The Toolbar can be embedded into your view hierarchy which makes sure that the drawer slides over the ActionBar.
Create a new layout file res/layout/toolbar.xml with the following code:
<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:fitsSystemWindows="true"
    android:minHeight="?attr/actionBarSize"
    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:background="?attr/colorPrimaryDark">
</android.support.v7.widget.Toolbar>
Note that when the android:fitsSystemWindows attribute is set to true for a view, the view would be laid out as if the StatusBar and the ActionBar were present i.e. the UI on top gets padding enough to not be obscured by the navigation bar. Without this attribute, there is not enough padding factored into consideration for the ToolBar:
We want our main content view to have the navigation bar and henceandroid:fitsSystemWindows is set to true for the Toolbar.
To use the Toolbar as an ActionBar, you need to disable the default ActionBar. This can be done by setting the app theme in styles.xml file.
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">#673AB7</item>
        <item name="colorPrimaryDark">#512DA8</item>
        <item name="colorAccent">#FF4081</item>
    </style>
</resources>
Also note that normally you should decide on your color scheme by going to Material Palette and choosing a primary and dark primary color. For this example, we will pick purple-based colors as shown in the screenshot.
Note: If you forget to disable the ActionBar in styles.xml, you are likely to see ajava.lang.IllegalStateException with an error message that reads This Activity already has an action bar supplied by the window decor. Do not request Window.FEATURE_ACTION_BAR and set windowActionBar to false in your theme to use a Toolbar instead. If you see this message, you need to make sure to follow the previous steps.

Setup Drawer in Activity

Next, let's setup a basic navigation drawer based on the following layout file which has the entire drawer setup in res/layout/activity_main.xml. Note that the Toolbar is added as the first child of the main content view by adding the include tag.
<!-- This DrawerLayout has two children at the root  -->
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- This LinearLayout represents the contents of the screen  -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!-- The ActionBar displayed at the top -->
        <include
            layout="@layout/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <!-- The main content view where fragments are loaded -->
        <FrameLayout
            android:id="@+id/flContent"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

    <!-- The navigation drawer that comes from the left -->
    <!-- Note that `android:layout_gravity` needs to be set to 'start' -->
    <android.support.design.widget.NavigationView
        android:id="@+id/nvView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="@android:color/white"
        app:menu="@menu/drawer_view" />
</android.support.v4.widget.DrawerLayout>
Add Gradle dependency support, and sync with gradle:
compile 'com.android.support:design:22.2.0'
Now, let's setup the drawer in our activity. We can also setup the menu icon too.
Note: Make sure you implement the correct onPostCreate(Bundle savedInstanceState)method. There are 2 signatures and only this one shows the hamburger icon.
public class MainActivity extends AppCompatActivity {
    private DrawerLayout mDrawer;
    private Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Set a Toolbar to replace the ActionBar.
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Find our drawer view
        mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // The action bar home/up action should open or close the drawer.
        switch (item.getItemId()) {
            case android.R.id.home:
                mDrawer.openDrawer(GravityCompat.START);
                return true;
            }

            return super.onOptionsItemSelected(item);
    }

        // Make sure this is the method with just `Bundle` as the signature
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
            super.onPostCreate(savedInstanceState);
    }
}

Navigating between Menu Items

Setup a handler to respond to click events on the navigation elements and swap out the fragment.
@Override
    protected void onCreate(Bundle savedInstanceState) {

         // Find our drawer view
        nvDrawer = (NavigationView) findViewById(R.id.nvView);
        // Setup drawer view
        setupDrawerContent(nvDrawer);
    }

    private void setupDrawerContent(NavigationView navigationView) {
        navigationView.setNavigationItemSelectedListener(
                new NavigationView.OnNavigationItemSelectedListener() {
                    @Override
                    public boolean onNavigationItemSelected(MenuItem menuItem) {
                        selectDrawerItem(menuItem);
                        return true;
                    }
                });
    }

    public void selectDrawerItem(MenuItem menuItem) {
        // Create a new fragment and specify the planet to show based on
        // position
        Fragment fragment = null;

        Class fragmentClass;
        switch(menuItem.getItemId()) {
            case R.id.nav_first_fragment:
                fragmentClass = FirstFragment.class;
                break;
            case R.id.nav_second_fragment:
                fragmentClass = SecondFragment.class;
                break;
            case R.id.nav_third_fragment:
                fragmentClass = ThirdFragment.class;
                break;
            default:
                fragmentClass = FirstFragment.class;
        }

        try {
            fragment = (Fragment) fragmentClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Insert the fragment by replacing any existing fragment
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit();

        // Highlight the selected item, update the title, and close the drawer
        menuItem.setChecked(true);
        setTitle(menuItem.getTitle());
        mDrawer.closeDrawers();
    }

Add Navigation Header

The NavigationView also accepts a custom attribute that can reference a layout that provides a header of our layout. For instance, you can create a layout/nav_header.xmlsimilar to the following:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="192dp"
    android:background="?attr/colorPrimaryDark"
    android:padding="16dp"
    android:theme="@style/ThemeOverlay.AppCompat.Dark"
    android:orientation="vertical"
    android:gravity="bottom">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Header"
        android:textColor="@android:color/white"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>

</LinearLayout>
You would then reference this in the layout res/layout/activity_main.xml in theNavigationView with the app:headerLayout custom attribute:
<!-- res/layout/activity_main.xml -->

 <!-- The navigation drawer -->
    <android.support.design.widget.NavigationView
        ...
        app:headerLayout="@layout/nav_header">

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

Animate the Hamburger Icon

In order for the hamburger icon to animate to indicate the drawer is being opened and closed, we need to use the ActionBarDrawerToggle class.
In your res/values/strings.xml add the following:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="drawer_open">Open navigation drawer</string>
    <string name="drawer_close">Close navigation drawer</string>
</resources>
We need to tie the DrawerLayout and Toolbar together:
  protected void onCreate(Bundle savedInstanceState) { 
    // Set a Toolbar to replace the ActionBar.
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Find our drawer view
        dlDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawerToggle = setupDrawerToggle();

        // Tie DrawerLayout events to the ActionBarToggle
        dlDrawer.setDrawerListener(drawerToggle);
   }

   private ActionBarDrawerToggle setupDrawerToggle() {
        return new ActionBarDrawerToggle(this, dlDrawer, toolbar, R.string.drawer_open,  R.string.drawer_close);
   }
Next, we need to make sure we synchronize the state whenever the screen is restored or there is a configuration change (i.e screen rotation):
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        drawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Pass any configuration change to the drawer toggles
        drawerToggle.onConfigurationChanged(newConfig);
    }
We also need to change the onOptionsItemSelected() method and allow the ActionBarToggle to handle the events.
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (drawerToggle.onOptionsItemSelected(item)) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
The ActionBarToggle will perform the same function done previously but adds a bit more checks and allows mouse clicks on the icon to open and close the drawer. See the source code for more context.
One thing to note is that the ActionBarDrawerToggle renders a customDrawerArrowDrawable for you for the hamburger icon.

Making Status Bar Translucent

To have the status bar translucent and have our drawer slide over it, we need to setandroid:windowTranslucentStatus to true. Because this style is not available for pre Kitkat devices, we'll add res/values-v19/styles.xml file for API version 19 and onwards. Note: If you modify your res/values/styles.xml directly with thisandroid:windowTranslucentStatus line, you are likely to need to build only for SDK versions 19 or higher, which will obviously limit you from supporting many older devices.
In res/values-v19/styles.xml we can add the following:
<resources>
  <!-- Base application theme. -->
  <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="android:windowTranslucentStatus">true</item>
  </style>
</resources>
Now if you run your app, you should see the navigation drawer and be able to select between your fragments.

Persistent Navigation Drawer

In certain situations, especially on tablets, the navigation drawer should be a permanent fixture on the activity acting as a sidebar:
Persistent

No comments:

Post a Comment