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 :
<?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.
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 :
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() :
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 :
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 :
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.
@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 :
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.
<?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 :
<?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 »:
<
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 :
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.