如何在画布内使用动画框架?
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()
,它可以是一种逐帧动画。这是否是好的做法取决于您要做什么。以上是关于如何在画布内使用动画框架?的主要内容,如果未能解决你的问题,请参考以下文章