Saturday, July 31, 2010

Colors in AlertDialog

I wanted to use colors in AlertDialog and I found this post. It work great although the solution there is not full (there are features of AlertDialog that will not work). One issue with that post - the code & XML there can not be easily copied.

Here's that code again, with some changes/improvements:
* Renamed the class name to ColorAlertDialog
* ColorAlertDialog.Builder does not inherits AlertDialog.Builder. This caused some confusion (you could call function but they didn't do anything).
* Added functionality to ColorAlertDialog.Builder
* Fixed force-close on Android 1.5

Class (I've renamed the class):

import java.util.ArrayList;
import java.util.Iterator;

import android.app.AlertDialog;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.Resources.Theme;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;

public class ColorAlertDialog extends AlertDialog {
private static int NONE = -1;
private int tint = NONE;

/**
* @param context
* @param theme
*/
protected ColorAlertDialog(Context context) {
super(context);
init();
}

/**
* @param context
* @param theme
*/
protected ColorAlertDialog(Context context, int theme) {
super(context, theme);
init();
}

/**
*
*/
private void init() {
final Theme theme = getContext().getTheme();
final TypedArray attrs = theme.obtainStyledAttributes(new int[] { android.R.attr.tint });
tint = attrs.getColor(0, NONE);
}

/* (non-Javadoc)
* @see android.app.Dialog#show()
*/
@Override
public void show() {
super.show();
setTint(tint);
}

/**
* @param tint
*/
public void setTint(int tint) {
this.tint = tint;

if ( tint != NONE ) {
Iterator vi = iterator(android.R.id.content);
while ( vi.hasNext() ) {
tintView(vi.next(), tint);
}
}
}

/**
* Set the {@link tint} color for the {@link View}
*
* @param v the {@link View} to change the tint
* @param tint color tint
*/
private static void tintView(final View v, final int tint) {
if ( v != null ) {
final Mode mode = Mode.SRC_ATOP;
if ( v instanceof ImageView ) {
final Drawable d = ((ImageView)v).getDrawable();
if ( d != null ) {
try {
d.mutate().setColorFilter(tint, mode);
} catch (Exception ex) {
// Patch for Android 1.5
}
}
}
else {
final Drawable d = v.getBackground();
if ( d != null ) {
d.setColorFilter(tint, mode);
}
}
}
}

/**
* @param button
*/
public void setCancelButton(Button button) {
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel();
}
});
}

/**
* @param button
*/
public void setPositiveButton(Button button) {
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
}

/**
* Return a {@link ChildrenIterator} starting at the {@link ViewGroup}
* identified by the specified resource id.
*
* @param res resource id of the {@link ViewGroup} to start the iteration
* @return iterator
*/
public Iterator iterator(int res) {
final ViewGroup vg = (ViewGroup)findViewById(res);
return new ChildrenIterator(vg);
}

public static class Builder {
private ColorAlertDialog dialog;

public Builder(Context context) {
dialog = new ColorAlertDialog(context);
}

public Builder(Context context, int theme) {
dialog = new ColorAlertDialog(context, theme);
}

public ColorAlertDialog create() {
return dialog;
}

public Builder setMessage(CharSequence message) {
dialog.setMessage(message);
return this;
}

public Builder setTitle(CharSequence title) {
dialog.setTitle(title);
return this;
}

public Builder setPositiveButton(int resId, OnClickListener listener) {
return setPositiveButton(dialog.getContext().getString(resId), listener);
}

public Builder setPositiveButton(CharSequence text, OnClickListener listener) {
dialog.setButton(BUTTON_POSITIVE, text, listener);
return this;
}

public Builder setNegativeButton(int resId, OnClickListener listener) {
return setNegativeButton(dialog.getContext().getString(resId), listener);
}

public Builder setNegativeButton(CharSequence text, OnClickListener listener) {
dialog.setButton(BUTTON_NEGATIVE, text, listener);
return this;
}

public Builder setIcon(int iconId) {
dialog.setIcon(iconId);
return this;
}

public Builder setCancelable(boolean flag) {
dialog.setCancelable(flag);
return this;
}

public ColorAlertDialog show() {
dialog.show();
return dialog;
}
}

public class ChildrenIterator implements Iterator {
ArrayList list;
private int i;

public ChildrenIterator(ViewGroup vg) {
super();

if ( vg == null )
throw new RuntimeException("ChildrenIterator needs a ViewGroup != null to find its children");

init();
findChildrenAndAddToList(vg, list);
}

private void init() {
list = new ArrayList();
i = 0;
}

@Override
public boolean hasNext() {
return ( i < list.size() );
}

@Override
public V next() {
return list.get(i++);
}

@Override
public void remove() {
list.remove(i);
}

@SuppressWarnings("unchecked")
private void findChildrenAndAddToList(final ViewGroup root, final ArrayList list) {
for (int i=0; i < root.getChildCount(); i++) {
V v = (V)root.getChildAt(i);
list.add(v);
if ( v instanceof ViewGroup )
findChildrenAndAddToList((ViewGroup)v, list);

}
}
}
}


And the styles (put in an XML file under the 'res/values' folder):











#88FF0000
#880000FF
#88FFFF00
#88995f86
#aaffbf00
#88ff33cc
#0000

2 comments:

CJ said...

Nice code but there are a number of typos and some rather confusing reuse of v as a class and a member:

Lines 63,129,131,193 all have View written incorrectly.

Line 230 has the class 'v' written (probably more correctly) as V. It should either be corrected to
v v = (v)root.getChildAt(i);
or more sensibly rewriting the class v as V or something more descriptive given the confusion it can create.

CJ said...

Please refer to the line above the line numbers I referenced, I forgot to include the package line my IDE included automatically when I copied the code..