Developer

Android Lollipop material design trick offers a more polished UX

Learn how to work around a caveat when using elevation from the Android 5.0 material design spec to create a shadow effect.

androidlollipop5dot0cnet.jpg
Image: Nicole Cozma/CNET

In Android 5.0 one of the key concepts from the material design spec is using elevation to promote UI elements in perceived importance. According to the documentation and Google-provided example, as long as the device is running Lollipop or better, all that is required is to set the elevation property, and the framework will handle the shadow effect.

However, in my testing with both Android 5.0 and 5.0.1, the documentation neglects to mention one very important caveat: If your drawable applies an alpha, the elevation property is ignored. To see this behavior, follow the walk-through below, or download and import the entire project directly into Android Studio.

1. Create a new Android project in Android Studio. Target SDK 21 or higher.

2. In the /res/drawable folder, create a new xml shape. (This xml drawable definition was lifted directly from the Google documentation on customizing view shadows.)

square.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#42000000" />
    <corners android:radius="5dp" />
</shape>

3. In the /res/layout folder, modify your activity_main.xml file to include: an image view, a couple of buttons, and a text view widget.

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical"
    android:layout_gravity="center"
    android:gravity="center">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginBottom="40dp"
        android:elevation="0dp"
        android:background="@drawable/square"
        android:id="@+id/img"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:orientation="horizontal">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="-"
            android:id="@+id/lower"/>
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0"
            android:id="@+id/label"
            android:padding="10dp"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="+"
            android:id="@+id/raise"/>


    </LinearLayout>

</LinearLayout>

4. In the /java/MainActivity.java file, wire up a couple of click listeners to raise and lower the elevation property.

MainActivty.java
package com.authorwjf.shadows;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;


public class MainActivity extends Activity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.raise).setOnClickListener(this);
        findViewById(R.id.lower).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        int elevation = Integer.parseInt(((TextView) findViewById(R.id.label)).getText().toString());
        if (v.getId()==R.id.raise) {
            elevation+=5;
        } else {
            elevation-=5;
        }
        if (elevation<0) {
            elevation=0;
        } else if (elevation>100) {
            elevation=100;
        }
        findViewById(R.id.img).setElevation(elevation);
        ((TextView) findViewById(R.id.label)).setText(Integer.toString(elevation));
    }
}

At this point, you'd expect that by raising and lowering the elevation, you would observe the framework rendering a shadow to visually denote the Z axis. You can see from the screenshot below (Figure A), this isn't the case. The image view still appears to have an elevation of zero, despite the exposed property saying otherwise.

Figure A

androidlollipopnoshadows010615.png
Screenshot by William J. Francis/TechRepublic

What I found after some googling and experimentation is that if the background assigned to your view contains an alpha, the built-in shadow effects don't work. I've taken the liberty of removing the alpha from the above XML drawable.

square.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#000000" />
    <corners android:radius="5dp" />
</shape>

Now the application behaves as expected (Figure B). It's a subtle difference, but I think that is a big part of what material design is about: subtle differences that make for a more polished user experience (UX).

Figure B

androidlollipopwithshadows010615.png
Screenshot by William J. Francis/TechRepublic

Also see

About William J. Francis

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

Editor's Picks

Free Newsletters, In your Inbox