Retrofitting Material Design to Pre-Lollipop Android

lollipopAndroid 5.0 Lollipop comes with a complete makeover of the Android user interface. Called Material Design, the new UI replaces the old Holo Light and Dark themes used since Android 4.0. Continuing a trend that started with Microsoft and the flat tiles of their Modern UI, later adapted by Apple with iOS 7 and 8, Material Design flattens out the elements of the UI, though not completely. Instead the UI replicates the illusion of pieces of paper sliding over surfaces within the device, and does so by the subtle use of tinting and shadows. Material Design is more than this: it includes brightly colored elements, logical transitions and other features.  Google is attempting to develop precise guidelines for UI design that can extend from small phones, to desktop computer screens.  The philosophy of Material Design is laid out here. The full panoply of Material Design is only available on the just released Nexus 6 and Nexus 9 devices, though it should come to other devices eventually (soon?). Nevertheless, Google has provided a glimpse of Material Design over the last several months by releasing revamped versions of their standard apps like Gmail and Newsstand with Material Design themes.  Now it is finally possible for developers to create versions of their own apps using some but not all features of Material Design. Google now supplies the appcompat-v7:21 support library to accomplish this.

I decided to upgrade my app EP Coding to material design. The app is based on the master-detail template included with the Android SDK, and uses an ActionBar (the menu bar with icons and text at the top of the screen). If you are designing apps only for Android 5.0 and higher, or if you don’t care about using Material Design in older versions of Android, you can continue to use the ActionBar by just having your theme in the res/values-v21 directory derive from one of the new Material Design themes, such as Theme.Material.Light.DarkActionBar. However if you want to use a Material Design theme for pre-Lollipop Android, then you can’t use the built-in ActionBar. Instead you must use the new Toolbar widget that is included in the AppCompat library. The Google docs proclaim how much more flexible the Toolbar is compared with the old ActionBar. Unfortunately with flexibility comes extra work.

It is necessary to include the Toolbar in each layout in which you want to use it as an ActionBar. The master-detail template used in my app makes heavy use of fragments, so it was necessary to make some major changes to my layouts in order to use the Toolbar.

The first step is derive your app theme from a non-ActionBar Material Design theme. You don’t want your Toolbar colliding with the old ActionBar. Thus in themes.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <!-- Application theme. -->
 <style name="AppTheme" parent="AppTheme.Base">
 </style>

 <style name="AppTheme.Base"
parent="Theme.AppCompat.Light.NoActionBar">
   <item name="colorPrimary">@color/primary</item>
   <item name="colorPrimary">@color/primary</item>
   <item name="colorPrimaryDark">@color/primary_dark</item>
   <item name="colorAccent">@android:color/black</item>
   <item name="android:windowNoTitle">true</item>
   <item name="windowActionBar">false</item>
 </style>
</resources>

Note that here we also define some colors for the ActionBar Toolbar. colorPrimary is the color of the Toolbar, and colorPrimaryDark is the color of the status bar above the Toolbar (the bar with notifications, the time, number of bars, etc.). It is actually only colored on Android 5.0, and remains black on pre-5.0 Android. colorAccent is used to highlight text and checked checkboxes and radio buttons (but see further below).

I defined the Toolbar in a separate file, and then included this in the layout files when needed. The Toolbar:

<?xml version="1.0" encoding="utf-8"?>
 <android.support.v7.widget.Toolbar
 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/toolbar"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:background="?attr/colorPrimary"
 android:elevation="4dp"
 app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
 app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
 tools:ignore="UnusedAttribute" />

I wanted the equivalent of Holo.Light.DarkActionBar, so the toolbar uses the ThemeOverlay.AppCompat.Dark.ActionBar theme. The app:popupTheme style used here ensures the text and icons on the Toolbar are white instead of black. It seems redundant, but the android:background has to be defined as the colorPrimary here, despite it being defined in the theme. Finally I set an elevation for the Toolbar of 4 dp, which is what the Material Design guidelines suggest for ActionBars/Toolbars. This casts a small shadow below the Toolbar, giving it a 3D look. Unfortunately this attribute is only used in Android 5.0, and is ignored otherwise.

In the master-detail template in my app there are two main components: a list view of procedures which is used to select an individual procedure. The detail view shows the details of the billing codes for the procedure. On a phone the list appears full screen and is replaced by the detail screen when a procedure is selected. On a tablet, the list appears along the left border, and the details appear on the right side of the screen. The procedure list and procedure details are therefore not implemented as Activities, rather they are Fragments that can either be included in a layout alone as an Activity (for phones) or combined into two views in the same Activity (for tablets). The original procedure list fragment looked like this:

<?xml version="1.0" encoding="utf-8"?>
 <fragment
    android:id="@+id/procedure_list"
    android:name="org.epstudios.epcoding.ProcedureListFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginRight="16dip"
    android:layout_marginLeft="16dip"
    tools:context=".ProcedureListActivity"
    tools:layout="@android:layout/list_content">
 </fragment>

In order to use the Toolbar, it is necessary to turn this into a LinearLayout containing the Toolbar and the Fragment. The Activity displaying the list can then use the entire Activity (for a phone’s screen) or just the Fragment (for a tablet’s screen). Note that the Toolbar is included using the <include> tag.

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_height="match_parent"
 android:layout_width="match_parent"
 android:orientation="vertical" >
 <include layout="@layout/toolbar" />
 <fragment
   android:id="@+id/procedure_list"
   android:name="org.epstudios.epcoding.ProcedureListFragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_marginRight="16dip"
   android:layout_marginLeft="16dip"
   tools:context=".ProcedureListActivity"
  tools:layout="@android:layout/list_content">
 </fragment>
 </LinearLayout>

In order to use the toolbar, the Activity class has to extend ActionBarActivity (which is a subclass of FragmentActivity). You set the Toolbar as the ActionBar by code like the following:

protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_procedure_list);
 Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
 setSupportActionBar(toolbar);
}

After this, the Toolbar will behave like the ActionBar.

There are some other nuances, however. I had trouble get my SearchView to work. This was corrected by referencing the support Searchview in my menu.xml file. Note also that you must define an app namespace, and use this namespace, not the android namespace for defining the actionViewClass and showAsAction.

<?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
<item
 android:id="@+id/search"
 app:actionViewClass="android.support.v7.widget.SearchView"
 android:icon="@drawable/ic_search_white_24dp"
 android:orderInCategory="10"
 app:showAsAction="ifRoom|collapseActionView"
 android:title="@string/search_title">
 </item>
 <item
 android:id="@+id/wizard"
 android:icon="@drawable/ic_directions_white_24dp"
 android:orderInCategory="90"
 app:showAsAction="ifRoom"
 android:title="@string/wizard_title">
 </item>
 ...
 </menu>

I also had trouble implementing my PreferencesActivity. Rather than go into detail, this post on Stackoverflow shows how it is done. You can also checkout the source code of my app at GitHub.

The old icons look clunky with Material Design. Android has released a complete set of standard icons, including ActionBar icons, that are very useful.

Finally not everything is perfect with backporting Material Design to “old” Android. I had one particular vexing problem with tinting of checkboxes. The AppCompat support library is supposed to use the defined colorAccent to color checkboxes when checked. However most of my checkboxes were being colored black instead of the light blue of my colorAccent. Worse, the checkboxes in my Preferences Activity were being colored blue as they were supposed to be. Worse still, when I went from the Preferences Activity back to one of my my detail screens, some of the checkboxes would be tinted blue and some remained black. For example:

device-2014-11-11-224026

It turns out the AppCompat tints the checkboxes after they are drawn, but only checkboxes that are in a layout. The checkboxes on my screen were all created via programming, so they were not tinted. But, when I went to the Preferences Activity, Android must behind the scenes save the layout of the previous screen. When that screen returns, AppCompat is able to tint the checkboxes, though the newly checked ones after that revert to black. (At least that’s my theory as to what’s happening.) Ugh! Hopefully an update will fix this issue. For now, I set my colorAccent to black to avoid having multicolored checkboxes in pre-5.0 Android, and set colorAccent to blue for Android 5.0.

Wrap-up

So the end result is something approaching Material Design on current Android devices (I’ve yet to see Lollipop up and running on a real device). It is not easy converting from the ActionBar motif to Toolbars. Android documentation could be better — a lot better! Many of the Android online documents still retain instructions pertinent to the old Holo themes which is confusing. There are a lot of subtleties and poorly documented techniques that make it difficult to realize Google’s goal of “Material Design Everywhere.” But at least I’ve got the transition done on one of my apps. Now on to the next!

About mannd

I am a retired cardiac electrophysiologist who has worked both in private practice in Louisville, Kentucky and as a Professor of Medicine at the University of Colorado in Denver. I am interested not only in medicine, but also in computer programming, music, science fiction, fantasy, 30s pulp literature, and a whole lot more.

7 thoughts on “Retrofitting Material Design to Pre-Lollipop Android

  1. Any View created programmatically without using layout inflator is supposed to not get tinted, Just pass a LayoutInflator instance to the view constructor and everything works fine 🙂

    1. Thanks will give it a try…

      But don’t I need to use an XML layout file to use LayoutInflater? I really just want to do the layout completely in code.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.