La classe principale permettant de développer un Live Wallpaper est la classe WallpaperService. Cette classe encapsule la classe fille WallpaperService.Engine. L'Engine est l'interface entre le papier peint et l'interface utilisateur et contient la Surface sur laquelle le fond d'écran est rendu.
La première étape consiste à étendre la classe WallpaperService et son WallpaperService.Engine. Comme tout service Android, un WallpaperService doit être déclaré au niveau du fichier AndroidManifest.xml avec l'Intent android.service.wallpaper.WallpaperService afin que le système Android identifie ce service en tant que Live Wallpaper. La permission android.permission.BIND_WALLPAPER doit également être positionnée afin d'autoriser les utilisateurs à utiliser ce Live Wallpaper sur leur Home :
<service android:name="LiveWallpaperService" android:enabled="true" android:icon="@drawable/icon" android:label="@string/app_name" android:permission="android.permission.BIND_WALLPAPER"> <intent-filter android:priority="1" > <action android:name="android.service.wallpaper.WallpaperService" /> </intent-filter> <meta-data android:name="android.service.wallpaper" android:resource="@xml/wallpaper" /> </service>Un fichier XML spécifique doit être placé dans le répertoire /res/xml/ de votre projet. Il permet de fournir une description du Live Wallpaper :
<?xml version="1.0" encoding="UTF-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" android:thumbnail="@drawable/thumbnail" android:description="@string/description" android:settingsActivity="PreferenceActivity"/>Le fichier attrs.xml d'Android décrit les différentes valeurs descriptibles pour les Live Wallpaper :
<declare-styleable name="Wallpaper"> <!-- Nom du composant préférences du Live Wallpaper permettant d'en modifier les caractéristiques. --> <attr name="settingsActivity" /> <!-- Une icone a afficher dans la liste des Live Wallpaper disponibles. --> <attr name="thumbnail" format="reference" /> <!-- Nom de l'auteur du Live Wallpaper. --> <attr name="author" format="reference" /> <!-- Description du Live Wallpaper. --> <attr name="description" /> </declare-styleable>Une implémenation type du Live Wallpaper pourrait-être :
package net.androgames.blog.sample.livewallpaper; import android.content.SharedPreferences; import android.service.wallpaper.WallpaperService; import android.view.MotionEvent; import android.view.SurfaceHolder; /** * Android Live Wallpaper Archetype * @author antoine vianey * under GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html */ public class LiveWallpaperService extends WallpaperService { @Override public Engine onCreateEngine() { return new SampleEngine(); } @Override public void onCreate() { super.onCreate(); } @Override public void onDestroy() { super.onDestroy(); } public class SampleEngine extends Engine { private LiveWallpaperPainting painting; SampleEngine() { SurfaceHolder holder = getSurfaceHolder(); painting = new LiveWallpaperPainting(holder, getApplicationContext()); } @Override public void onCreate(SurfaceHolder surfaceHolder) { super.onCreate(surfaceHolder); // register listeners and callbacks here setTouchEventsEnabled(true); } @Override public void onDestroy() { super.onDestroy(); // remove listeners and callbacks here painting.stopPainting(); } @Override public void onVisibilityChanged(boolean visible) { if (visible) { // register listeners and callbacks here painting.resumePainting(); } else { // remove listeners and callbacks here painting.pausePainting(); } } @Override public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { super.onSurfaceChanged(holder, format, width, height); painting.setSurfaceSize(width, height); } @Override public void onSurfaceCreated(SurfaceHolder holder) { super.onSurfaceCreated(holder); // start painting painting.start(); } @Override public void onSurfaceDestroyed(SurfaceHolder holder) { super.onSurfaceDestroyed(holder); boolean retry = true; painting.stopPainting(); while (retry) { try { painting.join(); retry = false; } catch (InterruptedException e) {} } } @Override public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) { } @Override public void onTouchEvent(MotionEvent event) { super.onTouchEvent(event); painting.doTouchEvent(event); } } }Les méthodes onCreate, onDestroy, onVisibilityChanged, onSurfaceChanged, onSurfaceCreated et onSurfaceDestroyed de l'Engine sont appelées lorsque l'état, la taille où la visibilité de la surface de rendu changent. Grâce à ces méthodes, il nous est possible d'animer le Live Wallpaper que lorsque cela est nécessaire. Les événements de toucher d'écran sont activés via la méthode setTouchEventsEnabled(true) récupérés par le callback onTouchEvent(MotionEvent event).
Le rendu du Live Wallpaper est effectué par un Thread spécialisé :
package net.androgames.blog.sample.livewallpaper; import android.content.Context; import android.graphics.Canvas; import android.view.MotionEvent; import android.view.SurfaceHolder; /** * Android Live Wallpaper painting thread Archetype * @author antoine vianey * GPL v3 : http://www.gnu.org/licenses/gpl-3.0.html */ public class LiveWallpaperPainting extends Thread { /** Reference to the View and the context */ private SurfaceHolder surfaceHolder; private Context context; /** State */ private boolean wait; private boolean run; /** Dimensions */ private int width; private int height; /** Time tracking */ private long previousTime; private long currentTime; public LiveWallpaperPainting(SurfaceHolder surfaceHolder, Context context) { // keep a reference of the context and the surface // the context is needed if you want to inflate // some resources from your livewallpaper .apk this.surfaceHolder = surfaceHolder; this.context = context; // don't animate until surface is created and displayed this.wait = true; } /** * Pauses the live wallpaper animation */ public void pausePainting() { this.wait = true; synchronized(this) { this.notify(); } } /** * Resume the live wallpaper animation */ public void resumePainting() { this.wait = false; synchronized(this) { this.notify(); } } /** * Stop the live wallpaper animation */ public void stopPainting() { this.run = false; synchronized(this) { this.notify(); } } @Override public void run() { this.run = true; Canvas c = null; while (run) { try { c = this.surfaceHolder.lockCanvas(null); synchronized (this.surfaceHolder) { currentTime = System.currentTimeMillis(); updatePhysics(); doDraw(c); previousTime = currentTime; } } finally { if (c != null) { this.surfaceHolder.unlockCanvasAndPost(c); } } // pause if no need to animate synchronized (this) { if (wait) { try { wait(); } catch (Exception e) {} } } } } /** * Invoke when the surface dimension change */ public void setSurfaceSize(int width, int height) { this.width = width; this.height = height; synchronized(this) { this.notify(); } } /** * Invoke while the screen is touched */ public void doTouchEvent(MotionEvent event) { // handle the event here // if there is something to animate // then wake up this.wait = false; synchronized(this) { notify(); } } /** * Do the actual drawing stuff */ private void doDraw(Canvas canvas) {} /** * Update the animation, sprites or whatever. * If there is nothing to animate set the wait * attribute of the thread to true */ private void updatePhysics() { // if nothing was updated : // this.wait = true; } }Cette classe est optimisée pour n'effectuer le rendu de la surface que si celle ci est visible et qu'un élément à rendre a été déplacé. S'il n'y a rien à animer, la méthode updatePhysics indique au Thread d'attendre. La méthode doDraw(Canvas canvas) doit quand même être appelée une dernière fois car les SurfaceView utilisent deux canvas en alternance pour le rendu...
Pour autoriser la configuration d'un Live Wallpaper, il suffit de créer une PreferenceActivity et associée celle-ci au Live Wallpaper via le fichier XML de description d'un Live Wallpaper vu plus haut. Les valeurs des préférences peuvent être récupérée via un objet SharedPreference.
Le code source de ce Live Wallpaper est disponible ici : SampleLiveWallpaper.
N'hésitez pas à présenter vos réalisation en commentaire de ce post !