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

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

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

这怎么可能?

【问题讨论】:

    标签: android android-animation


    【解决方案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 ,我在我的示例中遇到了同样的问题stackoverflow.com/questions/14212165/… 谁能帮我解决这个问题??
    • 考虑draw loop线程关闭策略:stackoverflow.com/questions/5318847/…
    • @JongzPuangput invalidate() 基本上是重绘视图。因此,如果您更新一些位置变量然后调用invalidate(),您可以使用这些新值在屏幕上绘制一些东西(无论onDraw 方法中的内容是什么)。所以是的,如果你更新一些变量并在循环中调用invalidate(),它可以是一种逐帧动画。这是否是好的做法取决于您要做什么。
    猜你喜欢
    • 2018-01-16
    • 1970-01-01
    • 2021-09-28
    • 2014-04-07
    • 1970-01-01
    • 2020-03-17
    • 2015-04-18
    • 2015-10-26
    • 2016-01-03
    相关资源
    最近更新 更多