Mobile
Article

Material Design with the Android Design Support Library

By Joyce Echessa

Material Design, a new design language that gives design guidelines for Android apps and apps on other platforms, was introduced with the release of Android 5.0 Lollipop.

With it came new UI components such as the ‘Floating Action Button’. Implementing these new components while ensuring backward compatibility was typically a tedious process. Third party libraries would usually be needed to streamline the process.

At this year’s Google IO conference, Google introduced the Android Design Support Library which brings a number of important material design components to developers. The components are backwards compatible, with support back to Android 2.1 and implementing them is easier than previously. The library includes a navigation drawer view, floating labels for editing text, a floating action button, snackbar, tabs and a motion and scroll framework to tie them together. In this tutorial we’ll create an app that showcases these components.

Getting Started

You can find the final project code on gitHub.

Before getting started on the components, we’ll set up the project and set some styles. Using Android Studio, create a new Android project. Name it ‘Design Demo’ and leave the other settings as default, ensure the Minimum SDK version is at API level 15.

Add the following dependencies to the build.gradle (Module:app) file.

compile 'com.android.support:design:22.2.1'
compile 'com.android.support:cardview-v7:22.2.1'

The first statement adds the design support library and the second the CardView library, which we’ll use later. Sync the project with the updated Gradle files. This may involve downloading some of the support libraries.

Create a resource file in the res/values folder called colors.xml. Modify it as below:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="primary">#3F51B5</color>
    <color name="primary_dark">#303F9F</color>
    <color name="accent">#FF4081</color>
</resources>

Modify res/values/strings.xml as below. These are all the Strings we’ll need for the project:

<resources>
    <string name="app_name">Design Demo</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="nav_item_attachment">Attachment</string>
    <string name="nav_item_images">Images</string>
    <string name="nav_item_location">My Location</string>
    <string name="nav_sub_menu">Sub Menu</string>
    <string name="nav_sub_menu_item01">Sub Menu Item 1</string>
    <string name="nav_sub_menu_item02">Sub Menu Item 2</string>
    <string name="drawer_header_text">Drawer Header</string>
    <string name="second_activity_text">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin consectetur diam id aliquam scelerisque. Donec ultrices lacus vel dignissim pharetra. Vivamus pharetra augue quis rhoncus placerat. Sed ultricies at risus non cursus. Nam rutrum leo nec placerat consectetur. Vestibulum feugiat eleifend diam, nec interdum augue tincidunt sit amet. Praesent feugiat est auctor lacus consectetur, vitae pellentesque dolor laoreet.</string>
    <string name="title_activity_second">SecondActivity</string>
</resources>

Modify res/values/styles.xml as below:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/primary</item>
        <item name="colorPrimaryDark">@color/primary_dark</item>
        <item name="colorAccent">@color/accent</item>
    </style>

</resources>

In the code above, we customize the app’s Color Palette by setting the primary, primary-dark and accent colors as specified in the material design guide.

Other theme settings that can be customized are shown in the image below.

Notice we do not include the android: prefix before the item name (e.g. android:colorPrimaryDark). This is for backward compatibility. The android: annotation requires the minimum API level set to 21. We use a theme with NoActionBar because we’ll be using a Toolbar as our Action Bar (or AppBar as it is now called).

Material Design Color Palette

(SOURCE: https://developer.android.com/training/material/theme.html)

To create the Toolbar, modify res/layout/activity_main:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark" />

</RelativeLayout>

Above, we remove the default padding on the RelativeLayout and add a Toolbar from the support library. If you are supporting devices from API level 21 and above, you would only need to use the default Toolbar component and not one from the support library.

In MainActivity.java modify the onCreate(Bundle) method:

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

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    ActionBar actionBar = getSupportActionBar();
    actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
    actionBar.setDisplayHomeAsUpEnabled(true);
}

Download the project resources from gitHub which contains drawable folders that you can paste into the res directory so that the above code doesn’t result in errors.

When prompted by Android Studio, ensure you import the Toolbar from the support library.

In the above code, we get a reference to the Toolbar and set it as the Action Bar. We then get a reference of the Action Bar and set its Home icon to the infamous hamburger menu icon.

Run the app and you should see the Toolbar set as the app’s AppBar.

Toolbar set as the Appbar

The Navigation Drawer is a common component in Android apps. It is one of the ways to build a navigation hierarchy on Android, the others being Tabs and Spinners. Implementing one was never a quick process, but now with the Design Support Library, its implementation is much simpler.

To get started, modify activity_main.xml as shown.

<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"
    android:fitsSystemWindows="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark" />

    </RelativeLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/drawer_header"
        app:menu="@menu/drawer"/>
</android.support.v4.widget.DrawerLayout>

In the above code, we add a NavigationView to the layout. A NavigationView is placed within a DrawerLayout. Two important attributes to note are app:headerLayout which controls the (optional) layout used for the header and app:menu which is the menu resource inflated for the navigation items (this can also be updated at runtime).

Create a file labelled drawer_header.xml in the res/layout folder and modify it as shown.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:background="?attr/colorPrimaryDark"
    android:padding="16dp"
    android:theme="@style/ThemeOverlay.AppCompat.Dark"
    android:gravity="bottom">

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

</LinearLayout>

This creates a header for the drawer that will have a background color of the app’s set primary color (in our case blue), with a height of 150dp and some text.

Next create a file named drawer.xml in the res/menu folder and modify it as shown.

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

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/navigation_item_attachment"
            android:checked="true"
            android:icon="@drawable/ic_attachment"
            android:title="@string/nav_item_attachment" />
        <item
            android:id="@+id/navigation_item_images"
            android:icon="@drawable/ic_image"
            android:title="@string/nav_item_images" />
        <item
            android:id="@+id/navigation_item_location"
            android:icon="@drawable/ic_place"
            android:title="@string/nav_item_location" />
    </group>

    <item android:title="@string/nav_sub_menu">
        <menu>
            <item
                android:icon="@drawable/ic_emoticon"
                android:title="@string/nav_sub_menu_item01" />
            <item
                android:icon="@drawable/ic_emoticon"
                android:title="@string/nav_sub_menu_item02" />
        </menu>
    </item>

</menu>

In the above, we create the drawer’s menu items. The first part shows a collection of checkable menu items. The checked item will appear highlighted in the navigation drawer, ensuring the user knows which navigation item is currently selected. In the second part, we use a subheader to separate the second group of items from the first.

In MainActivity.java add the following member variable.

private DrawerLayout mDrawerLayout;

Then add the following to the bottom of onCreate(Bundle).

mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);

Modify onOptionsItemSelected(MenuItem) as shown:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    switch (id) {
        case android.R.id.home:
            mDrawerLayout.openDrawer(GravityCompat.START);
            return true;
        case R.id.action_settings:
            return true;
    }

    return super.onOptionsItemSelected(item);
}

This will bring the drawer onto the screen when the Home button (the one with the hamburger menu icon) is tapped. Run the app and you should see the following.

Navigation View

To capture click events on the menu items we need to set an OnNavigationItemSelectedListener on the NavigationView. Place the following at the bottom of onCreate(Bundle).

NavigationView navigationView = (NavigationView) findViewById(R.id.navigation_view);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(MenuItem menuItem) {
            menuItem.setChecked(true);
            mDrawerLayout.closeDrawers();
            Toast.makeText(MainActivity.this, menuItem.getTitle(), Toast.LENGTH_LONG).show();
            return true;
        }
    });

The above code sets a listener on the navigation view so that when a drawer menu item is selected, the menu item is set to checked (this will only affect the menu items marked as checkable), The drawer is closed and a Toast is shown displaying the title of the selected menu item.

In a real app you would do something like navigate to another Fragment and not just show a Toast. If you open the drawer again, you can confirm that the selected menu item is now checked by its highlight (only for the items marked checkable).

Floating Action Button (FAB)

A floating action button is a round button denoting a primary action on your interface. The Design library’s FloatingActionButton gives a single consistent implementation, by default colored using the colorAccent from your theme.

In addition to the normal size (56dp) floating action button, it supports the mini size (40dp) when visual continuity with other elements is critical.

To add a FAB to the layout, modify res/layout/activity_main.xml as shown.

<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"
    android:fitsSystemWindows="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark" />

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:layout_marginBottom="@dimen/activity_vertical_margin"
            android:src="@drawable/ic_done" />

    </RelativeLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/drawer_header"
        app:menu="@menu/drawer"/>
</android.support.v4.widget.DrawerLayout>

In the above, we add the FloatingActionButton at the bottom right corner of the layout. android:src sets the icon shown within the button. In code, you can set this with setImageDrawable().

Run the app, and you should see the FAB.

Floating action button

We’ll set an onClickListener on the FAB in the next section.

Snackbar

Traditionally, if you wanted to present quick brief feedback to the user, you would use a Toast. Now there is another option – the Snackbar.

Snackbars are shown at the bottom of the screen and contain text with an optional single action. They automatically time out after the given time by animating off the screen. Users can also swipe them away before the timeout.

By including the ability to interact with the Snackbar through actions and swiping to dismiss, they are considerably more powerful than toasts, and the API is familiar.

In MainActivity.java add the following to the bottom of onCreate(Bundle):

FloatingActionButton fab = (FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Snackbar.make(findViewById(R.id.drawer_layout), "I'm a Snackbar", Snackbar.LENGTH_LONG).setAction("Action", new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, "Snackbar Action", Toast.LENGTH_LONG).show();
                }
            }).show();
        }
    });

In the above, we set an onClickListener on the FAB so that when it is tapped, a Snackbar will be shown. We create an Action on the Snackbar and set an onClickListener that displays a Toast when tapped.

Note the use of a View as the first parameter to make(). Snackbar will attempt to find an appropriate parent of the Snackbar’s view to ensure that it is anchored to the bottom. Run the app to test this.

Snackbar

Notice that the Snackbar overlaps the FAB. We’ll fix this in the section on CoordinatorLayout.

TabLayout

Using Tabs to switch between different views is not a new concept on Android. The Design library’s TabLayout simplifies the process of adding tabs to your app. It implements both fixed tabs, where the view’s width is divided equally between all of the tabs, as well as scrollable tabs, where the tabs are not a uniform size and can scroll horizontally.

To add a TabLayout to the app, modify res/layout/activity_main.xml as shown.

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    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:fitsSystemWindows="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            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="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark" />

            <android.support.design.widget.TabLayout
                android:id="@+id/tablayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="?attr/colorPrimary"
                app:tabGravity="fill"
                android:theme="@style/ThemeOverlay.AppCompat.Dark" />

            <android.support.v4.view.ViewPager
                android:id="@+id/viewpager"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"/>

        </LinearLayout>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:layout_marginBottom="@dimen/activity_vertical_margin"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:src="@drawable/ic_done"/>

    </RelativeLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/drawer_header"
        app:menu="@menu/drawer"/>
</android.support.v4.widget.DrawerLayout>

In the above, we add a TabLayout as well as a ViewPager. The ViewPager will be used to enable horizontal paging between tabs.

In MainActivity.java add the following subclasses.

public static class DesignDemoFragment extends Fragment {
    private static final String TAB_POSITION = "tab_position";

    public DesignDemoFragment() {

    }

    public static DesignDemoFragment newInstance(int tabPosition) {
        DesignDemoFragment fragment = new DesignDemoFragment();
        Bundle args = new Bundle();
        args.putInt(TAB_POSITION, tabPosition);
        fragment.setArguments(args);
        return fragment;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Bundle args = getArguments();
        int tabPosition = args.getInt(TAB_POSITION);
        TextView tv = new TextView(getActivity());
        tv.setGravity(Gravity.CENTER);
        tv.setText("Text in Tab #" + tabPosition);
        return tv;
    }
}
static class DesignDemoPagerAdapter extends FragmentStatePagerAdapter {

    public DesignDemoPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return DesignDemoFragment.newInstance(position);
    }

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

    @Override
    public CharSequence getPageTitle(int position) {
        return "Tab " + position;
    }
}

Then at the bottom of onCreate(Bundle) add the following.

DesignDemoPagerAdapter adapter = new DesignDemoPagerAdapter(getSupportFragmentManager());
ViewPager viewPager = (ViewPager)findViewById(R.id.viewpager);
viewPager.setAdapter(adapter);
TabLayout tabLayout = (TabLayout)findViewById(R.id.tablayout);
tabLayout.setupWithViewPager(viewPager);

In the above, we create a Fragment class that creates a simple fragment with a single TextView as its content. We then create a FragmentStatePagerAdapter that will be used as the ViewPager’s adapter. The getCount() function of the DesignDemoPagerAdapter class returns the number of pages/tabs. getItem(int) returns the Fragment that will be placed on the view page and getPageTitle(int) returns the title that will appear on a particular tab. For this to work, we use setupWithViewPager() on the TabLayout. This ensures that tab selection events update the ViewPager and page changes update the selected tab.

Run the app and you should see the tabs. You can swipe to change tabs or just tap on a tab to switch to it.

TabLayout

CoordinatorLayout

The Design library introduces the CoordinatorLayout, a layout which provides an additional level of control over touch events between child views, something which many of the components in the Design library take advantage of.

An example of this is when you add a FloatingActionButton as a child of your CoordinatorLayout and then pass that CoordinatorLayout to your Snackbar.make() call.

Instead of the snackbar displaying over the floating action button, as seen previously, the FloatingActionButton takes advantage of additional callbacks provided by CoordinatorLayout to automatically move upward as the snackbar animates in and returns to its position when the snackbar animates out. This is supported on Android 3.0 and higher devices.

To use the CoordinatorLayout in our layout, modify res/layout/activity_main.xml as shown:

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    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:fitsSystemWindows="true">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coordinator"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            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="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark" />

            <android.support.design.widget.TabLayout
                android:id="@+id/tablayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="?attr/colorPrimary"
                app:tabGravity="fill"
                android:theme="@style/ThemeOverlay.AppCompat.Dark" />

            <android.support.v4.view.ViewPager
                android:id="@+id/viewpager"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"/>

        </LinearLayout>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_gravity="bottom|right"
            android:layout_marginBottom="@dimen/activity_vertical_margin"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:src="@drawable/ic_done"/>

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

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/drawer_header"
        app:menu="@menu/drawer"/>
</android.support.v4.widget.DrawerLayout>

As seen above, the FAB needs to be a child of the CoordinatorLayout, so we replace the previous RelativeLayout with the CoordinatorLayout. Also note that we change the FAB’s position setting to android:layout_gravity="bottom|right".

Next in MainActivity.java when constructing the Snackbar, pass the CoordinatorLayout as the view parameter.

Snackbar.make(findViewById(R.id.coordinator), "I'm a Snackbar", Snackbar.LENGTH_LONG).setAction("Action", new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, "Snackbar Action", Toast.LENGTH_LONG).show();
                }
            }).show();

Run the app and now when you tap on the FAB, the Snackbar will slide into view, but this time it won’t overlap the FAB, the FAB will slide up with it and when the Snackbar is dismissed either by timing out or with a swipe, the FAB will fall back into place.

Snackbar working with the CoordinatorLayout

The other main use case for the CoordinatorLayout concerns the app bar and scrolling techniques. The design library provides the AppBarLayout which allows the Toolbar and other views (such as tabs provided by TabLayout) to react to scroll events in a sibling view marked with a ScrollingViewBehavior.

Before looking at this, let’s first create something we can scroll through. We’ll use a RecyclerView to create a list of items we can scroll through.

Note: I spent about an hour trying to make the CoordinatorLayout work with a ListView, but the scrolling behaviour was just not working. A simple search led me to this post, this post and this post. If you check those links, it seems that the CoordinatorLayout doesn’t work well out-of-the-box with ListViews (and apparently GridViews and ScrollViews). The last two links show a workaround to making it work with a ListView, but it doesn’t seem to be backwards compatible. I wanted to let you know incase you get stuck on the same issue. Support for ListViews will probably be added in a future update.

I’ll go through the creation of the RecyclerView without much explanation as this is out of the scope of this tutorial and you can easily find help online regarding RecyclerViews.

Place the following in your build.gradle (Module: app) file and sync the gradle files..

compile 'com.android.support:recyclerview-v7:22.2.1'

Create a file in res/layout named fragment_list_view.xml. Modify it as shown.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Create another layout file in res/layout and name it list_row.xml. Replace its contents with the content below.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:padding="16dp"
          android:layout_width="match_parent"
          android:layout_height="56dp">

    <TextView
        android:id="@+id/list_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

Create a java file called DesignDemoRecyclerAdapter.java and paste the following.

package com.echessa.designdemo; // Rename as Appropriate

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

/**
 * Created by echessa on 7/24/15.
 */
public class DesignDemoRecyclerAdapter extends RecyclerView.Adapter<DesignDemoRecyclerAdapter.ViewHolder> {

    private List<String> mItems;

    DesignDemoRecyclerAdapter(List<String> items) {
        mItems = items;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_row, viewGroup, false);

        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        String item = mItems.get(i);
        viewHolder.mTextView.setText(item);
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        private final TextView mTextView;

        ViewHolder(View v) {
            super(v);
            mTextView = (TextView)v.findViewById(R.id.list_item);
        }
    }

}

Modify DesignDemoFragment.onCreateView() in MainActivity.java as shown:

@Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Bundle args = getArguments();
        int tabPosition = args.getInt(TAB_POSITION);

        ArrayList<String> items = new ArrayList<String>();
        for (int i = 0; i < 50; i++) {
            items.add("Tab #" + tabPosition + " item #" + i);
        }

        View v =  inflater.inflate(R.layout.fragment_list_view, container, false);
        RecyclerView recyclerView = (RecyclerView)v.findViewById(R.id.recyclerview);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        recyclerView.setAdapter(new DesignDemoRecyclerAdapter(items));

        return v;
    }

This inflates the previously created layout file. Now the fragment’s view will contain a list of items. Run the app to confirm this.

RecyclerView

With that completed, we can look at the scrolling behaviour provided by the CoordinatorLayout.

Modify activity_main.xml as shown.

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    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:fitsSystemWindows="true">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/coordinator"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.AppCompat.Dark">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways"/>

            <android.support.design.widget.TabLayout
                android:id="@+id/tablayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="?attr/colorPrimary"
                app:tabGravity="fill"/>

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

        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_gravity="bottom|right"
            android:layout_marginBottom="@dimen/activity_vertical_margin"
            android:layout_marginRight="@dimen/activity_horizontal_margin"
            android:src="@drawable/ic_done"/>

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

    <android.support.design.widget.NavigationView
        android:id="@+id/navigation_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:headerLayout="@layout/drawer_header"
        app:menu="@menu/drawer"/>
</android.support.v4.widget.DrawerLayout>

In the above, we nest the Toolbar and TabLayout in an AppBarLayout. The AppBarLayout allows the Toolbar and other views (such as tabs provided by TabLayout) to react to scroll events in a sibling view marked with a ScrollingViewBehavior. When the user scrolls through the RecyclerView, the AppBarLayout responds by using its children’s scroll flags to control how they enter (scroll on screen) and exit (scroll off screen).

Flags include:

  • scroll: This flag should be set for all views that want to scroll off the screen. For views that do not use this flag, they’ll remain pinned to the top of the screen
  • enterAlways: This flag ensures that any downward scroll will cause this view to become visible, enabling the ‘quick return’ pattern
  • enterAlwaysCollapsed: When your view has declared a minHeight and you use this flag, your View will only enter at its minimum height (i.e., ‘collapsed’), only re-expanding to its full height when the scrolling view has reached it’s top.
  • exitUntilCollapsed: This flag causes the view to scroll off until it is ‘collapsed’ (its minHeight) before exiting

Note that all views using the scroll flag must be declared before views that do not use the flag. This ensures that all views exit from the top, leaving the fixed elements behind.

In our app we use the scroll and enterAlways flags on the Toolbar which will result in the Toolbar scrolling off screen when scrolling upwards and come back on screen on the downward scroll. Notice the use of app:layout_behavior="@string/appbar_scrolling_view_behavior" on the ViewPager. The support library contains a special string resource @string/appbar_scrolling_view_behavior that maps to AppBarLayout.ScrollingViewBehavior, which is used to notify the AppBarLayout when scroll events occur on a particular view. The behavior must be established on the view that triggers the event.

Run the app to see the effect of scrolling on the AppBar.

Toolbar and TabLayout scrolling

CollapsingToolbarLayout

The CollapsingToolbarLayour provides another type of scrolling behaviour to the AppBar. To see it in action, we’ll first create another activity that has a Toolbar, but no TabLayout.

Create a Blank Activity (File -> New -> Activity -> Blank Activity) and name it SecondActivity.

Modify res/layout/activity_second.xml as shown.

<android.support.design.widget.CoordinatorLayout
    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.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/image"
                app:layout_collapseMode="parallax"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"/>

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

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

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:paddingTop="24dp">

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="16dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="16dp">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Lorem ipsum"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/second_activity_text"/>

                </LinearLayout>

            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="16dp"
                android:layout_marginLeft="16dp"
                android:layout_marginRight="16dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="16dp">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Sed quam eros"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/second_activity_text"/>

                </LinearLayout>

            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="16dp"
                android:layout_marginLeft="16dp"
                android:layout_marginRight="16dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="16dp">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Sed a euismod dui"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/second_activity_text"/>

                </LinearLayout>

            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="16dp"
                android:layout_marginLeft="16dp"
                android:layout_marginRight="16dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="16dp">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Fusce nec lacinia mi"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/second_activity_text"/>

                </LinearLayout>

            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="16dp"
                android:layout_marginLeft="16dp"
                android:layout_marginRight="16dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="16dp">

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Praesent hendrerit"/>

                    <TextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="@string/second_activity_text"/>

                </LinearLayout>

            </android.support.v7.widget.CardView>

        </LinearLayout>

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

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

In the above, we use the CollapsingToolbarLayout with the scroll and exitUntilCollapsed flags which will make its subviews scroll off screen. For the Toolbar however, we set app:layout_collapseMode="pin" which will ensure that the Toolbar itself remains pinned to the top of the screen while the view collapses. Another effect we’ll get is that the title in the CollapsingToolbarLayout will appear larger when the layout is fully visible, then transition to its default size as it is collapsed. We’ll set this title in code. The rest of the layout contains a NestedScrollView with several Cards as its children.

In SecondActivity.java modify onCreate(Bundle) as shown.

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

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
    collapsingToolbar.setTitle("Second Activity");
}

Here we set up the Up caret on the Toolbar and then set a title for the CollapsingToolbarLayout.

For the Up caret to work, place the following in the manifest file as a child of the SecondActivity’s activity tag.

<meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.echessa.designdemo.MainActivity" />

In DesignDemoRecyclerAdapter.java modify `onBindViewHolder() as shown.

@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
    String item = mItems.get(i);
    viewHolder.mTextView.setText(item);

    viewHolder.mTextView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Context context = view.getContext();
            context.startActivity(new Intent(context, SecondActivity.class));
        }
    });
}

In the above, we set an onClick Listener to the TextView on each row of the RecyclerView. This is not the best way of setting a listener on the RecyclerView items, because if you run the app, the listener’s touch target will only cover the area with the row’s text and not the whole row. I do this here because all I want is a way to start the new activity and so I opted on a way to write the least amount of code.

Run the app and you will see the following scrolling behaviour.

CollapsingToolbarLayout

Floating Labels for EditText

The last component of the Design Support Library that we’ll look at is the improved EditText. Normally when typing is started on an EditText, the placeholder hint that was on the field is hidden. Now you can wrap an EditText in a TextInputLayout causing the hint text to become a floating label above the EditText, ensuring that users never lose context in what they are entering.

To see this, place the following inside one of the CardViews in activity_second.xml. You can place it after the second TextView of the Card.

<android.support.design.widget.TextInputLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <EditText
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:inputType="textEmailAddress"
                        android:hint="Email" />

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

Run the app and when you start to type into the EditText, the hint text will float above the field.

Floating label for EditText

In addition to showing hints, you can display an error message below the EditText by calling setError().

Conclusion

That brings us to the end of this tutorial. The completed project for the tutorial can be downloaded here. For more on the Android Design Support Library, read the Android developers blog.

  • Benjamin McMahan

    Thank you so much for posting such a complete and thorough tutorial!

    • http://www.echessa.com/ Joyce Echessa

      Thanks! I’m glad it helped.

      • Aman Verma

        I want to use tab text and icon both in tablayout.and I want to the the color whn selected/unselected aswell..I m almost done with it…but the problem is when I the project the first tab is unselected at first I want it to be selected..

  • Rafelcf

    Echessa Hello , I really like this tutorial ye got rename tabs :

    @Override
    public CharSequence getPageTitle(int position) {

    switch (position) {

    case 0: //search//todo
    return “Prueva “;

    case 1: //search//todo
    return “Esta es”;

    }
    return null;
    }

    I know what it’s like now as I do each tab to show me a Activity or Fragment
    Thanks for your answer.

    • http://careersreport.com leah Budde

      I will guide you to this excellent internet job+ opportunity… 3-5 hours of work daily… Payment at the end of every week… Bonus opportunities…Payscale of six to nine thousand dollars /a month… Only few h of free time, any kind of computer, most elementary understanding of Internet and dependable connection needed…Get more info on my disqus*page

  • http://careersreport.com leah Budde

    I will guide you to this excellent internet job opportunity… 3-5 hours of work daily… Payment at the end of every week… Bonus opportunities…Payscale$ of six to nine thousand dollars /a month… Only few h of free time, any kind of computer, most elementary understanding of Internet and dependable connection needed…Get more info on my disqus*page

  • Asaduzzaman

    Thanks for this wonderful tutorial. Actually I need to hide the status bar smoothly (supported also for pre-lolypop as like whatsApp). Can you please help with any link or something?

  • Heather S

    Thank you Joyce! I was a bit lost with material design and this answered all my questions.

  • Ganesh Giri

    Thanx #Joyce Echessa

  • Gufran Khurshid

    Thanks for such a wonderful tutorial.

  • kevin kubai

    Hi Joyce,
    Am running into the error below.

    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.echessa.designdemo/com.echessa.designdemo.MainActivity}: android.view.InflateException: Binary XML file line #53: Binary XML file line #53: Error inflating class android.support.design.widget.NavigationView

    • http://www.echessa.com/ Joyce Echessa

      Hi,

      Are you running the completed project from the download link at the end of the article? I just ran it and it doesn’t give any error. You can check with that and see what might be causing the error in your code.

      InflateException usually indicates a problem in the layout file, it could be an omission, spelling mistake, or you could have imported a library from the main Android library when you should have imported from the support library.

      Start by looking at your NavigationView (as the error message suggests that this might be the problem) and the corresponding menu/drawer.xml and layout/drawer_header.xml files that it uses.

      I hope that helps.

      • gerard horgan

        Hi Joyce, Kevin, had a similar error to above. Downloaded the complete project from the download link and got the error java.lang.RuntimeException: Unable to start activity ComponentInfo{com.echessa.designdemo/com.echessa.designdemo.SecondActivity}: android.view.InflateException: Binary XML file line #19: Binary XML file line #19: Error inflating class .

        The inflate exception is not actually the problem, but really comes from another deeper issue in your layout that is then wrapped in an InflateException. The issue is an out of memory exception when trying to inflate an imageview loading a drawable resource. If one of the resources has a high pixel resolution it would take a lot of memory causing then an inflate exception. I replaced the given image with the android icon and everything worked as expected. I probably didn’t allocate enough RAM on my virtual device. Hope this helps Kevin and thanks for the tutorial Joyce!

      • gerard horgan

        Hi Joyce, Kevin, had a similar error to above. Downloaded the complete project from the download link and got the error java.lang.RuntimeException: Unable to start activity ComponentInfo{com.echessa.designdemo/com.echessa.designdemo.SecondActivity}: android.view.InflateException: Binary XML file line #19: Binary XML file line #19: Error inflating class .

        The inflate exception is not actually the problem, but really comes from another deeper issue in your layout that is then wrapped in an InflateException. The issue is an out of memory exception when trying to inflate an imageview loading a drawable resource. If one of the resources has a high pixel resolution it would take a lot of memory causing then an inflate exception. I replaced the given image with the android icon and everything worked as expected. I probably didn’t allocate enough RAM on my virtual device. Hope this helps Kevin and thanks for the tutorial Joyce!

        • http://www.echessa.com/ Joyce Echessa

          Thanks for that. It’s good to know what’s causing the problem. Yeah, I really did use a high-res image for the demo. I should have reduced it’s size.

          Anyway, thanks for your comment.

          • gerard horgan

            No problem Joyce. Thanks for the demo.

  • Raj

    HI – thank you for the wonderful tutorial. This is one of the very few and best android design support tutorial anywhere on the internet.
    This method loads the data in all three (any number of tabs) at once. How do I load the data on the tab only if tapped or scrolled to the tab so that we will load only the data required?
    Thank you very much for your time and tutorial.

  • Balaram Nayak

    Thanks A lot it really Helped

    • http://www.echessa.com/ Joyce Echessa

      Thanks. I’m glad it was helpful.

  • http://www.aykutasil.com Aykut Asil

    it’s realy amazing tutorial.thanks.

    • http://www.echessa.com/ Joyce Echessa

      Thanks. I’m glad it helped.

  • Neon Warge

    Thank you for the part explaining this to us: app:layout_behavior=”@string/appbar_scrolling_view_behavior”

    God I swear I could not find any documentation about this, I am struggling to look for @string/appbar_scrolling_view_behavior in my string resource file and couldn’t find any.

  • Chris

    Thanks for the great tutorial. I am having a problem with my FrameLayout now which used to host other fragments whan doing fragment transaction i.e. .replace(R.id.container, fragment). It is not working properly. May I kindly see sample code for this. Thanks.

  • ye yint

    amazing tutorial.thanks a lot ..

  • Omar F. Rodriguez Morales

    is it possible to use the CollapsingToolbar effect with a NavigationView

  • Shreya Sinha

    Hello, I tried collapsing toolbar layout, but the header can’t be scrolled. Please help!

    • Meshileya Israel

      Try to check your xml file (layout), if your ScrollView is enabled

  • wildhan satriady

    thank you for your tutorial :)

  • Omid Sadeghi

    It was a great learning thank you and good luck

  • Athanasiadis Giannis

    Very very good tutorial. Thanks a lot . Really helpful. I have one question though, what if I want to pass an object from the main activity to one of the tabs? How could I achieve this?

  • Pannam

    How can i add or delete tabs ?

  • sharvan jain

    Thank you for this wonderful tutorial> This is very helpful for understanding the basics of android design library. I am working on an android application. So i need to implement gridview in material designs. Is there any way to create a material gridview using android design library. Please help me.

  • Ravi Sinha

    In place of ScrollView use NestedScrollView (support.v4.widget) to achieve desired animation. Fab Post!

  • Dmitry Sokolov

    Thank you very much for this tutorial! It’s very useful!
    Unfortunately, i have faced a problem when CollapsingToolbarLayout is “locking” touch/click events on toolbar’s menu items. When toolbar is outside CollapsingToolbarLayout everything works fine, but when it inside – all buttons on the toolbar isn’t working.
    Do you have any ideas why it happens?
    Thank you!

  • http://kishorer.in kishore rajendra

    Hi, I have a similar app with tab bar layout, view pager (fragments go inside this) and a recycler view for drawer items. But when i scroll in my fragments, the toolbar wont hide. I have 4 tabs, i can swipe up/down on the App Bar to hide/show it. But scrolling inside the fragment does not hide the toolbar. Please help!

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Mobile, once a week, for free.