Introduction
When Android 2.1 was released, it came out with this nice new feature called ‘Live Wallpapers‘. I’m not going to describe in details what Live Wallpapers are here, but what people didn’t realize at that time is that they can’t be considered as traditional applications or widgets:
- They don’t appear in the application launcher, instead you need to go through numerous steps to open the Live Wallpaper Preview and set your wallpaper
- The market’s ‘Open’ button is disabled by default
In other words, if you’re not used to Live Wallpapers, you may feel a bit lost trying to use one. When I launched Shake Them All, I received a lot of feedback asking how to launch the application, some of them thought it just didn’t work, probably because they couldn’t launch it through regular ways.
My opinion is that the guys at Google should enable the ‘Open’ button of the market by default, and bind it to the Live Wallpaper Preview set on the installed Wallpaper. In the meantime, the only solution I found for Shake Them All was to bind my own Activity to the ‘Open’ button, and use an Intent action to have it launch the Live Wallpaper Picker while displaying a Toast message.
Binding an Activity to the ‘Open’ button of the market
First, you will need a simple Activity:
package net.yougli.shakethemall;
import android.app.Activity;
import android.os.Bundle;
public class OpenActivity extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
}
}
Now, binding this Activity to the ‘Open’ button of the market is simply done by adding these lines to your AndroidManifest.xml, inside the <application> element:
<activity android:label="Home" android:name=".OpenActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.INFO" /> </intent-filter> </activity>
Basically, this declares our new Activity, and set the Intent filter that will bind it to our ‘Open’ button.
Launching the Live Wallpaper Picker
Now that our ‘Open’ button can do something, let it launch the Live Wallpaper Picker:
package net.yougli.shakethemall;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.Intent;
import android.os.Bundle;
public class OpenActivity extends Activity {
private int REQUEST_CODE = 1;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Intent intent = new Intent();
intent.setAction(WallpaperManager.ACTION_LIVE_WALLPAPER_CHOOSER);
startActivityForResult(intent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent)
{
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == REQUEST_CODE)
finish();
}
}
To achieve this we create a new Intent, set its action to ACTION_LIVE_WALLPAPER_CHOOSER, and launch it with startActivityForResult().
I could have simply used startActivity(), but I wanted to make sure my Activity is closed as soon as the user selects a wallpaper. Hence the override of the onActivityResult() method and its call of finish().
Displaying a Toast
Ok now, that’s nice, the user will see the list of Live Wallpapers installed on its system, but he still has to select one before really using it, so we’re going to help him a little by suggesting him what to do with the help of a Toast message:
package net.yougli.shakethemall;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
public class OpenActivity extends Activity {
private int REQUEST_CODE = 1;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Toast toast = Toast.makeText(this, "Choose \"Shake Them All!\" in the list to start the Live Wallpaper.", Toast.LENGTH_LONG);
toast.show();
Intent intent = new Intent();
intent.setAction(WallpaperManager.ACTION_LIVE_WALLPAPER_CHOOSER);
startActivityForResult(intent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent)
{
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == REQUEST_CODE)
finish();
}
}
To display a Toast, we simply create one through the static makeText() method, which takes the text to display, and the display time as parameters. We then call the show() method to display it. Of course, the Toast needs to be displayed before we launch the picker Activity.
Note that you could, for instance, make your Toast a little more attractive by creating a custom View to be displayed in it, via the setView() method.
Voilà, that’s all.
Again, this is not the perfect solution, but hopefully your users will fell a bit less disappointed now … while Google make some changes in the Market
Introduction
Depending on your application, you might one day need a good color picker.
With Shake Them All! I needed a color picker to select the color of the default skin and default background. I first had a look at the Android API to see if I could find a Dialog that I could use, without success. I then looked at the SDK samples, and found this color picker:
It is nice but quite limited regarding the number of colors available (black and white are missing for instance), so I used it for a while but I soon needed to make my own.
As I’m quite familiar with the Photoshop color picker, I wanted to make one that looked like it and came up with this list of feature:
- it should allow the selection of any color without limitation
- be able to keep the default color in memory and restore it
- be implemented as a custom Dialog for re usability
- and still be simple enough to be used on a smartphone
- finally the selected color should be associated to a key to be stored in the SharedPreferences
To achieve this, I needed to implement my own custom Dialog (ColorPickerDialog), associated with a custom View (ColorPickerView).
I’m now going to describe the classes and methods, so if you’re in a hurry, you can jump directly to the end of the post where you will find the download link with instructions on how to use the dialog.
ColorPickerDialog
The ColorPickerDialog is quite simple.
public class ColorPickerDialog extends Dialog {
public interface OnColorChangedListener {
void colorChanged(String key, int color);
}
private OnColorChangedListener mListener;
private int mInitialColor, mDefaultColor;
private String mKey;
public ColorPickerDialog(Context context, OnColorChangedListener listener, String key, int initialColor, int defaultColor) {
super(context);
mListener = listener;
mKey = key;
mInitialColor = initialColor;
mDefaultColor = defaultColor;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OnColorChangedListener l = new OnColorChangedListener() {
public void colorChanged(String key, int color) {
mListener.colorChanged(mKey, color);
dismiss();
}
};
setContentView(new ColorPickerView(getContext(), l, mInitialColor, mDefaultColor));
setTitle(R.string.settings_bg_color_dialog);
}
}
Basically, this Dialog first declares a listener Interface that will be used to notify the new selected color to the class that opened it. The constructor then initializes a few variables:
- mListener: the listener passed to the constructor
- mKey: the key associated to the color (I needed this because I’m using the same Dialog for 2 Preferences – skin color and background color)
- mInitialColor: the initial color
- mDefaultColor: the default color
The onCreate method declares what to do when the OnColorChangedListener is called: notify the caller, and close (dismiss) the dialog. The method also sets the content view to our custom View, and the title.
ColorPickerView
This one is more complicated… And to be honest I made this a while ago and I won’t be able to explain every line of code
Here are the main methods used in the class. I’ve left some comments in the code which will hopefully help you understand how it works. If not, feel free to ask me in the comments of the post.
Constructor
The constructor initializes an array of colors used in the hue bar (the top bar):
ColorPickerView(Context c, OnColorChangedListener l, int color, int defaultColor) {
super(c);
mListener = l;
mDefaultColor = defaultColor;
// Get the current hue from the current color and update the main color field
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
mCurrentHue = hsv[0];
updateMainColors();
mCurrentColor = color;
// Initialize the colors of the hue slider bar
int index = 0;
for (float i=0; i<256; i += 256/42) // Red (#f00) to pink (#f0f)
{
mHueBarColors[index] = Color.rgb(255, 0, (int) i);
index++;
}
for (float i=0; i<256; i += 256/42) // Pink (#f0f) to blue (#00f)
{
mHueBarColors[index] = Color.rgb(255-(int) i, 0, 255);
index++;
}
for (float i=0; i<256; i += 256/42) // Blue (#00f) to light blue (#0ff)
{
mHueBarColors[index] = Color.rgb(0, (int) i, 255);
index++;
}
for (float i=0; i<256; i += 256/42) // Light blue (#0ff) to green (#0f0)
{
mHueBarColors[index] = Color.rgb(0, 255, 255-(int) i);
index++;
}
for (float i=0; i<256; i += 256/42) // Green (#0f0) to yellow (#ff0)
{
mHueBarColors[index] = Color.rgb((int) i, 255, 0);
index++;
}
for (float i=0; i<256; i += 256/42) // Yellow (#ff0) to red (#f00)
{
mHueBarColors[index] = Color.rgb(255, 255-(int) i, 0);
index++;
}
// Initializes the Paint that will draw the View
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(12);
}
getCurrentMainColor
Returns the current selected color from the hue bar:
private int getCurrentMainColor()
{
int translatedHue = 255-(int)(mCurrentHue*255/360);
int index = 0;
for (float i=0; i<256; i += 256/42)
{
if (index == translatedHue)
return Color.rgb(255, 0, (int) i);
index++;
}
for (float i=0; i<256; i += 256/42)
{
if (index == translatedHue)
return Color.rgb(255-(int) i, 0, 255);
index++;
}
for (float i=0; i<256; i += 256/42)
{
if (index == translatedHue)
return Color.rgb(0, (int) i, 255);
index++;
}
for (float i=0; i<256; i += 256/42)
{
if (index == translatedHue)
return Color.rgb(0, 255, 255-(int) i);
index++;
}
for (float i=0; i<256; i += 256/42)
{
if (index == translatedHue)
return Color.rgb((int) i, 255, 0);
index++;
}
for (float i=0; i<256; i += 256/42)
{
if (index == translatedHue)
return Color.rgb(255, 255-(int) i, 0);
index++;
}
return Color.RED;
}
updateMainColors
Updates the main field colors depending on the current selected hue:
private void updateMainColors()
{
int mainColor = getCurrentMainColor();
int index = 0;
int[] topColors = new int[256];
for (int y=0; y<256; y++)
{
for (int x=0; x<256; x++)
{
if (y == 0)
{
mMainColors[index] = Color.rgb(255-(255-Color.red(mainColor))*x/255, 255-(255-Color.green(mainColor))*x/255, 255-(255-Color.blue(mainColor))*x/255);
topColors[x] = mMainColors[index];
}
else
mMainColors[index] = Color.rgb((255-y)*Color.red(topColors[x])/255, (255-y)*Color.green(topColors[x])/255, (255-y)*Color.blue(topColors[x])/255);
index++;
}
}
}
onDraw
Displays the hue bar, the main field, the circle around the selected color, and the buttons to close the dialog with either the selected or the default color:
@Override
protected void onDraw(Canvas canvas) {
int translatedHue = 255-(int)(mCurrentHue*255/360);
// Display all the colors of the hue bar with lines
for (int x=0; x<256; x++)
{
// If this is not the current selected hue, display the actual color
if (translatedHue != x)
{
mPaint.setColor(mHueBarColors[x]);
mPaint.setStrokeWidth(1);
}
else // else display a slightly larger black line
{
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(3);
}
canvas.drawLine(x+10, 0, x+10, 40, mPaint);
}
// Display the main field colors using LinearGradient
for (int x=0; x<256; x++)
{
int[] colors = new int[2];
colors[0] = mMainColors[x];
colors[1] = Color.BLACK;
Shader shader = new LinearGradient(0, 50, 0, 306, colors, null, Shader.TileMode.REPEAT);
mPaint.setShader(shader);
canvas.drawLine(x+10, 50, x+10, 306, mPaint);
}
mPaint.setShader(null);
// Display the circle around the currently selected color in the main field
if (mCurrentX != 0 && mCurrentY != 0)
{
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.BLACK);
canvas.drawCircle(mCurrentX, mCurrentY, 10, mPaint);
}
// Draw a 'button' with the currently selected color
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mCurrentColor);
canvas.drawRect(10, 316, 138, 356, mPaint);
// Set the text color according to the brightness of the color
if (Color.red(mCurrentColor)+Color.green(mCurrentColor)+Color.blue(mCurrentColor) < 384)
mPaint.setColor(Color.WHITE);
else
mPaint.setColor(Color.BLACK);
canvas.drawText(getResources().getString(R.string.settings_bg_color_confirm), 74, 340, mPaint);
// Draw a 'button' with the default color
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mDefaultColor);
canvas.drawRect(138, 316, 266, 356, mPaint);
// Set the text color according to the brightness of the color
if (Color.red(mDefaultColor)+Color.green(mDefaultColor)+Color.blue(mDefaultColor) < 384)
mPaint.setColor(Color.WHITE);
else
mPaint.setColor(Color.BLACK);
canvas.drawText(getResources().getString(R.string.settings_default_color_confirm), 202, 340, mPaint);
}
onTouchEvent
Deals with the touch events:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN) return true;
float x = event.getX();
float y = event.getY();
// If the touch event is located in the hue bar
if (x > 10 && x < 266 && y > 0 && y < 40)
{
// Update the main field colors
mCurrentHue = (255-x)*360/255;
updateMainColors();
// Update the current selected color
int transX = mCurrentX-10;
int transY = mCurrentY-60;
int index = 256*(transY-1)+transX;
if (index > 0 && index < mMainColors.length)
mCurrentColor = mMainColors[256*(transY-1)+transX];
// Force the redraw of the dialog
invalidate();
}
// If the touch event is located in the main field
if (x > 10 && x < 266 && y > 50 && y < 306)
{
mCurrentX = (int) x;
mCurrentY = (int) y;
int transX = mCurrentX-10;
int transY = mCurrentY-60;
int index = 256*(transY-1)+transX;
if (index > 0 && index < mMainColors.length)
{
// Update the current color
mCurrentColor = mMainColors[index];
// Force the redraw of the dialog
invalidate();
}
}
// If the touch event is located in the left button, notify the listener with the current color
if (x > 10 && x < 138 && y > 316 && y < 356)
mListener.colorChanged("", mCurrentColor);
// If the touch event is located in the right button, notify the listener with the default color
if (x > 138 && x < 266 && y > 316 && y < 356)
mListener.colorChanged("", mDefaultColor);
return true;
}
How to use it
Here is an excerpt of the Shake Them All! settings:
public class MySettings extends PreferenceActivity implements OnPreferenceClickListener, ColorPickerDialog.OnColorChangedListener {
................................................
public boolean onPreferenceClick(Preference pref)
{
new ColorPickerDialog(this, this, DROIDS_COLOR_KEY, mPrefs.getInt(DROIDS_COLOR_KEY, DROIDS_COLOR_DEFAULT), DROIDS_COLOR_DEFAULT).show();
return true;
}
public void colorChanged(String key, int color)
{
((PreferenceScreen)this.findPreference(SETTINGS_KEY)).getEditor().putInt(key, color).commit();
}
}
Basically, your PreferenceActivity should implement the OnColorChangedListener interface. Then, you can open the color picker like any dialog by initializing it and calling its show method. Finally, in your listener, you can use your SharedPreference editor to change the value of the preference with the selected color, and commit your changes.
Voilà, this was my first, poor contribution to the wonderful world of Android. I hope this will help some of you, feel free to use, modify, distribute this code, and of course leave your comments there
Well… welcome to my diary.
This blog will focus on Android development and code snippets.
When I got my Nexus One, I almost immediately started writing applications for it. As I wrote my first one (Shake Them All!), I often got stuck with very specific stuffs for which I couldn’t find any help or tutorials on the net. Android is a wonderful OS, but being so young makes it difficult for new developers to find help and code snippets even for quite common tasks.
That’s why I decided to start this blog.
You will find here some code samples coming from my actual applications (Shake Them All! and Put.io on the go!), and later, code samples from my future applications. There is a bunch of applications I’d like to make, and I’m confident there will be interesting material to put on there.
Anyway, I hope you will find what you’re looking for here



