Using Andengine to create an Android Live Wallpaper

Aug
2011
02

Android, Programming

No comments

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