如何在画布内使用动画框架?

Posted

技术标签:

【中文标题】如何在画布内使用动画框架?【英文标题】:How can I use the animation framework inside the canvas? 【发布时间】:2011-06-23 18:06:40 【问题描述】:

我想在画布内使用某些动画,例如弹跳。里面可以使用动画插值器吗?

在我的情况下,我想将图像从 0 度旋转到 180 度,并在最后反弹。

这怎么可能?

【问题讨论】:

【参考方案1】:

android 动画类适用于视图和布局等对象。画布只是一个用于绘图的表面,它要么是视图的一部分,要么链接到位图。在自定义视图的 onDraw 中,只有一帧被绘制,直到调用下一个无效,这意味着您必须逐帧绘制动画。这是一个旋转的弹跳球的示例,您可能会发现它很有用。

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Bundle;
import android.text.format.Time;
import android.view.View;

public class StartActivity extends Activity 

    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(new BallBounce(this));
    



class BallBounce extends View 
    int screenW;
    int screenH;
    int X;
    int Y;
    int initialY ;
    int ballW;
    int ballH;
    int angle;
    float dY;
    float acc;
    Bitmap ball, bgr;

    public BallBounce(Context context) 
        super(context);
        ball = BitmapFactory.decodeResource(getResources(),R.drawable.football); //load a ball image
        bgr = BitmapFactory.decodeResource(getResources(),R.drawable.sky_bgr); //load a background
        ballW = ball.getWidth();
        ballH = ball.getHeight();
        acc = 0.2f; //acceleration
        dY = 0; //vertical speed
        initialY = 100; //Initial vertical position.
        angle = 0; //Start value for rotation angle.
    

    @Override
    public void onSizeChanged (int w, int h, int oldw, int oldh) 
        super.onSizeChanged(w, h, oldw, oldh);
        screenW = w;
        screenH = h;
        bgr = Bitmap.createScaledBitmap(bgr, w, h, true); //Resize background to fit the screen.
        X = (int) (screenW /2) - (ballW / 2) ; //Centre ball into the centre of the screen.
        Y = initialY;
    

    @Override
    public void onDraw(Canvas canvas) 
        super.onDraw(canvas);

        //Draw background.
        canvas.drawBitmap(bgr, 0, 0, null);

        //Compute roughly ball speed and location.
        Y+= (int) dY; //Increase or decrease vertical position.
        if (Y > (screenH - ballH)) 
            dY=(-1)*dY; //Reverse speed when bottom hit.
        
        dY+= acc; //Increase or decrease speed.

        //Increase rotating angle.
        if (angle++ >360)
            angle =0;

        //Draw ball
        canvas.save(); //Save the position of the canvas.
        canvas.rotate(angle, X + (ballW / 2), Y + (ballH / 2)); //Rotate the canvas.
        canvas.drawBitmap(ball, X, Y, null); //Draw the ball on the rotated canvas.
        canvas.restore(); //Rotate the canvas back so that it looks like ball has rotated.

        //Call the next frame.
        invalidate();
    

这只是一个简单的说明,但我会使用 surfaceView 并从另一个线程驱动帧,这有点复杂,但在制作游戏等交互式动画时是一种正确的方法。这是一个带有滚动背景和用户可以用手指移动球:

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class SurfaceViewActivity extends Activity 
    BallBounces ball;

    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        ball = new BallBounces(this);
        setContentView(ball);
    



class BallBounces extends SurfaceView implements SurfaceHolder.Callback 
    GameThread thread;
    int screenW; //Device's screen width.
    int screenH; //Devices's screen height.
    int ballX; //Ball x position.
    int ballY; //Ball y position.
    int initialY ;
    float dY; //Ball vertical speed.
    int ballW;
    int ballH;
    int bgrW;
    int bgrH;
    int angle;
    int bgrScroll;
    int dBgrY; //Background scroll speed.
    float acc;
    Bitmap ball, bgr, bgrReverse;
    boolean reverseBackroundFirst;
    boolean ballFingerMove;

    //Measure frames per second.
    long now;
    int framesCount=0;
    int framesCountAvg=0;
    long framesTimer=0;
    Paint fpsPaint=new Paint();

    //Frame speed
    long timeNow;
    long timePrev = 0;
    long timePrevFrame = 0;
    long timeDelta;


    public BallBounces(Context context) 
        super(context);
        ball = BitmapFactory.decodeResource(getResources(),R.drawable.football); //Load a ball image.
        bgr = BitmapFactory.decodeResource(getResources(),R.drawable.sky_bgr); //Load a background.
        ballW = ball.getWidth();
        ballH = ball.getHeight();

        //Create a flag for the onDraw method to alternate background with its mirror image.
        reverseBackroundFirst = false;

        //Initialise animation variables.
        acc = 0.2f; //Acceleration
        dY = 0; //vertical speed
        initialY = 100; //Initial vertical position
        angle = 0; //Start value for the rotation angle
        bgrScroll = 0;  //Background scroll position
        dBgrY = 1; //Scrolling background speed

        fpsPaint.setTextSize(30);

        //Set thread
        getHolder().addCallback(this);

        setFocusable(true);
    

    @Override
    public void onSizeChanged (int w, int h, int oldw, int oldh) 
        super.onSizeChanged(w, h, oldw, oldh);
        //This event-method provides the real dimensions of this custom view.
        screenW = w;
        screenH = h;

        bgr = Bitmap.createScaledBitmap(bgr, w, h, true); //Scale background to fit the screen.
        bgrW = bgr.getWidth();
        bgrH = bgr.getHeight();

        //Create a mirror image of the background (horizontal flip) - for a more circular background.
        Matrix matrix = new Matrix();  //Like a frame or mould for an image.
        matrix.setScale(-1, 1); //Horizontal mirror effect.
        bgrReverse = Bitmap.createBitmap(bgr, 0, 0, bgrW, bgrH, matrix, true); //Create a new mirrored bitmap by applying the matrix.

        ballX = (int) (screenW /2) - (ballW / 2) ; //Centre ball X into the centre of the screen.
        ballY = -50; //Centre ball height above the screen.
    

    //***************************************
    //*************  TOUCH  *****************
    //***************************************
    @Override
    public synchronized boolean onTouchEvent(MotionEvent ev) 

        switch (ev.getAction()) 
            case MotionEvent.ACTION_DOWN: 
                ballX = (int) ev.getX() - ballW/2;
                ballY = (int) ev.getY() - ballH/2;

                ballFingerMove = true;
                break;
            

            case MotionEvent.ACTION_MOVE: 
                ballX = (int) ev.getX() - ballW/2;
                ballY = (int) ev.getY() - ballH/2;

                break;
            

            case MotionEvent.ACTION_UP:
                ballFingerMove = false;
                dY = 0;
                break;
            
        return true;
    

    @Override
    public void onDraw(Canvas canvas) 
        super.onDraw(canvas);

        //Draw scrolling background.
        Rect fromRect1 = new Rect(0, 0, bgrW - bgrScroll, bgrH);
        Rect toRect1 = new Rect(bgrScroll, 0, bgrW, bgrH);

        Rect fromRect2 = new Rect(bgrW - bgrScroll, 0, bgrW, bgrH);
        Rect toRect2 = new Rect(0, 0, bgrScroll, bgrH);

        if (!reverseBackroundFirst) 
            canvas.drawBitmap(bgr, fromRect1, toRect1, null);
            canvas.drawBitmap(bgrReverse, fromRect2, toRect2, null);
        
        else
            canvas.drawBitmap(bgr, fromRect2, toRect2, null);
            canvas.drawBitmap(bgrReverse, fromRect1, toRect1, null);
        

        //Next value for the background's position.
        if ( (bgrScroll += dBgrY) >= bgrW) 
            bgrScroll = 0;
            reverseBackroundFirst = !reverseBackroundFirst;
        

        //Compute roughly the ball's speed and location.
        if (!ballFingerMove) 
            ballY += (int) dY; //Increase or decrease vertical position.
            if (ballY > (screenH - ballH)) 
                dY=(-1)*dY; //Reverse speed when bottom hit.
            
            dY+= acc; //Increase or decrease speed.
        

        //Increase rotating angle
        if (angle++ >360)
            angle =0;

        //DRAW BALL
        //Rotate method one
        /*
        Matrix matrix = new Matrix();
        matrix.postRotate(angle, (ballW / 2), (ballH / 2)); //Rotate it.
        matrix.postTranslate(ballX, ballY); //Move it into x, y position.
        canvas.drawBitmap(ball, matrix, null); //Draw the ball with applied matrix.

        */// Rotate method two

        canvas.save(); //Save the position of the canvas matrix.
        canvas.rotate(angle, ballX + (ballW / 2), ballY + (ballH / 2)); //Rotate the canvas matrix.
        canvas.drawBitmap(ball, ballX, ballY, null); //Draw the ball by applying the canvas rotated matrix.
        canvas.restore(); //Rotate the canvas matrix back to its saved position - only the ball bitmap was rotated not all canvas.

        //*/

        //Measure frame rate (unit: frames per second).
         now=System.currentTimeMillis();
         canvas.drawText(framesCountAvg+" fps", 40, 70, fpsPaint);
         framesCount++;
         if(now-framesTimer>1000) 
                 framesTimer=now;
                 framesCountAvg=framesCount;
                 framesCount=0;
         
    

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
    

    @Override
    public void surfaceCreated(SurfaceHolder holder) 
        thread = new GameThread(getHolder(), this);
        thread.setRunning(true);
        thread.start();
    

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) 
        boolean retry = true;
        thread.setRunning(false);
        while (retry) 
            try 
                thread.join();
                retry = false;
             catch (InterruptedException e) 

            
        
    


    class GameThread extends Thread 
        private SurfaceHolder surfaceHolder;
        private BallBounces gameView;
        private boolean run = false;

        public GameThread(SurfaceHolder surfaceHolder, BallBounces gameView) 
            this.surfaceHolder = surfaceHolder;
            this.gameView = gameView;
        

        public void setRunning(boolean run) 
            this.run = run;
        

        public SurfaceHolder getSurfaceHolder() 
            return surfaceHolder;
        

        @Override
        public void run() 
            Canvas c;
            while (run) 
                c = null;

                //limit frame rate to max 60fps
                timeNow = System.currentTimeMillis();
                timeDelta = timeNow - timePrevFrame;
                if ( timeDelta < 16) 
                    try 
                        Thread.sleep(16 - timeDelta);
                    
                    catch(InterruptedException e) 

                    
                
                timePrevFrame = System.currentTimeMillis();

                try 
                    c = surfaceHolder.lockCanvas(null);
                    synchronized (surfaceHolder) 
                       //call methods to draw and process next fame
                        gameView.onDraw(c);
                    
                 finally 
                    if (c != null) 
                        surfaceHolder.unlockCanvasAndPost(c);
                    
                
            
        
    

图片如下:

【讨论】:

一个很棒的教程,我的小修复。游戏视图.onDraw(c); => if(gameView!=null && c!=null) gameView.onDraw(c); 您应该使用postInvalidateOnAnimation() 来获得流畅的动画。 我已经尝试过这个应用程序,当我将屏幕方向从“纵向”更改为“ Landscape" 和 VS ,我在我的示例中遇到了同样的问题***.com/questions/14212165/… 谁能帮我解决这个问题?? 考虑draw loop线程关闭策略:***.com/questions/5318847/… @JongzPuangput invalidate() 基本上是重绘视图。因此,如果您更新一些位置变量然后调用invalidate(),您可以使用这些新值在屏幕上绘制一些东西(无论onDraw 方法中的内容是什么)。所以是的,如果你更新一些变量并在循环中调用invalidate(),它可以是一种逐帧动画。这是否是好的做法取决于您要做什么。

以上是关于如何在画布内使用动画框架?的主要内容,如果未能解决你的问题,请参考以下文章

动画 CC 画布和遮罩

如何在 html 画布中获取动画方向?

如何在一页上放置多个画布js动画。 (创建js)

如何在两个动画画布元素之间进行通信?

如何在画布上动画绘制线条

如何在html5画布中逐步绘制线条动画