Upcoming and OnDemand Webinars View full list

Becoming Material with Android’s Design Support Library

Bolot Kerimbaev

Last year, Google announced Material Design, a set of guidelines for Android apps and apps on other platforms.
One example of such new goodies was the Floating Action Button, which requires a bit of setup. What’s more, some of its features work only on Lollipop and later.

Sure, it’s technically just a circle with a drop shadow, but how many developers have the extra time to implement every new design language specification?
A few third-party implementations of the floaing action button became available, but ensuring consistency and completeness was still elusive.

Fortunately, Google clearly recognizes the importance of a good user experience for the KitKats and JellyBeans of the world (and all the way back to Eclair MR1).
As time progressed, more and more elements of Material Design were added to the AppCompat support library.

However, the AppCompat library may not be the right place to add all of the design goodies. Enter the design support library, announced at this year’s I/O.
Not only does it bring the FABulous to earlier versions of Android, it also gives us new goodies, such as the Navigation View, snackbar, floating labels for EditText, and CoordinatorLayout.

The NavigationView makes it easy to create Material Design-style side drawer layouts.
It consists of the header and the menu.

<android.support.v4.widget.DrawerLayout
        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">

    <!-- your content layout -->

    <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>

The header is loaded from the layout specified by the app:headerLayout attribute. It would be configured to highlight the identity of the app by using the appropriate color scheme and/or large images.

The menu is used to define the “body” of the navigation drawer. It is loaded from the menu resource specified by the app:menu attribute. The top-level menu items will be displayed at the top portion of the navigation. The NavigationView also supports hierarchical menus; the sub-menus will be displayed below the main list and will feature subheaders.

<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <group android:checkableBehavior="single">
      <item
          android:id="@+id/navigation_item_1"
          android:checked="true"
          android:icon="@drawable/ic_android"
          android:title="@string/navigation_item_1"/>
      <item
          android:id="@+id/navigation_item_2"
          android:icon="@drawable/ic_android"
          android:title="@string/navigation_item_2"/>
  </group>

  <item
      android:id="@+id/navigation_subheader"
      android:title="@string/navigation_subheader">
      <menu>
          <item
              android:id="@+id/navigation_sub_item_1"
              android:icon="@drawable/ic_android"
              android:title="@string/navigation_sub_item_1"/>
          <item
              android:id="@+id/navigation_sub_item_2"
              android:icon="@drawable/ic_android"
              android:title="@string/navigation_sub_item_2"/>
      </menu>
  </item>
</menu>

Since this menu resource is not loaded in the Activity’s or Fragment’s onCreateOptionsMenu, selecting one of these items will not trigger onOptionsItemSelected. Instead, we need to attach the click listener to the NavigationView:

    navigationView.setNavigationItemSelectedListener(
            new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(MenuItem menuItem) {
            menuItem.setChecked(true);
            mDrawerLayout.closeDrawers();
            switch (menuItem.getItemId()) {
                case R.id.navigation_item_1:
                    // react
                    break;
            }
            return true;
        }
    });

TextInputLayout

The design library takes an interesting approach to customizing the EditText: it doesn’t change it directly. Instead, the TextInputLayout is used to wrap the EditText and provide the enhancements.

The first one, displaying a floating label when the user types something into the field, is done automagically. The TextInputLayout finds the EditText among its children and attaches itself as a TextWatcher, so it’s able to determine when the field has been modified and animates the movement of the hint from its regular place in the EditText to the floating label position above it.

The second enhancement, displaying the error message, requires a slight change in code. Instead of setting the error on the EditText, the error should be set on the TextInputLayout.
That’s because there is no automatic way for the TextInputLayout to be notified when the error is set on the EditText.

Here’s what the layout might look like:

    <android.support.design.widget.TextInputLayout
        android:id="@+id/username_text_input_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/username_edit_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/username_hint"/>
    </android.support.design.widget.TextInputLayout>

Note that both the EditText and TextInputLayout need layout IDs.
In the fragment, we would need to configure the TextInputLayout to enable displaying errors:

    TextInputLayout usernameTextInputLayout = (TextInputLayout) view.findViewById(R.id.username_text_input_layout);
    usernameTextInputLayout.setErrorEnabled(true);
    ...
    usernameTextInputLayout.setError(R.string.username_required);

Snackbar

Snackbars are like Toasts, but a bit more flexible.
A snackbar contains an action and can be dismissed.
Its API has been deliberately made similar to Toast’s:

Snackbar
  .make(view, R.string.snackbar_text, Snackbar.LENGTH_LONG)
  .setAction(R.string.snackbar_action, mOnClickListener)
  .show();

Instead of displaying in a predetermined location in the window, the snackbar will place itself inside the view that’s passed into the make() method. The static make() method will find a suitable parent to embed the Snackbar.

What constitutes a suitable parent? It’s the nearest ancestor that’s either a CoordinatorLayout (if available), or if that fails, the nearest FrameLayout. Since the decor content view is also a FrameLayout, that’s the furthest the Snackbar will perform the search.

Why the special treatment for the CoordinatorLayout? It offers some nice benefits, including moving floating action buttons out of the way when the snackbar appears and moving them back when the snackbar is dismissed.

CoordinatorLayout

The CoordinatorLayout is responsible for managing interactions between its child views. It does so by applying rules set by its children’s CoordinatorLayout.LayoutParams. One set of rules is defined by the subclasses of the abstract CoordinatorLayout.Behavior class. There are a few Behaviors that come bundled with the design support library, such as AppBarLayout.ScrollingViewBehavior and SwipeDismissBehavior.

For example, to make scrolling of a view (e.g., RecyclerView) affect the toolbar, you could add app:layout_behavior="@string/appbar_scrolling_view_behavior" to your view:

    <android.support.v7.widget.RecyclerView
        android:id="@+id/crime_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

layout_behavior expects the fully-qualified name of the class that implements the Behavior, in this case it happens to be android.support.design.widget.AppBarLayout$ScrollingViewBehavior.

Another example is keeping the floating action button anchored to the bottom of the toolbar. This can be achieved with the pair of attributes app:layout_anchor and app:layout_anchorGravity as in this example:

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|right|end"
        app:borderWidth="0dp"
        android:layout_margin="@dimen/fab_margin"
        android:src="@drawable/ic_photo_camera_white_24dp" />

Collapsing the toolbar can be fine-tuned via app:layout_collapseMode. It accepts three values: "off", "pin", and "parallax". For example, setting this attribute to "parallax" is useful for applying a nice parallax effect to images.

The CoordinatorLayout deserves a bit more attention, so I’ll return to it in a later post.

Demo

This blog post wouldn’t be complete without putting these new toys into action. Here’s a demo that shows:

  • the TextInputLayout managing the hint animation
  • FloatingActionButton anchored to the bottom of the CollapsingToolbar
  • Snackbar with the associated action

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project