Using Andengine to create an Android Live Wallpaper
2011
So, I got one of those wonderful Asus Transformers…I had to see if I could make something for it. And so I did! Having some Android experience from the 1.x days, I started looking around for a quick way to get something done, and it was then that I found the amazing AndEngine and its Live Wallpaper Extension . While the documentation is sparse (specially when it comes down to the newer changes in the engine), there’s a big enough community that if you search around in the forums and over the net you will find what you are looking for. Getting it all in place turned out to be quite simple once I figured out what changes needed to be done to adapt the provided example to the latest modifications done to the engine.
All in all, it’s pretty simple:
(LiveWallpaperService.java)
package org.anddev.wallpaper.live.donprimerizowp; import java.io.File; import java.util.Random; import net.rbgrn.opengl.GLWallpaperService.GLEngine; import org.anddev.andengine.engine.camera.Camera; import org.anddev.andengine.engine.handler.timer.ITimerCallback; import org.anddev.andengine.engine.handler.timer.TimerHandler; import org.anddev.andengine.engine.options.EngineOptions; import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation; import org.anddev.andengine.engine.options.resolutionpolicy.FillResolutionPolicy; import org.anddev.andengine.entity.particle.ParticleSystem; import org.anddev.andengine.entity.particle.modifier.AlphaModifier; import org.anddev.andengine.entity.particle.modifier.ExpireModifier; import org.anddev.andengine.entity.scene.Scene; import org.anddev.andengine.entity.sprite.AnimatedSprite.IAnimationListener; import org.anddev.andengine.entity.sprite.Sprite; import org.anddev.andengine.entity.sprite.AnimatedSprite; import org.anddev.andengine.extension.ui.livewallpaper.BaseLiveWallpaperService; import org.anddev.andengine.opengl.texture.Texture; import org.anddev.andengine.opengl.texture.TextureOptions; import org.anddev.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas; import org.anddev.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory; import org.anddev.andengine.opengl.texture.region.TextureRegion; import org.anddev.andengine.opengl.texture.region.TextureRegionFactory; import org.anddev.andengine.opengl.texture.region.TiledTextureRegion; import org.anddev.andengine.opengl.view.GLSurfaceView.Renderer; import org.anddev.andengine.opengl.view.RenderSurfaceView; import org.anddev.andengine.sensor.accelerometer.AccelerometerData; import org.anddev.andengine.sensor.accelerometer.IAccelerometerListener; import org.anddev.andengine.sensor.orientation.IOrientationListener; import org.anddev.andengine.sensor.orientation.OrientationSensorOptions; import android.app.WallpaperManager; import android.content.res.Configuration; import android.os.Bundle; public class LiveWallpaperService extends BaseLiveWallpaperService implements IAccelerometerListener, IOffsetsChanged { protected class MyBaseWallpaperGLEngine extends GLEngine { // =========================================================== // Fields // =========================================================== private Renderer mRenderer; private IOffsetsChanged mOffsetsChangedListener = null; // =========================================================== // Constructors // =========================================================== public MyBaseWallpaperGLEngine(IOffsetsChanged pOffsetsChangedListener) { this.setEGLConfigChooser(false); this.mRenderer = new RenderSurfaceView.Renderer(LiveWallpaperService.this.mEngine); this.setRenderer(this.mRenderer); this.setRenderMode(RENDERMODE_CONTINUOUSLY); this.mOffsetsChangedListener = pOffsetsChangedListener; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public Bundle onCommand(final String pAction, final int pX, final int pY, final int pZ, final Bundle pExtras, final boolean pResultRequested) { if(pAction.equals(WallpaperManager.COMMAND_TAP)) { LiveWallpaperService.this.onTap(pX, pY); } else if (pAction.equals(WallpaperManager.COMMAND_DROP)) { LiveWallpaperService.this.onDrop(pX, pY); } return super.onCommand(pAction, pX, pY, pZ, pExtras, pResultRequested); } @Override public void onResume() { super.onResume(); LiveWallpaperService.this.getEngine().onResume(); LiveWallpaperService.this.onResume(); } @Override public void onPause() { super.onPause(); LiveWallpaperService.this.getEngine().onPause(); LiveWallpaperService.this.onPause(); } @Override public void onDestroy() { super.onDestroy(); if (this.mRenderer != null) { // mRenderer.release(); } this.mRenderer = null; } @Override public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) { // TODO Auto-generated method stub super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset); if(this.mOffsetsChangedListener != null) this.mOffsetsChangedListener.offsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset); } } // =========================================================== // Constants // =========================================================== private static final int CAMERA_WIDTH = 1280; private static final int CAMERA_HEIGHT = 800; // =========================================================== // Fields // =========================================================== private BitmapTextureAtlas mTexture; private BitmapTextureAtlas mTexture2; private BitmapTextureAtlas mTexture3; private BitmapTextureAtlas mTexture4; private BitmapTextureAtlas mTexture5; private TextureRegion mPulperia; private TextureRegion mCarreta; private TiledTextureRegion mPibito; private TiledTextureRegion mMosca; private TiledTextureRegion mDPSeq1; private TiledTextureRegion mDPSeq2; private Sprite mPulperiaSprite; private AnimatedSprite mPibitoSprite; private Sprite mCarretaSprite; private AnimatedSprite mMoscaSprite; private AnimatedSprite mDP1Sprite; private AnimatedSprite mDP2Sprite; private ScreenOrientation mScreenOrientation; private Camera mCamera; private Scene mScene; private IAnimationListener mDP1ListenerF, mDP1ListenerB, mDP2Listener; // =========================================================== // Constructors // =========================================================== // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public org.anddev.andengine.engine.Engine onLoadEngine() { mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT); return new org.anddev.andengine.engine.Engine(new EngineOptions(true, this.mScreenOrientation, new FillResolutionPolicy(), mCamera)); } @Override public void onLoadResources() { this.getEngine().disableOrientationSensor(this); this.mTexture = new BitmapTextureAtlas(2048, 2048, TextureOptions.BILINEAR); this.mTexture2 = new BitmapTextureAtlas(2048, 2048, TextureOptions.BILINEAR); this.mTexture3 = new BitmapTextureAtlas(2048, 2048, TextureOptions.BILINEAR); this.mTexture4 = new BitmapTextureAtlas(1024, 1024, TextureOptions.BILINEAR); this.mTexture5 = new BitmapTextureAtlas(1024, 2048, TextureOptions.BILINEAR); /* Creates the needed texture-regions on the texture. */ this.mPulperia = BitmapTextureAtlasTextureRegionFactory.createFromAsset(this.mTexture, this, "gfx/pulperia.png", 0, 0); // 1280x800 this.mCarreta = BitmapTextureAtlasTextureRegionFactory.createFromAsset(this.mTexture, this, "gfx/carreta.png", 0, 801); // 263x386 this.mPibito = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(this.mTexture2, this, "gfx/pibito.png", 0, 0, 6, 6); // 2048x1980 this.mMosca = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(this.mTexture3, this, "gfx/mosca.png", 0, 0, 11, 5); // 1980x1070 this.mDPSeq1 = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(this.mTexture4, this, "gfx/dpseq1.png", 0, 0, 7, 4); // 1022x1000 this.mDPSeq2 = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(this.mTexture5, this, "gfx/dpseq2.png", 0, 0, 10, 7); // 990x1393 this.getEngine().getTextureManager().loadTexture(this.mTexture); this.getEngine().getTextureManager().loadTexture(this.mTexture2); this.getEngine().getTextureManager().loadTexture(this.mTexture3); this.getEngine().getTextureManager().loadTexture(this.mTexture4); this.getEngine().getTextureManager().loadTexture(this.mTexture5); this.enableAccelerometerSensor(this); } @Override public Scene onLoadScene() { mScene = new Scene(); mPulperiaSprite = new Sprite(0, 0, this.mPulperia); mScene.attachChild(mPulperiaSprite); mPibitoSprite = new AnimatedSprite(88, 200, this.mPibito); mPibitoSprite.setScale((float) 1.6); mMoscaSprite = new AnimatedSprite(700, 550, this.mMosca); mDP1Sprite = new AnimatedSprite(530, 260, this.mDPSeq1); mDP2Sprite = new AnimatedSprite(530, 310, this.mDPSeq2); mDP1ListenerF = new IAnimationListener () { @Override public void onAnimationEnd(final AnimatedSprite pAnimatedSprite) { runOnUpdateThread(new Runnable() { @Override public void run() { // Stop the animation, play it backwards mDP1Sprite.stopAnimation(); mDP1Sprite.animate( new long[] {100,100,100,100,100,100,100, 100,100,100,100,100,100,100, 100,100,100,100,100,100,100, 100,100,100,100,100,100,100,}, new int[] {27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}, 0, mDP1ListenerB); } }); } }; mDP1ListenerB = new IAnimationListener () { @Override public void onAnimationEnd(final AnimatedSprite pAnimatedSprite) { runOnUpdateThread(new Runnable() { @Override public void run() { mScene.detachChild(mDP1Sprite); mDP1Sprite.stopAnimation(); mDP2Sprite.animate(100, false, mDP2Listener); mScene.attachChild(mDP2Sprite); } }); } }; mDP2Listener = new IAnimationListener () { @Override public void onAnimationEnd(final AnimatedSprite pAnimatedSprite) { runOnUpdateThread(new Runnable() { @Override public void run() { mScene.detachChild(mDP2Sprite); mDP2Sprite.stopAnimation(); mDP1Sprite.animate(100, false, mDP1ListenerF); mScene.attachChild(mDP1Sprite); } }); } }; mDP1Sprite.animate(100, false, mDP1ListenerF); mScene.attachChild(mDP1Sprite); mCarretaSprite = new Sprite(-30, 414, this.mCarreta); mScene.attachChild(mCarretaSprite); //this.mVelocityInitializer = new VelocityInitializer(-20, 20, -100, -120); // this.getEngine().registerPreFrameHandler(new FPSCounter()); mScene.registerUpdateHandler(new TimerHandler(10f, true, new ITimerCallback() { Random randomSrc = new Random(); @Override public void onTimePassed(final TimerHandler pTimerHandler) { try { int x = randomSrc.nextInt(10); //Pibito runs 3 out of 10 times if (x5 && ! mMoscaSprite.isAnimationRunning()) { mScene.attachChild(mMoscaSprite); mMoscaSprite.animate(100, false, new IAnimationListener () { @Override public void onAnimationEnd(final AnimatedSprite pAnimatedSprite) { runOnUpdateThread(new Runnable() { @Override public void run() { mScene.detachChild(mMoscaSprite); } }); } }); } } catch (Exception e) {} } })); return mScene; } @Override public void onLoadComplete() { } @Override public Engine onCreateEngine() { // TODO Auto-generated method stub return new MyBaseWallpaperGLEngine(this); } @Override public void offsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) { /* if(mCamera != null){ // Emulator has 3 screens mCamera.setCenter( ((960 * xOffset ) - 240) , mCamera.getCenterY() ); / *formel mCamera.setCenter(( (Camera-WIDTH * (screensCount-1)) * xOffset ) - (Camera-WIDTH / 2) ,mCamera.getCenterY() ); * / }*/ mCarretaSprite.setPosition(-xOffset*80, 414); mMoscaSprite.setPosition(-xOffset*300+700, 550); } @Override public void onAccelerometerChanged(final AccelerometerData pAccelerometerData) { /* final float minVelocityX = (pAccelerometerData.getX() + 2) * 5; final float maxVelocityX = (pAccelerometerData.getX() - 2) * 5; final float minVelocityY = (pAccelerometerData.getY() - 8) * 10; final float maxVelocityY = (pAccelerometerData.getY() - 10) * 10; this.mVelocityInitializer.setVelocity(minVelocityX, maxVelocityX, minVelocityY, maxVelocityY);*/ } @Override public void onUnloadResources() { // TODO Auto-generated method stub } @Override public void onPauseGame() { super.onPause(); LiveWallpaperService.this.getEngine().onPause(); LiveWallpaperService.this.onPause(); } @Override public void onResumeGame() { super.onResume(); LiveWallpaperService.this.getEngine().onResume(); LiveWallpaperService.this.onResume(); } @Override public void onConfigurationChanged (Configuration newConfig){ if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { mScene.setScaleX(1280.0f/800.0f); mScene.setScaleY(1.0f); } else if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { mScene.setScale(1); } } // =========================================================== // Methods // =========================================================== // =========================================================== // Inner and Anonymous Classes // =========================================================== }
(IOffsetsChanged.java)
package org.anddev.wallpaper.live.donprimerizowp; public interface IOffsetsChanged{ public void offsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset); }
I’m making the full project available here . The Live Wallpaper is available from the Android Market.
The license for the code is “do as you please”, for the art it’s “All rights reserved under Creative Commons License”. All art is from the defunct (oh really? I hear him breathing!) “Epopeya de Don Primerizo Lata” and was created by the great Leonardo Falaschini, you should check his stuff at liondart.com