Version 2.1
Copyright © 2011, 2012 Lars Vogel
15.10.2012
| Revision History | |||
|---|---|---|---|
| Revision 0.1 | 08.01.2011 | Lars Vogel |
Created |
| Revision 0.2 - 2.1 | 09.01.2011 - 15.10.2012 | Lars Vogel |
updated and bugfixes |
Table of Contents
The following tutorial assumes that you have already basic knowledge in Android development. Please check the Android development tutorial to learn the basics.
Live Wallpapers are animated, interactive backgrounds for the Android homescreen. A live wallpaper is similar to other Android applications and can use most of the same functionality.
To create a live wallpaper, you need to create an XML file which describes your wallpaper. This file should contain a description of the application and can contain a preview and a link to a preference activity Activity which allow to customize the live wallpaper.
You also create a
service
which
must extend the
WallpaperService
class. This class is the base class for all live
wallpapers in
the
system. You must implement the
onCreateEngine()
method
and return
an
object of type
android.service.wallpaper.WallpaperService.Engine. This
objects
handles the
life cycle
events,
animations and drawings
of
the
wallpaper. The
Engine
class defines the life
cycle methods, as for example
onCreate(),
onSurfaceCreated(),
onVisibilityChanged(),
onOffsetsChanged(),
onTouchEvent()
and
onCommand().
The
service
requires
the permission
android.permission.BIND_WALLPAPER
and
must be
registered via an
intent-filter for the
android.service.wallpaper.WallpaperService
action.
You should also enter in the
AndroidManifest.xml
file of the application
that your
application uses the
android.software.live_wallpaper
feature. This
will prevent
that your wallpaper can be
installed on
devices
which do
not support
live wallpapers.
You can use an Intent to set the Wallpaper.
// Button to set the Wallpaper public void onClick(View view) { Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER); intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(this, MyWallpaperService.class)); startActivity(intent); }
Create a new project called de.vogella.android.wallpaper. Do not create an activity.
Create the
/res/xml
folder and create the
mywallpaper.xml
file.
<?xml version="1.0" encoding="UTF-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" android:thumbnail="@drawable/icon" android:description="@string/wallpaper_description" android:settingsActivity="de.vogella.android.wallpaper.MyPreferencesActivity"/>
This file
contains a description of your
wallpaper
and
a preview
graphic.
You can
also enter a link to an
activity
which
allow to
configure the
wallpaper. This resource file
will be linked
to
from the
AndroidManifest.xml. You could also
include the "android:thumbnail attribute" which would
point to a
drawable which gives a smaller image of the running
wallpaper.
Change your
AndroidManifest.xml
to the following to define
your
MyWallpaperService
service. Also define the uses-feature.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.vogella.android.wallpaper" android:versionCode="1" android:versionName="1.0" > <application android:icon="@drawable/icon" android:label="@string/app_name" > <service android:name="MyWallpaperService" android:enabled="true" android:label="Wallpaper Example " android:permission="android.permission.BIND_WALLPAPER" > <intent-filter> <action android:name="android.service.wallpaper.WallpaperService" > </action> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/mywallpaper" > </meta-data> </service> <activity android:name=".MyPreferencesActivity" android:exported="true" android:label="@string/app_name" android:theme="@android:style/Theme.Light.WallpaperSettings" > </activity> <activity android:name=".SetWallpaperActivity" android:label="@string/app_name" android:theme="@android:style/Theme.Light.WallpaperSettings" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="10" /> <uses-feature android:name="android.software.live_wallpaper" android:required="true" > </uses-feature> </manifest>
We create the
MyPoint
class to save the elements we have drawn.
package de.vogella.android.wallpaper; public class MyPoint { String text; private int x; private int y; public MyPoint(String text, int x, int y) { this.text = text; this.x = x; this.y = y; } }
Create a new
activity. Create the
prefs.xml
preference
file
in the
res/xml
folder.
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key="touch" android:title="Enable Touch"></CheckBoxPreference> <EditTextPreference android:key="numberOfCircles" android:title="Number of Circles"></EditTextPreference> </PreferenceScreen>
Create a new activity called MyPreferencesActivity and the following class.
package de.vogella.android.wallpaper; import android.os.Bundle; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.PreferenceActivity; import android.widget.Toast; public class MyPreferencesActivity extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.prefs); // We want to add a validator to the number of circles so that it only // accepts numbers Preference circlePreference = getPreferenceScreen().findPreference("numberOfCircles"); // Add the validator circlePreference.setOnPreferenceChangeListener(numberCheckListener); }/** * Checks that a preference is a valid numerical value */Preference.OnPreferenceChangeListener numberCheckListener = new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { // Check that the string is an integer if (newValue != null && newValue.toString().length() > 0 && newValue.toString().matches("\\d*")) { return true; } // If now create a message to the user Toast.makeText(MyPreferencesActivity.this, "Invalid Input", Toast.LENGTH_SHORT).show(); return false; } }; }
Create the following coding for the Wallpaper service.
package de.vogella.android.wallpaper; import java.util.ArrayList; import java.util.List; 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.MotionEvent; import android.view.SurfaceHolder; public class MyWallpaperService extends WallpaperService { @Override public Engine onCreateEngine() { return new MyWallpaperEngine(); } private class MyWallpaperEngine extends Engine { private final Handler handler = new Handler(); private final Runnable drawRunner = new Runnable() { @Override public void run() { draw(); } }; private List<MyPoint> circles; private Paint paint = new Paint(); private int width; int height; private boolean visible = true; private int maxNumber; private boolean touchEnabled; public MyWallpaperEngine() { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(MyWallpaperService.this); maxNumber = Integer .valueOf(prefs.getString("numberOfCircles", "4")); touchEnabled = prefs.getBoolean("touch", false); circles = new ArrayList<MyPoint>(); paint.setAntiAlias(true); paint.setColor(Color.WHITE); paint.setStyle(Paint.Style.STROKE); paint.setStrokeJoin(Paint.Join.ROUND); paint.setStrokeWidth(10f); handler.post(drawRunner); } @Override public void onVisibilityChanged(boolean visible) { this.visible = visible; if (visible) { handler.post(drawRunner); } else { handler.removeCallbacks(drawRunner); } } }
@Override public void onSurfaceDestroyed(SurfaceHolder holder) { super.onSurfaceDestroyed(holder); this.visible = false; handler.removeCallbacks(drawRunner); } @Override public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { this.width = width; this.height = height; super.onSurfaceChanged(holder, format, width, height); } @Override public void onTouchEvent(MotionEvent event) { if (touchEnabled) { float x = event.getX(); float y = event.getY(); SurfaceHolder holder = getSurfaceHolder(); Canvas canvas = null; try { canvas = holder.lockCanvas(); if (canvas != null) { canvas.drawColor(Color.BLACK); circles.clear(); circles.add(new MyPoint(String.valueOf(circles.size() + 1), x, y)); drawCircles(canvas, circles); } } finally { if (canvas != null) holder.unlockCanvasAndPost(canvas); } super.onTouchEvent(event); } } private void draw() { SurfaceHolder holder = getSurfaceHolder(); Canvas canvas = null; try { canvas = holder.lockCanvas(); if (canvas != null) { if (circles.size() >= maxNumber) { circles.clear(); } int x = (int) (width * Math.random()); int y = (int) (height * Math.random()); circles.add(new MyPoint(String.valueOf(circles.size() + 1), x, y)); drawCircles(canvas, circles); } } finally { if (canvas != null) holder.unlockCanvasAndPost(canvas); } handler.removeCallbacks(drawRunner); if (visible) { handler.postDelayed(drawRunner, 5000); } } // Surface view requires that all elements are drawn completely private void drawCircles(Canvas canvas, List<MyPoint> circles) { canvas.drawColor(Color.BLACK); for (MyPoint point : circles) { canvas.drawCircle(point.x, point.y, 20.0f, paint); } } }
Create the following Activity which should use a layout with one
Button
included. This Button should use the
onClick
property to point to the
onClick
method.
package de.vogella.android.wallpaper; import android.app.Activity; import android.app.WallpaperManager; import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.view.View; public class SetWallpaperActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void onClick(View view) { Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER); intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(this, MyWallpaperService.class)); startActivity(intent); } }
If you start your application your application should allow to set the wallpaper. Your background should look similar to the following screenshot. If you have Touch enabled via the preferences you can click on the screen to remove the existing circles, also via the settings you can define the number of circles which should be displayed.

Before posting questions, please see the vogella FAQ. If you have questions or find an error in this article please use the www.vogella.com Google Group. I have created a short list how to create good questions which might also help you.
vogella Training Android and Eclipse Training from the vogella team
Android Tutorial Introduction to Android Programming
GWT Tutorial Program in Java and compile to JavaScript and HTML
Eclipse RCP Tutorial Create native applications in Java
JUnit Tutorial Test your application
Git Tutorial Put everything you have under distributed version control system