Android

Tinting your next Android UI

App development expert William J. Francis walks you through the steps of applying a color filter to a standard Android button widget.

Android is very flexible when it comes to creating a user interface (UI). By utilizing the Android Drawable class, it is possible to set a widget background to just about any image you want.

This is a great approach when you own the user experience, but what happens when you are not creating an app directly for the end user? For instance, suppose you are creating an app for retailers who want to customize the color scheme to their corporate colors. Or, you are creating a widget library for use by other developers. How do you allow someone to adjust the "theme" of your UI without packaging along an infinite number of graphic resources? The answer is in the Drawable class.

Each drawable has a color filter that can be applied in a number of interesting ways to achieve a wide array of variation in a UI at runtime. When used correctly, a color filter does not replace the original graphic resource but rather applies the change in hue uniformly; this preserves shadowing and gradient, while preventing the user from getting too crazy and inadvertently creating an eyesore. 

This example demonstrates applying a color filter to a standard Android button widget. You can follow along with the step-by-step tutorial, or download the entire project and import it directly into Eclipse.

1. Create a new Android project in Eclipse. Target Android 2.2 or higher.

2. We will need two base drawables for our buttons: one in the normal state and one in the pressed state. When you are working with color filters, you will achieve the most dramatic result if your base image is a combination of grays. In the /res/drawable folder, I define two rounded rectangles to use as our button backgrounds. The first rectangle uses a nice gradient to produce a 3D effect. The second rectangle is flat and has a sharp outline.

button_normal.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
	<gradient
		android:endColor="#808080"
	    android:centerColor="#ffffff"
	    android:startColor="#c0c0c0"
	    android:angle="270" />
	<corners
	   	android:radius="10dp" />
	<padding
	    android:left="10dp"            
	    android:top="10dp"
	    android:right="10dp"
	    android:bottom="10dp" />
</shape>
button_pressed.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
	<gradient
	    android:endColor="#c0c0c0"
	    android:centerColor="#c0c0c0"
	    android:startColor="#c0c0c0"
	    android:angle="270" />
	<stroke android:width="10px" 
	    android:color="#808080"/>
	<corners
	   	android:radius="10dp" />
	<padding
	    android:left="10dp"            
	    android:top="10dp"
	    android:right="10dp"
	    android:bottom="10dp" />
</shape>

3. In the same folder, let's create our state list drawable.

button_states.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item 
        android:state_pressed="true" 
        android:drawable="@drawable/button_pressed" />
    <item 
        android:state_pressed="false" 
        android:drawable="@drawable/button_normal" />
</selector>

4. In our /res/layout folder, we are ready to define our widget layout. In this case, we have a linear layout that stacks five buttons vertically. Each button has its background property set to our recently created state list.

activity_main.xml
<LinearLayout 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"
    android:gravity="center"
    android:layout_margin="8dp"
    android:orientation="vertical"
    android:background="#000000" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#ffffff"
        android:text="Dynamic Tint Effect Demo"
        android:layout_margin="8dp"
        android:textSize="20sp" />
    
    <Button 
       android:layout_width="match_parent"
       android:layout_height="wrap_content" 
       android:layout_margin="8dp"
       android:id="@+id/button_1"
       android:background="@drawable/button_states"
       android:text="text"/>
    
    <Button 
       android:layout_width="match_parent"
       android:layout_height="wrap_content" 
       android:layout_margin="8dp"
       android:id="@+id/button_2"
       android:background="@drawable/button_states"
       android:text="text"/>
    
    <Button 
       android:layout_width="match_parent"
       android:layout_height="wrap_content" 
       android:layout_margin="8dp"
       android:id="@+id/button_3"
       android:background="@drawable/button_states"
       android:text="text"/>
    
    <Button 
       android:layout_width="match_parent"
       android:layout_height="wrap_content" 
       android:layout_margin="8dp"
       android:id="@+id/button_4"
       android:background="@drawable/button_states"
       android:text="text"/>
    
    <Button 
       android:layout_width="match_parent"
       android:layout_height="wrap_content" 
       android:layout_margin="8dp"
       android:id="@+id/button_5"
       android:background="@drawable/button_states"
       android:text="text"/>

</LinearLayout>

5. We are ready to code the /src/MainActivity.java class. We will start by extending the base Activity, implementing an OnClickListener, and creating our own utility that allows us to get resources by their text name rather than the more commonly used ID.

MainActivity.java
package com.authorwjf.tinted;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.PorterDuff.Mode;

public class MainActivity extends Activity implements OnClickListener {

	private String[] colors = {"white", "magenta", "yellow", "cyan", "green"};
	private int index = 0;
	
	
private int getIdFromName(String name) {
		return getResources().getIdentifier(name,"id",getPackageName());
	}

}

6. We will override the OnCreate function. This is where we wire up our buttons and first make use of our getIdFromName function.

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);		
for (int i=0; i<5; i++) {
		String name = "button_"+Integer.toString(i+1);
		findViewById(getIdFromName(name)).setOnClickListener(this);
	}
	onClick(null);
}

7. We are ready to implement the onClick callback. This is where we apply a color tint -- in this case, it is based on the array we defined previously of "known" Android color names.

@Override
public void onClick(View v) {		
for (int i=0; i<5; i++) {
		String name = "button_"+Integer.toString(i+1);
		((Button)findViewById(getIdFromName(name))).setText(colors[index]);
		int tint = Color.parseColor(colors[index]);
((Button)findViewById(getIdFromName(name))).getBackground().setColorFilter(tint, Mode.MULTIPLY);
	}
	index++;		
if (index>4) index=0;	
}

The app is ready to compile and run. You can check it out on an emulator or an actual Android device. Each time you press one of the buttons, they will cycle through the next color in the list.

Figure A: Base UI

white_buttons_AndroidUI_073013.png

Figure B: Yellow tinted UI

yellow_buttons_AndroidUI_073013.png

I hope the power of Android's color filter is sinking in now. And remember, you can apply this technique to any view in the UI that allows you to set a background drawable. You can even create a vanilla set of PNG-based icons and apply a tint to them as well. How slick is that?

About

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

0 comments