Click here to Skip to main content
15,867,453 members
Articles / Mobile Apps / Android

Create a Live Aquarium Wallpaper in Android

Rate me:
Please Sign up or sign in to vote.
4.97/5 (23 votes)
29 Oct 2011CPOL2 min read 84.7K   3   57   17
Android development - Aquarium Live wallpaper

(I have only tested this in the SDK simulator and haven’t considered all the screen sizes, so you may find some UI glitches)

image image

Few weeks ago, I started learning Android programming, so this article is an outcome of that outside 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 CodeProject 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

Let's get started….

Start by creating a 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:

Java
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.

Java
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:

Java
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(…):

Java
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 its look by extending from this class.

Java
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:

Java
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 simply extend a new class for a shark with that image.

Java
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 in some way or the other.

Happy new year!!!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
India India
I am someone who is passionate about programming. I started my career with classic asp and VB 6, later dived into the world of .NET. My ambition is to become a technical architect who could design complex systems in a simplistic form which obeys the "Laws of nature"

My personal blog

Comments and Discussions

 
QuestionTouch on fishes Pin
Member 1103624425-Aug-14 20:23
Member 1103624425-Aug-14 20:23 
Questionaquarium live wallpaper Pin
Abbas Yousaf14-Aug-14 3:29
Abbas Yousaf14-Aug-14 3:29 
QuestionThis LWP slows down games on Wildfire S (600MHz), whats wrong? Pin
Benelli1111123-Feb-12 2:42
Benelli1111123-Feb-12 2:42 
QuestionFor Putting Settings in Aquarium LiveWallpaper Pin
Solanki Ranvir22-Feb-12 20:17
Solanki Ranvir22-Feb-12 20:17 
QuestionFor this LWP Pin
Solanki Ranvir22-Feb-12 20:09
Solanki Ranvir22-Feb-12 20:09 
QuestionMy bote of 5 Pin
Yves Tkaczyk3-Nov-11 5:54
Yves Tkaczyk3-Nov-11 5:54 
GeneralMy vote of 5 Pin
Gabriel RB18-Oct-11 3:31
Gabriel RB18-Oct-11 3:31 
GeneralMy vote of 5 Pin
Paresh N. Mayani7-Sep-11 20:34
Paresh N. Mayani7-Sep-11 20:34 
GeneralMy vote of 5 Pin
rohit_love18-Aug-11 5:11
rohit_love18-Aug-11 5:11 
GeneralMy vote of 5 Pin
jubayar26-Jun-11 22:05
jubayar26-Jun-11 22:05 
GeneralMy vote of 5 Pin
Patrick Kalkman21-Apr-11 9:22
Patrick Kalkman21-Apr-11 9:22 
GeneralRe: My vote of 5 Pin
Ed Korsberg3-Nov-11 5:57
Ed Korsberg3-Nov-11 5:57 
GeneralMy vote of 5 Pin
wopuma19-Apr-11 22:48
wopuma19-Apr-11 22:48 
GeneralThank you and a question Pin
GeorgeFreeman8-Mar-11 17:28
GeorgeFreeman8-Mar-11 17:28 
GeneralMy vote of 5 Pin
SledgeHammer0131-Dec-10 13:11
SledgeHammer0131-Dec-10 13:11 
Generalnice one Pin
Pranay Rana30-Dec-10 16:37
professionalPranay Rana30-Dec-10 16:37 
GeneralRe: nice one Pin
Rajeesh.C.V30-Dec-10 17:55
Rajeesh.C.V30-Dec-10 17:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.