Rajeeshcv.com

Sharing my knowledge

Create a live aquarium wallpaper in Android

image image

Download the source code: http://www.rajeeshcv.com/download/LiveAquariumWallpaper.zip Android package: http://www.rajeeshcv.com/download/LiveAquariumWallpaper.apk (I have only tested this in the SDK simulator and haven’t considered all the screen sizes, so may find some UI glitches)
Few weeks ago I started learning Android programming , so this article is an outcome of that out-side office study :). Here I will be explaining – how to create a live wallpaper which looks like an aquarium with fishes swimming across the screen. The fish animation is done using sprite technique. Courtesy :
  1. Fish sprite used here is from a code project article - http://www.codeproject.com/KB/GDI-plus/LovelyGoldFishDeskPet.aspx
  2. Creating animation using sprites - http://www.droidnova.com/2d-sprite-animation-in-android,471.html
Lets get started…. Starts by creating new Android project in eclipse (I am not familiar with any other IDEs for Android development :) ). Now create a class for your live wallpaper service, I called it as AquariumWallpaperService, then instantiate the AquariumWallpaperEngine. This engine is responsible for creating the actual Aquarium class which does all the rendering logic. It also controls the flow of Aquarium based Surface callbacks Below is the code for  AquariumWallpaperService
public class AquariumWallpaperService extends WallpaperService {

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

    class AquariumWallpaperEngine extends Engine{    

        private Aquarium _aquarium;

        public AquariumWallpaperEngine() {
            this._aquarium = new Aquarium();
            this._aquarium.initialize(getBaseContext(), getSurfaceHolder());
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            if(visible){
                this._aquarium.render();
            }
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format,
                int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);
        }

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

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            this._aquarium.stop();
        }
    }
}
Aquarium class wraps all the rendering logic, as well as creating the fishes. This also starts a thread which is responsible for updating the view.
public class Aquarium {

    private AquariumThread _aquariumThread;
    private SurfaceHolder _surfaceHolder;
    private ArrayList<Renderable> _fishes;
    private Bitmap _backgroundImage;
    private Context _context;

    public void render(){
        Canvas canvas = null;
        try{

            canvas = this._surfaceHolder.lockCanvas(null);
            synchronized (this._surfaceHolder) {
                this.onDraw(canvas);
            }

        }finally{
            if(canvas != null){
                this._surfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }

    protected void onDraw(Canvas canvas) {
        this.renderBackGround(canvas);
        for (Renderable renderable : this._fishes) {
            renderable.render(canvas);
        }
    };

    public void start(){
        this._aquariumThread.switchOn();
    }

    public void stop(){
        boolean retry = true;
        this._aquariumThread.switchOff();
        while (retry) {
            try {
                this._aquariumThread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }

    public int getLeft() {
        return 0;
    }

    public int getRight() {
        return this._backgroundImage.getWidth();
    }

    public void initialize(Context context, SurfaceHolder surfaceHolder) {
        this._aquariumThread = new AquariumThread(this);
        this._surfaceHolder = surfaceHolder;
        this._fishes = new ArrayList<Renderable>();
        this._context = context;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPurgeable = true;
        this._backgroundImage = BitmapFactory.decodeResource(context.getResources(), com.plugai.android.livewallpapers.R.drawable.aquarium, options);
        this.addFishes();
    }

    private void addFishes() {
        Point startPoint = new Point(100, 100);
        this._fishes.add(new ClownFish(this._context, this, startPoint, 90));
        Point startPoint1 = new Point(100, 300);
        this._fishes.add(new ClownFish(this._context, this, startPoint1, 50));

        Point startPoint2 = new Point(200, 200);
        this._fishes.add(new ClownFish(this._context, this, startPoint2, 15));
    }

    private void renderBackGround(Canvas canvas)
    {
        canvas.drawBitmap(this._backgroundImage, 0, 0, null);
    }
}
Here is the code for AquariumThread class
public class AquariumThread extends Thread {

    private Aquarium _aquarium;
    private boolean _running;

    public AquariumThread(Aquarium aquarium) {
        this._aquarium = aquarium;
    }

    public void switchOn(){
        this._running = true;
        this.start();
    }

    public void pause(){
        this._running = false;
        synchronized(this){
            this.notify();
        }
    }

    public void switchOff(){
        this._running = false;
        synchronized(this){
            this.notify();
        }
    }

    @Override
    public void run() {
        while(this._running){
            this._aquarium.render();
        }
    }
}
All the renderable object in the aquarium must implement an interface Renderable, which has got one single method called render(…)
package com.plugai.android.livewallpapers;

import android.graphics.Canvas;

public interface Renderable {
    void render(Canvas canvas);
}
This interface helps to render an object other than a fish, like plants etc... in future. I have created another abstract class which has common functionalities like changing the position and direction of a fish after a particular interval and called it as AquaticAnimal, this is because I could create specific fishes which just differ by it’s look by extending from this class.
public abstract class AquaticAnimal implements Renderable {

    private static int MAX_SPEED = 100;
    private Context _context;
    private Aquarium _aquarium;
    private FishSprite _leftSprite;
    private FishSprite _rightSprite;

    private int _direction = -1;
    private int _speedFraction;
    private long _previousTime;

    public AquaticAnimal(Context context, Aquarium aquarium){
        this._context = context;
        this._aquarium = aquarium;
    }

    protected void initialize(Bitmap leftBitmap, Bitmap rightBitmap, int fps, int totalFrames, Point startPoint, int speed){
        this._leftSprite = new FishSprite(leftBitmap, fps, totalFrames, startPoint);
        this._rightSprite = new FishSprite(rightBitmap, fps, totalFrames, startPoint);
        this._speedFraction = (MAX_SPEED / speed) * 10;
    }

    private FishSprite getSprite(){
        if(this._direction < 0){
            return this._leftSprite;
        }
        return this._rightSprite;
    }

    public int getDirection(){
        FishSprite sprite = this.getSprite();
        int xPos = sprite.getXPos();
        if(this._direction < 0){
            xPos += sprite.getWidth();
        }
        if(xPos < this._aquarium.getLeft()){
            this._direction = 1;
        }else if(xPos > this._aquarium.getRight()){
            this._direction = -1;
        }else{
            // Do nothing
        }

        return this._direction;
    }

    public Context getContext(){
        return this._context;
    }

    public Aquarium getAquarium(){
        return this._aquarium;
    }

    @Override
    public void render(Canvas canvas){
        long currentTime = System.currentTimeMillis();
        this.getSprite().render(canvas, currentTime);
        this.swim(currentTime);
    }

    public void swim(long currentTime){
        long diff = currentTime - this._previousTime;
        if(diff > this._speedFraction){
            int currentX = this.getSprite().getXPos();
            this.getSprite().setXPos(currentX + this.getDirection());
            this._previousTime = currentTime;
        }
    }

}
The sprite animation is moved into a specific class FishSprite
public class FishSprite {

    /**
     * Private fields
     */
    private Bitmap _currentSpriteBitmap;
    private Rect _drawRect;
    private int _fps;
    private int _noOfFrames;
    private int _currentFrame;
    private long _timer;
    private int _spriteWidth;
    private int _spriteHeight;
    private Point _position;

    public FishSprite(Bitmap spriteBitmap, int fps, int frameCount, Point startPoint) {

        this.initialize();        

        this._position = startPoint;
        this._currentSpriteBitmap = spriteBitmap;
        this._spriteHeight = spriteBitmap.getHeight();
        this._spriteWidth = spriteBitmap.getWidth() / frameCount;
        this._drawRect = new Rect(0,0, this._spriteWidth, this._spriteHeight);
        this._fps = 1000 / fps;
        this._noOfFrames = frameCount;
    }

    private void initialize() {
        this._drawRect = new Rect(0,0,0,0);
        this._timer = 0;
        this._currentFrame = 0;
    }

    private void Update(long currentTime) {
        if(currentTime > this._timer + this._fps ) {
            this._timer = currentTime;
            this._currentFrame +=1;

            if(this._currentFrame >= this._noOfFrames) {
                this._currentFrame = 0;
            }
        }

        this._drawRect.left = this._currentFrame * this._spriteWidth;
        this._drawRect.right = this._drawRect.left + this._spriteWidth;
    }

    public void render(Canvas canvas, long currentTime) {

        this.Update(currentTime);

        Rect dest = new Rect(getXPos(), getYPos(), getXPos() + this._spriteWidth,
                        getYPos() + this._spriteHeight);

        canvas.drawBitmap(this._currentSpriteBitmap, this._drawRect, dest, null);
    }

    public Point getPosition() {
        return _position;
    }

    public void setPosition(Point position) {
        this._position = position;
    }

    public int getYPos() {
        return this._position.y;
    }

    public int getXPos() {
        return this._position.x;
    }

    public void setYPos(int y) {
        this._position.y = y;
    }

    public void setXPos(int x) {
        this._position.x = x;
    }

    public int getWidth(){
        return this._spriteWidth;
    }

    public int getHeight(){
        return this._spriteHeight;
    }

}
Now to the final bit, that is creating a fish. Someone might have noticed that I have created an object of ClownFish in the Aquarium class. ClownFish is just a derived class from AquaticAnimal, with a specific sprite image. So if you have sprite images for a shark, you could simple extend a new class for a shark with that image.
public class ClownFish extends AquaticAnimal {
    private static final int TOTAL_FRAMES_IN_SPRITE = 20;
    private static final int CLOWN_FISH_FPS = 20; 

    public ClownFish(Context context, Aquarium aquarium,  Point startPoint, int speed){
        super(context, aquarium);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPurgeable = true;
        Bitmap leftBitmap = BitmapFactory.decodeResource(getContext().getResources(), com.plugai.android.livewallpapers.R.drawable.left, options);
        BitmapFactory.Options options1 = new BitmapFactory.Options();
        options1.inPurgeable = true;
        Bitmap rightBitmap = BitmapFactory.decodeResource(getContext().getResources(), com.plugai.android.livewallpapers.R.drawable.right, options1);
        this.initialize(leftBitmap, rightBitmap, CLOWN_FISH_FPS, TOTAL_FRAMES_IN_SPRITE, startPoint, speed);
    }

    public void render(Canvas canvas){
        super.render(canvas);
    }
}
Please feel free to go through the code, If I haven’t explained clearly. Hope this article helped some way or other. Happy new year!!!

Comments

GuruMoorthy Arumugam
GuruMoorthy Arumugam commented
hai man nice tutorial please tell me .if possible to add view in android wallpaper .i want to play some video in live wallpaper.if it is possible ? thank u in advance
BIPIN
BIPIN commented
Hello r u there???
BIPIN
BIPIN commented
i tried i hv imorted ur zip in eclipse but every time i run i get it done soon when i clik on home button it doesnt show ur app with name aquarium i am using 2.3..3 emulator help is needed over here
k.Ashok Kumar
k.Ashok Kumar commented
hai... sir I am working on android technology .But this technology is new to me. My project is to develop the live wallpapers like aquarium wallpaper done by you. I need to know how to add more fishes and backgrounds and bobbles etc.. . Will please just give me the few examples. Regarding that i will follow my project. Present i am using your script. We are all waiting for your reply. So,please give us a reply as early as possible. Thanks and Regards K.Ashok kumar & Team
cyberfisher
cyberfisher commented
rajeesh! omfg! my seahhorse is anorexic! omFFG! What do i do? no matter how much I feed them, he just won't eat please help --fishing for answers
shawn
shawn commented
scroll down to setWallpaperOffsetSteps , i just cant figure this out
shawn
shawn commented
i commented out this.getSprite().setXPos(currentX + this.getDirection()); line and it does stop the movement, but for all sprites. i am trying to add animated seaweed, bubbles ect... but do not want the sprites to move, just animate. also is there a way to add movement other than left and right? up and down? diagonal? i am trying to get the background to scroll. it looks like you need to do something with onOffsetsChanged, but i cant get it to work. here is my reading http://developer.android.com/reference/android/app/WallpaperManager.html#setWallpaperOffsets(android.os.IBinder, float, float) thank you for all your help so far :)
vl
vl commented
I use Aquarium Live Wallpaper a great Android app that displays various nice fish on my home screen. http://bstdownload.com/reviews/aquarium-live-wallpaper-2/
Rajeesh
Rajeesh commented
@shawn - speed variable actually controls the refresh rate of rendering which in turn controls the speed in which the fish swims. But if you want the fish to stay in one position, don't update the X position of fish sprite i.e you could comment out line this.getSprite().setXPos(currentX + this.getDirection()); in swim method of AquaticAnimal class. I haven't tried this though, but seems like it should work. Please let me know if it doesn't. Answer to your second question - Theoretically possible if you have different sprite images, but you need to dispose sprites after use otherwise there could be some performance issues if you have many sprites loaded into memory. Answer to your third question - I haven't done this, but could be doable if you you capture the gesture events change the background positions. Please check this link to see how to detect the slide gesture http://stackoverflow.com/questions/3005643/which-event-is-used-to-slide-finger-left-and-right-ward-in-android
shawn
shawn commented
maybe one more question :) how can you make the background scroll when you slide left or right?
shawn
shawn commented
two quick questions for you. can you set the speed to 0 so the animation playes but the sprite doesnt move? i tried and it seems to force close. the big one i am wondering. can you change the sprite to another sprite at a specified time? like you have a small fish, then after 1 week it "grows" into a bigger fish, by changing to a diferent sprite, then after another week it changes again? any help would be greatly appreciated! thank you Rajeesh!!!
B. Harris
B. Harris commented
This may be a simple question but I can't figure it out. How do you employ a frame delay on the last frame of the sprite sheet before the loop starts over? For example, you decide to put an octopus into the aquarium. He moves from one side to the other by jumping up and landing again. But you don't want him to jump continuously. You want him to jump and land then pause in place for 5 seconds then jump again. How can this be accomplished? Thanks!
John
John commented
Thank you so much for your example. Very thorough.
Saurabh Sachdeva
Saurabh Sachdeva commented
Hi Rajeesh, I am going to start learning the Android. Please suggest me any of book or any tutorial that help me.
Rajeesh
Rajeesh commented
@Rizacan - Yes, the attached source code was working in the emulator with eclipse. I am not quite why it is not working for you in emulator. Are you able to run any other sample application(not a live wallpaper app)? In the case of your Andrdoid phone - please check whether it has support for live wallpaper, some android phone doesn't have this feature
Rizacan
Rizacan commented
Hey, I have a problem .. Till days i try to do a live wallpaper i mean i try to learn it from tutorials..but no tutorial is working on my emulator also in my 2.2 android phone.. What could be the problem ? your source code are working automaticly when you put them eclipse yes?
Sujith PV
Sujith PV commented
Hey dude..keep rockin..good start on Android :-)
Sam
Sam commented
Great tutorial Ive been searching for one all over the net that uses sprites and images. Thanks again Its been a big help :)
Ajai
Ajai commented
Good Starting!!!!!! Hoping more articles from you
Floris
Floris commented
Hi Rajeesh, Yes I have seen this sites, thanks by the way! Can you please make a example source in eclipse for me with a .gif extension? If you can make that for me, I will promote your website in my city ''Amsterdam'' with flyers : ) I can do this because i am a interactive designer, and I make Flyers for business companies! I hope you can help out with this Best Regards, Florisdesigns
Rajeesh
Rajeesh commented
Thanks Floris. Yes, there are many tutorials out there, I liked few from the official android website itself. 1. http://developer.android.com/resources/articles/live-wallpapers.html 2. http://developer.android.com/guide/topics/resources/drawable-resource.html 3. http://developer.android.com/guide/practices/screens_support.html Hope this helps. Thanks, Rajeesh
Floris
Floris commented
Great Tutorial!! Do you have a example of a simple live wallpaper, where you can add .png's/.gif ? I hope you can help my... i have searching the whole web, for this example.. Greats from the Netherlands! Floris van den Oever
square
square commented

cool source to try

i couldn't get project to run in eclipse

but the code was useful!

sancho
sancho commented
great tutorial, i don know where to add on touch event and the bubbles in it..????
Farhan
Farhan commented

Hi, and thanx for such a nice tutorial. I just wanted to admire/appreciate as you said you started android development some weeks before, and you produce this.. Man i've been doing for 1 year and i dont think i can produce some thing like this. so may be if you write something about how you learn to be able to do something like this would be very very highly appreciated. you can also send a personal message to me if u like. Very much thanx again. Please do write.. :)

Rajan
Rajan commented

anybody please send me the android manifest file of this project...@rajan1189@gmail.com

Rajeesh
Rajeesh commented

@rajan - please download the source code(zip package), link is on the top of this article.

TD
TD commented

Rajeesh - The XML files seem to be emtpy in the zip file. Like andriodManifest, res . etc

jeetendra kumar
jeetendra kumar commented

Hi, I have download your zip file of live wall paper and import your file but your code is not display whereas i also run on emulator 2.3. please suggest me....

krupal
krupal commented

Hi... A niice example. It did help me with my live wallpaper example. I just have one question. Why is the fish not swimming properly on m device. Its like it swims with jerks. why is it so?

Rajeesh
Rajeesh commented

@Krupal It is has been a long time, I have looked into this code myself. I feel your issue might be due to the frame rate, I think I have fixed to 20 FPS. You may have to find the optimal FPS for your device.

Krupal
Krupal commented

HI. Thanks for your prompt reply. But as I am making my live wallpaper for multiple devices do you know any such way how to achieve FPS or can u suggest me any other alternative for the same.

Leave your comments