From Punched Cards to Prompts
AndroidIntroduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
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;
}
});
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);
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.
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 Behavior
s 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.
This blog post wouldn’t be complete without putting these new toys into action. Here’s a demo that shows:
TextInputLayout
managing the hint animationFloatingActionButton
anchored to the bottom of the CollapsingToolbar
Snackbar
with the associated actionIntroduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Jetpack Compose is a declarative framework for building native Android UI recommended by Google. To simplify and accelerate UI development, the framework turns the...
Big Nerd Ranch is chock-full of incredibly talented people. Today, we’re starting a series, Tell Our BNR Story, where folks within our industry share...