Last week we laid out the foundation for creating a live wallpaper on an Android phone. While the application architecture is a bit different from the projects we normally tackle in this blog, once the basics are there, things begin to fall into place.
In this tutorial, I explain how to implement a settings screen for your wallpaper service, and share a handy trick for debugging live wallpapers in the Eclipse IDE. The finished project file can be downloaded here. The archive contains everything that was in last week’s download as well as the new/modified files.
1. We will be modifying the project from part one of this tutorial, so rather than creating a new project in Eclipse add the project from last week back into a workspace and prepare to pick up where we left off.
2. Open the AndroidManifest.xml file from your /res folder. Just under the <service> tag we previously defined and inside the <application> tag a new activity gets included. This represents our preferences screen. Below is a final version of the manifest.
AndroidManifest.xml<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.authorwjf.live_wallpaper_p2" android:versionCode="1"
android:versionName="1.0">
<application android:label="TR Demo Live Wallpaper" android:icon="@drawable/icon">
<service android:label="TR Demo Live Wallpaper"android:name=".DemoWallpaperService"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data android:name="android.service.wallpaper"
android:resource="@xml/wallpaper" />
</service>
<activity android:label="Settings..."
android:name=".WallpaperPrefsActivity"
android:exported="true">
</activity>
</application>
<uses-sdk android:minSdkVersion="7" />
<uses-feature android:name="android.software.live_wallpaper" />
</manifest>
3. We want to create an XML file that will define the settings we will use to customize our wallpaper. To keep the demo simple, we aren’t going to do anything to crazy. We’ll collect a string to display and a color for the text. Add a new file called prefs.xml to the /res/xml folder.
prefs.xml<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference android:key="text_to_display"
android:title="Text to display:" />
<EditTextPreference android:key="text_color"
android:title="Hex color value:" />
</PreferenceScreen>
4. In the /src folder we will need to create the WallpaperPrefsActivity. By extending the PreferenceActivity class and pointing it at our prefs.xml file, the platform generates most of the code for us. The one exception being the validation for the input, which we are handling in the onPreferenceChange callback.
WallpaperPrefsActivity.java package com.authorwjf.live_wallpaper_p2; import android.graphics.Color; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.Preference.OnPreferenceChangeListener; import android.widget.Toast;
public class WallpaperPrefsActivity extends PreferenceActivity implements OnPreferenceChangeListener {
@Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState);addPreferencesFromResource(R.xml.prefs);
Preference p = getPreferenceScreen().findPreference("text_color");
p.setOnPreferenceChangeListener(this);p = getPreferenceScreen().findPreference("text_to_display");
p.setOnPreferenceChangeListener(this); }
@Override
publicboolean onPreferenceChange(Preference preference, Object newValue) {
if (preference.getKey().equalsIgnoreCase("text_color")) {
try {
String input = newValue.toString();
if (input.length()!=7) thrownew Exception ("Invalid length");
if (!input.startsWith("#")) thrownew Exception ("Invalid format");
String r = input.substring(1,3);
String g = input.substring(3,5);
String b = input.substring(5,7);
intcolor = Color.rgb(Integer.parseInt(r, 16), Integer.parseInt(g, 16), Integer.parseInt(b, 16));
returntrue;
} catch (Exception e) {
Toast.makeText(this, "Invalid hex color value (example input: #ff0000).",Toast.LENGTH_SHORT).show();
returnfalse;
}
} elseif (preference.getKey().equalsIgnoreCase("text_to_display")) {
try {
String input = newValue.toString();
if (input.length()<1) thrownew Exception ("Invalid length");
returntrue;
} catch (Exception e) {
Toast.makeText(this, "Invalid display string.",Toast.LENGTH_SHORT).show();
returnfalse;
}}
return true;}
}
5. The final step will be to modify the DemoWallpaperService file we created last week. The service has been altered first to load in our preferences in the onVisibilityChanged callback, and secondly to draw that text in the specified color in the draw() method. Nothing else changes from last week. The majority of the code that changed in the draw() method deals with increasing and decreasing the alpha channel to produce a pulsing effect on the text.
package com.authorwjf.live_wallpaper_p2;
import android.content.SharedPreferences; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Handler; import android.preference.PreferenceManager; import android.service.wallpaper.WallpaperService; import android.view.SurfaceHolder;
public class DemoWallpaperService extends WallpaperService {
@Override public Engine onCreateEngine() { return new DemoWallpaperEngine(); }
private class DemoWallpaperEngine extends Engine { private boolean mVisible = false; private int mColor = 0; private int mAlpha = 0; private boolean mIsCountingUp=true; private String mText = "Hello!"; private final Handler mHandler = new Handler(); private final Runnable mUpdateDisplay = new Runnable() {@Override
public void run() {draw();
}};
private void draw() {SurfaceHolder holder = getSurfaceHolder();
Canvas c = null; try {c = holder.lockCanvas();
if (c != null) {//android.os.Debug.waitForDebugger();
Paint p = new Paint();p.setTextSize(20);
p.setAntiAlias(true);String text = mText;
float w = p.measureText(text, 0, text.length()); int offset = (int) w / 2; int x = c.getWidth()/2 - offset; int y = c.getHeight()/2;p.setColor(Color.BLACK);
c.drawRect(0, 0, c.getWidth(), c.getHeight(), p);
p.setColor(mColor);
p.setAlpha(mAlpha);
c.drawText(text, x, y, p);
if (mIsCountingUp) {mAlpha+=10;
if (mAlpha > 255) {mAlpha = 255;
mIsCountingUp=false; }
} else {mAlpha-=10;
if (mAlpha < 0) {mAlpha = 0;
mIsCountingUp=true;}
}
} } finally { if (c != null)holder.unlockCanvasAndPost(c);
}
mHandler.removeCallbacks(mUpdateDisplay);
if (mVisible) {mHandler.postDelayed(mUpdateDisplay, 100);
}
}
@Override public void onVisibilityChanged(boolean visible) {mVisible = visible;
if (visible) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(DemoWallpaperService.this);mText = prefs.getString("text_to_display", "This is the default.");
String tmp = prefs.getString("text_color", "#ffffff");
String r = tmp.substring(1,3);
String g = tmp.substring(3,5);
String b = tmp.substring(5,7);
mColor = Color.rgb(Integer.parseInt(r, 16), Integer.parseInt(g, 16), Integer.parseInt(b, 16));
draw();
} else {mHandler.removeCallbacks(mUpdateDisplay);
}
}
@Override public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {draw();
}
@Override public void onSurfaceDestroyed(SurfaceHolder holder) { super.onSurfaceDestroyed(holder); mVisible = false;mHandler.removeCallbacks(mUpdateDisplay);
}
@Override public void onDestroy() { super.onDestroy(); mVisible = false;mHandler.removeCallbacks(mUpdateDisplay);
}
}
}
There you have it! A modest but fully functional live wallpaper. One thing that is worth noting is the commented out call to //android.os.Debug.waitForDebugger(); in the draw method. For the purpose of this tutorial, you have the advantage of working with tested code, but should your live wallpaper service require debugging, just setting a break point via the IDE will not work as you expect. You need to add the waitForDebugger() call in the source file.
With that, I will leave you to creating your own live wallpapers. I look forward to downloading them in the Android Market! Feel free to post a link in the discussion area to show off your creations to other TechRepublic members.
Figure A
Figure B
Figure C