Smartphones

Give your Android app users UI customization options

Not much of a UI guy? Then let your Android app users specify a color in your product. William J. Francis explains how in this nerdy proof of concept.

Those of you who read my TechRepublic Android App Builder columns regularly know I'm not much of a UI guy. It's not that I don't appreciate a good user interface or that I'm too lazy to build one, I'm just not very good at it.

Lucky for me, I've noticed a trend lately of customizable UIs, particularly for phone apps. The idea is that the product runs out of the box in a plain old vanilla version, and then the user can specify the colors to his or her liking. In most cases, I've seen the effect achieved using sets of preloaded graphics or skins, but it occurred to me something similar might be done with much less overhead using the handy collection of dynamic drawing tools provided as part of the Android SDK.

The following is not so much a tutorial as it is a proof of concept. I created a layout where the user can specify a button's background color. Then using something I learned in a required freshmen year art theory course (at long last), I break the selected color first into RGB (red, green, blue) components and then into HSV (hue, saturation, value). From there it's a matter of subtraction to create a darker "shade" of the original color that I can use in a state list to indicate the button is "pressed," for example.

I know... this all sounds amply nerdy, but didn't I just read that nerdy is in? Or maybe it is geeks.... Anyway, if you'd like to learn more, you can follow along with the step-by-step instructions below or download and import the entire project.

1. Create a new Android project in Eclipse. Target Android 1.6 or higher. Be sure to rename the startup activity to Main.java.

2. In the /res/layout folder, edit the main.xml file to include two buttons: two text views and an edit text.  I nested a couple of the widgets inside a second linear layout for aesthetics.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_margin="20dip"
        android:text="user defined button color demo" />
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button color:"/>
       <EditText
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:minWidth="80dip"
           android:id="@+id/button_color_field"/>
       <Button
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="OK"
           android:id="@+id/okay_button"/>
</LinearLayout>
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="My Big Button"
    android:padding="30dip"
    android:layout_margin="20dip"
    android:textSize="20sp"
    android:id="@+id/big_button"/>
</LinearLayout>

3. Since we will be drawing all the shapes and colors on the fly, this week we don't need to touch any of the /drawable resource folders; instead, we can go directly to the Main.java file in our /src directory where we will begin by overriding the on create method and wiring up the "ok" button.

Main.java
package com.authorwjf.auto_color_select;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class Main extends Activity implements OnClickListener {
        @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

findViewById(R.id.okay_button).setOnClickListener(this);
        }
}

4. It's time to implement the on click handler. The handler has three responsibilities: get the hex value the user entered into the edit text field, convert that value to a color, and call our method to build the drawable state list and apply the result as the target button's background.

@Override
public void onClick(View v) {
int buttonDefaultColor = Color.parseColor(((EditText)

findViewById(R.id.button_color_field)).getText().toString());

Button b = (Button) findViewById(R.id.big_button);

b.setBackgroundDrawable(createDrawableColorStateList(buttonDefaultColor, b.getWidth(),

b.getHeight()));
}

5. The remaining method for our little experiment is responsible for doing the color manipulation and building the drawable state list. The first few lines of the method use some bit twiddling to extract the red, green, and blue values. Android converts the color to an HSV, at which time I decrement the V by half. The remainder of the code builds a state list that covers only the pressed and default state. If you've never built a state list dynamically before, it can be a little tricky, and Google's official documentation on the matter isn't as helpful as it could be. Just remember that the states get evaluated in the same order you add them, so you should always add int[0] as your last state, with the default image to display when none of your former conditions are met.

private StateListDrawable createDrawableColorStateList(int color, int width, int height) {
int red = (color >> 16) & 0xFF;
int green = (color >> 8) & 0xFF;
int blue = (color >> 0) & 0xFF;
float[] hsv = new float[3];

Color.RGBToHSV(red, green, blue, hsv);

hsv[2] = (float) (hsv[2] - (hsv[2] * .50)); int pressedColor = Color.HSVToColor(hsv); float[] rounded = new float[] { 12, 12, 12, 12, 12, 12, 12, 12 }; ShapeDrawable defaultDrawable = new ShapeDrawable(new RoundRectShape(rounded, null, null));

defaultDrawable.getPaint().setColor(color);

ShapeDrawable pressedDrawable = new ShapeDrawable(new RoundRectShape(rounded, null, null));

pressedDrawable.getPaint().setColor(pressedColor);

StateListDrawable drawableStateList = new StateListDrawable(); drawableStateList.addState(new int[] { android.R.attr.state_pressed }, pressedDrawable); drawableStateList.addState(new int[0], defaultDrawable); return drawableStateList;
}
Now we are ready load the code into the emulator and see how our hypothesis holds up (Figure A). Figure A

I am pleased with the results. The code is pretty rough, but I feel like there is potential to this approach--perhaps adding shading to the buttons and supporting the disabled state would be a good next step. I can imagine an entire subclass of UI kit widgets that are completely configurable by an end user via color selections in a standard Android preferences menu.

If you're an Android developer, I'd be curious to know what if any use case scenarios you could envision for this type of canvas manipulation, as well as any improvements you might suggest for my algorithm.

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

1 comments
MaryWeilage
MaryWeilage

In step 5, please replace the smiley face that displays in the code with an 8: int green = (color >> 8) & 0xFF; Our apologies if the code display caused you any confusion about the instructions.