Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating and rotating an image in a Surface View

Tags:

android

I would like to animate movement on a SurfaceView . Ideally I would like to also be notified once the animation had finished.

For example: I might have a car facing North. If I wanted to animate it so that it faces South for a duration of 500ms, how could I do that?

I am using a SurfaceView so all animation must be handled manually, I don't think I can use XML or the android Animator classes.

Also, I would like to know the best way to animate something continuously inside a SurfaceView (ie. a walk cycle)

like image 828
jax Avatar asked Mar 13 '10 17:03

jax


2 Answers

Rotating images manually can be a bit of a pain, but here's how I've done it.

private void animateRotation(int degrees, float durationOfAnimation){
    long startTime = SystemClock.elapsedRealtime();
    long currentTime;
    float elapsedRatio = 0;
    Bitmap bufferBitmap = carBitmap;

    Matrix matrix = new Matrix();

    while (elapsedRatio < 1){
        matrix.setRotate(elapsedRatio * degrees);
        carBitmap = Bitmap.createBitmap(bufferBitmap, 0, 0, width, height, matrix, true);
        //draw your canvas here using whatever method you've defined
        currentTime = SystemClock.elapsedRealtime();
        elapsedRatio = (currentTime - startTime) / durationOfAnimation;
    }

    // As elapsed ratio will never exactly equal 1, you have to manually draw the last frame
    matrix = new Matrix();
    matrix.setRotate(degrees);
    carBitmap = Bitmap.createBitmap(bufferBitmap, 0, 0, width, height, matrix, true);
    // draw the canvas again here as before
    // And you can now set whatever other notification or action you wanted to do at the end of your animation

}

This will rotate your carBitmap to whatever angle you specify in the time specified + the time to draw the last frame. However, there is a catch. This rotates your carBitmap without adjusting its position on screen properly. Depending on how you're drawing your bitmaps, you could end up with your carBitmap rotating while the top-left corner of the bitmap stays in place. As the car rotates, the bitmap will stretch and adjust to fit the new car size, filling the gaps around it with transparent pixels. It's hard to describe how this would look, so here's an example rotating a square:

alt text

The grey area represents the full size of the bitmap, and is filled with transparent pixels. To solve this problem, you need to use trigonometry. It's a bit complicated... if this ends up being a problem for you (I don't know how you're drawing your bitmaps to the canvas so it might not be), and you can't work out the solution, let me know and I'll post up how I did it.

(I don't know if this is the most efficient way of doing it, but it works smoothly for me as long as the bitmap is less than 300x300 or so. Perhaps if someone knows of a better way, they could tell us!)

like image 85
Steve Haley Avatar answered Nov 15 '22 21:11

Steve Haley


Do you want multiple independent animated object? If so, then you should use a game loop. (One master while loop that incrementally updates all game objects.) Here's a good discussion on various loop implementations. (I'm currently using "FPS dependent on Constant Game Speed" for my Android game project.)

So then your Car will look something like this (lots of code missing):

class Car {
    final Matrix transform = new Matrix();
    final Bitmap image;

    Car(Bitmap sprite) {
        image = sprite;  // Created by BitmapFactory.decodeResource in SurfaceView
    }
    void update() {
        this.transform.preRotate(turnDegrees, width, height);
    }
    void display(Canvas canvas) {
        canvas.drawBitmap(this.image, this.transform, null);
    }
}

You only need to load your bitmap once. So if you have multiple Cars, you may want to give them each the same Bitmap object (cache the Bitmap in your SurfaceView).

I haven't got into walk animations yet, but the simplest solution is to have multiple Bitmaps and just draw a different bitmap each time display is called.

Have a look at lunarlander.LunarView in the Android docs if you haven't already.


If you want to be notified when the animation is complete, you should make a callback.

interface CompletedTurnCallback {
    void turnCompleted(Car turningCar);
}

Have your logic class implement the callback and have your Car call it when the turn's complete (in update()). Note that you'll get a ConcurrentModificationException if you are iterating over a list of Cars in update_game() and you try to remove a Car from that list in your callback. (You can solve this with a command queue.)

like image 36
idbrii Avatar answered Nov 15 '22 19:11

idbrii