Créer un Live Wallpaper sous Android

Image non disponible

Dans cet article nous allons apprendre à réaliser un Live Wallpaper sous Android, autrement dit, un fond d'écran animé et interactif.
Sous Android, une classe permettant de développer des wallpapers est la classe «WallpaperService». Cette classe est disponible depuis la version 2.1 d'Android.

Image non disponible

Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Mon premier Live Wallpaper

Le but est de créer un Live Wallpaper contenant une image qui se déplace aléatoirement, cette image pourra aussi être déplacée en touchant l'écran.

Pour commencer, nous allons créer un nouveau projet Android qu'on nommera :« MyLiveWallpaper ». Le projet possédera les propriétés suivantes :

  • version du SDK : 2.3 ;
  • application name : My First Live Wallpaper ;
  • package name : com.tuto.android ;
  • min SDK version : 10 ;
  • décocher la cache : Create Activity.

On va tout d'abord créer dans le dossier res/xml un fichier «mywallpaper.xml» qui servira à décrire le wallpaper :

 
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<wallpaper 
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:thumbnail="@drawable/android"
        android:description="@string/wallpaper_description"/>

android:description  et  android:thumbnail  permettent d'affecter respectivement une description et une image à votre wallpaper. Cette description est disponible lors de la sélection du wallpaper par l'utilisateur.

Maintenant nous allons créer une classe  «MyLiveWallpaper»  qui hérite de la classe  «WallpaperService» , puis on va surcharger la méthode  onCreateEngine()  qui retourne une instance de la classe  «Engine» , cette instance gère :

  • le dessin ;
  • les animations ;
  • le cycle de vie ;
  • l'interaction utilisateur.
 
Sélectionnez
public class MyLiveWallpaper extends WallpaperService {
        @Override
        public Engine onCreateEngine() {
                return new LiveWallpaperEngine();
        }
}

Une fois cette étape effectuée, on crée une classe membre «LiveWallpaperEngine» qui étend la classe «Engine» puis on surcharge ses méthodes comme ci-dessous :

 
Sélectionnez
private class LiveWallpaperEngine extends Engine {

        private final Handler handler = new Handler();
        private final Runnable drawer = new Runnable() {
                @Override
                public void run() {
                        draw();
                }
        };

        private boolean visible = true;
        private int width;
        private int height;

        public LiveWallpaperEngine() {
                handler.post(drawer);
        }

        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
            setTouchEventsEnabled(true);
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            handler.removeCallbacks(drawer);
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
                super.onSurfaceCreated(holder);
        }

        @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 onSurfaceDestroyed(SurfaceHolder holder) {
                super.onSurfaceDestroyed(holder);
                this.visible = false;
                handler.removeCallbacks(drawer);
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
                this.visible = visible;
                if (visible) {
                        handler.post(drawer);
                } else {
                        handler.removeCallbacks(drawer);
                }
        }

        @Override
        public void onTouchEvent(MotionEvent event) {
                super.onTouchEvent(event);
        }
}

Passons à quelques explications :

  • la méthode  onCreate(SurfaceHolder surfaceHolder)  initialise l'engine et active en même temps les événements tactiles via la méthode  setTouchEventsEnabled(true)  ;
  • la méthode  onDestroy()  : après l'appel à cette méthode, l'engine n'est plus valide ;
  • la méthode  onSurfaceCreated(SurfaceHolder holder)  est appelée quand la surface de dessin est créée ;
  • la méthode  onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)  est appelée quand  la surface de dessin subit un changement ;
  • la méthode  onSurfaceDestroyd(SurfaceHolder holder)  est appelée quand la surface de dessin est détruite ;
  • la méthode  onVisibilityChanged(boolean visible)  nous permet de savoir si le wallpaper est visible ou non. Quand le wallpaper est visible, le dessin continue de s'effectuer sur le fond d'écran. Si une application passe en foreground le dessin s'arrête, cela permet d'économiser la batterie et minimiser l'impact du wallpaper sur les performances du système ;
  • la méthode  onTouchEvent(MotionEvent event)  gère l'interaction utilisateur avec le wallpaper ;
  • un Live Wallpaper a un contenu dynamique, ce qui permet d'avoir des animations. C'est donc à vous de gérer les différentes étapes de dessin sur le wallpaper. Pour cela, on utilise les deux classes  Runnable  et  Handler .
    Le handler envoie le runnable (ici  drawer ) dans le  messageQueue  associé à l'UI Thread afin qu'il soit exécuté. Ce runnable se charge de dessiner sur le wallpaper grâce à la méthode  draw()  qu'on expliquera plus tard. Répéter plusieurs fois ce mécanisme nous permet d'avoir une animation.
    Quand le wallpaper doit s'arrêter, la variable  visible  (qui stocke la visibilité actuelle du wallpaper) passe à  false dans les méthodes  onSurfaceDestroyed()  et   onVisibilityChanged()  ce qui stoppera l'animation (méthode handler.removeCallbacks(drawer)).
    Quand le wallpaper devient visible, la variable  visible  passe à  true  dans la méthode   onVisibilityChanged()  , ce qui relance l'animation du wallpaper (méthode  handler.post(drawer)).
    Voyons maintenant comment on dessine sur le wallpaper, la méthode facilitant cette opération est la méthode draw()  :
 
Sélectionnez
private void draw() {
        SurfaceHolder holder = getSurfaceHolder();
        Canvas canvas = null;
        try {
                canvas = holder.lockCanvas();
                if (canvas != null) {
                        float x = (width * random.nextFloat());
                        float y = (height * random.nextFloat());
                        drawImage(canvas, x, y);
                }
        } finally {
                if (canvas != null)
                        holder.unlockCanvasAndPost(canvas);
        }
        handler.removeCallbacks(drawer);
        if (visible) {
                //On reposte le runnable après un petit laps de temps
                handler.postDelayed(drawer, 4000);
        }
}

La méthode qui permet de dessiner l'image dans le canvas est décrite ci-dessous :

 
Sélectionnez
private void drawImage(Canvas canvas, float x, float y) {
        canvas.drawColor(Color.WHITE);
        canvas.drawBitmap(androidPic, x-(androidPic.getWidth()/2), y-(androidPic.getHeight()/2), null);
}

Sans oublier de déclarer la variable qui représente l'image qu'on veut dessiner :

 
Sélectionnez
Bitmap androidPic = BitmapFactory.decodeResource(getResources(), R.drawable.android);

Explications  :

Nous avons utilisé un  Canvas , ce qui nous permet de dessiner de manière répétitive sur la  Surface  du wallpaper. Cette surface est fournie par la classe  « SurfaceView » .

Afin de manipuler cette surface, on utilise l'interface  SurfaceHolder . On obtient le canvas grâce à la méthode lockCanvas() , puis on dessine sur un point du canvas avec la méthode  drawImage() .Ce point est obtenu aléatoirement grâce à ses coordonnées (x,y).

Enfin la méthode  unlockCanvasAndPost(canvas)  est appelée pour que le canvas soit dessiné sur la surface du wallpaper.

II. Gestion des événements tactiles :

Cela se fait grâce à la méthode  onTouchEvent(MotioEvent event) , cette méthode possède le même comportement que la méthode  draw() , sauf que cette fois les coordonnées du point où s'effectue le dessin sont obtenues à l'endroit où l'interaction utilisateur est effectuée.

 
Sélectionnez
@Override
public void onTouchEvent(MotionEvent event) {
        float X = event.getX();
        float Y = event.getY();
        SurfaceHolder holder = getSurfaceHolder();
        Canvas canvas = null;
        try {
                canvas = holder.lockCanvas();
                if (canvas != null) {
                        canvas.drawColor(Color.WHITE);
                        drawImage(canvas, X, Y);
                }
        } finally {
                if (canvas != null)
                        holder.unlockCanvasAndPost(canvas);
        }
        handler.removeCallbacks(drawer);
        if (visible) {
                handler.postDelayed(drawer, 4000);
        }
        super.onTouchEvent(event);
}

Ce qui donnera :

 
Sélectionnez
import java.util.Random;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

public class MyLiveWallpaper extends WallpaperService {

        private static Random random =  new Random() ;

        @Override
        public Engine onCreateEngine() {
                return new LiveWallpaperEngine();
        }

        private class LiveWallpaperEngine extends Engine {

                private final Handler handler = new Handler();
                private final Runnable drawer = new Runnable() {
                        @Override
                        public void run() {
                                draw();
                        }
                };

                private boolean visible = true;
                private int width;
                private int height;
                Bitmap androidPic = BitmapFactory.decodeResource(getResources(), R.drawable.android);

                public LiveWallpaperEngine() {
                        handler.post(drawer);
                }

                @Override
            public void onCreate(SurfaceHolder surfaceHolder) {
                super.onCreate(surfaceHolder);
                setTouchEventsEnabled(true);
                }

            @Override
            public void onDestroy() {
                super.onDestroy();
                handler.removeCallbacks(drawer);
            }

                @Override
                public void onSurfaceCreated(SurfaceHolder holder) {
                        super.onSurfaceCreated(holder);
                }

                @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 onSurfaceDestroyed(SurfaceHolder holder) {
                        super.onSurfaceDestroyed(holder);
                        this.visible = false;
                        handler.removeCallbacks(drawer);
                }

                @Override
                public void onVisibilityChanged(boolean visible) {
                        this.visible = visible;
                        if (visible) {
                                handler.post(drawer);
                        } else {
                                handler.removeCallbacks(drawer);
                        }
                }

                @Override
                public void onTouchEvent(MotionEvent event) {
                        float X = event.getX();
                        float Y = event.getY();
                        SurfaceHolder holder = getSurfaceHolder();
                        Canvas canvas = null;
                        try {
                                canvas = holder.lockCanvas();
                                if (canvas != null) {
                                        canvas.drawColor(Color.WHITE);
                                        drawImage(canvas, X, Y);
                                }
                        } finally {
                                if (canvas != null)
                                        holder.unlockCanvasAndPost(canvas);
                        }
                        handler.removeCallbacks(drawer);
                        if (visible) {
                                handler.postDelayed(drawer, 4000);
                        }
                        super.onTouchEvent(event);
                }

                private void draw() {
                        SurfaceHolder holder = getSurfaceHolder();
                        Canvas canvas = null;
                        try {
                                canvas = holder.lockCanvas();
                                if (canvas != null) {
                                        float x = (width * random.nextFloat());
                                        float y = (height * random.nextFloat());
                                        drawImage(canvas, x, y);
                                }
                        } finally {
                                if (canvas != null)
                                        holder.unlockCanvasAndPost(canvas);
                        }
                        handler.removeCallbacks(drawer);
                        if (visible) {
                                //On reposte le runnable après un petit laps de temps
                                handler.postDelayed(drawer, 4000);
                        }
                }

                // Permet de dessiner l'image dans le canvas
                private void drawImage(Canvas canvas, float x, float y) {
                        canvas.drawColor(Color.WHITE);
                        canvas.drawBitmap(androidPic, x-(androidPic.getWidth()/2), y-(androidPic.getHeight()/2), null);
                }
        }
}

Dernière étape, on déclare notre service dans l'AndroidManifest.xml avec l'action« android.service.wallpaper.WallpaperService » et la permission « android.permission.BIND_WALLPAPER » qui autorise l'utilisation du Live Wallpaper.
Notons aussi l'ajout de la balise uses-feature , qui indique à Google Play que votre application contient un Live Wallpaper, pour que celle-ci ne soit visible qu'aux utilisateurs ayant un device supportant les Live Wallpapers.

 
Sélectionnez
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tuto.android"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" />
    <uses-feature android:name="android.software.live_wallpaper"></uses-feature>

    <application
        android:label="@string/app_name"
        android:icon="@drawable/ic_launcher" >
        
        <service 
            android:label="@string/my_live_wallpaper"
            android:name=".MyLiveWallpaper"
                        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>
    </application>

</manifest>

Sans oublier le String.xml :

 
Sélectionnez
<?xml version="1.0" encoding="UTF-8"?>
<resources>

    <string name="app_name">Live Wallpaper</string>
    <string name="my_live_wallpaper">My Live Wallpaper</string>
    <string name="wallpaper_description">My first Live Wallpaper</string> 

</resources>

III. Remarque :

Pour gérer les préférences de votre wallepaper, créez une  Pr efe renceActivity  qui permettra de définir les configurations de votre fond d'écran. La récupération des valeurs de vos préférences s'effectue à l'aide des SharedPreference . Pour cela, ajoutez les lignes suivantes à votre « wallpaper.xml »:

 
Sélectionnez
<android:settingsActivity="MyPreferenceActivity"/>

sans oublier de déclarer votre activité dans l'AndroidManifest.xml.

Lancez maintenant votre application, sélectionnez le Live Wallpaper que vous avez créé afin d'obtenir le résultat suivant :

Image non disponible

IV. Conclusion

Voilà, j'espère que cet article vous a permis de mieux comprendre comment fonctionnent les Live Wallpapers, le code du projet est disponible  ici .
Maintenant place à votre imagination pour créer vos propres fonds d'écran animés.

V. Remerciements

Je tiens à remercier tout particulièrement Feanorin qui a mis ce tutoriel au format Developpez.com.
Merci également à ClaudeLELOUP d'avoir pris le temps de le relire et de le corriger.

VI. Liens

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Benbourahla Nazim. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.