Android 使用SurfaceView进行2D动画的开发

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SakuraMashiro/article/details/77854781

SurfaceView介绍

SurfaceView是View的子类,可以直接从内存或者DMA等硬件接口取得图像数据,是个非常重要的绘图容器。它的特性是:可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。

这个视图里内嵌了一个专门用于绘制的Surface,Surface可以这样理解:它是内存中一块区域,它是Surfaceview不可见那个部分,绘图操作作用于它,然后它就会被显卡之类的显示控制器绘制到屏幕上。使用SurfaceView 有一个原则,所有的绘图工作必须得在Surface 被创建之后才能开始。因此我们需要实现SurfaceHolder.Callback接口。通过SurfaceHolder接口可以访问这个surface,getHolder()方法可以得到这个接口。Surface对应了一个内存区,大家都知道,内存区的对象是有生命周期的,可以动态的申请创建和销毁,当然也可能会更新。于是,就有了作用于这个内存区的操作,这些操作就是surfaceCreated/Changed/Destroyed。三个操作放在一起,就是Callback。以上三个生命周期回调方法都有其各自的用途,具体如下:

  • public void surfaceCreated(SurfaceHolder holder):该方法在SurfaceView被创建时调用,每次创建界面时需要初始化图片、线程等资源,这些代码一般写在surfaceCreated中。
  • public void surfaceChanged( SurfaceHolder holder , int format , int width , int height ) : 该方法在SurfaceView变化时被调用(如自动横竖屏切换时),在surfaceCreated方法被调用后,该方法至少会被调用一次。当SurfaceView变化时,如果需要改变一些值,这些代码应该放在surfaceChanged方法中。
  • public void surfaceDestroyed( SurfaceHolder holder ) : 该方法在SurfaceView销毁时被调用。当SurfaceView被销毁时,有些与界面相关的资源应该被释放掉,这些代码应写在surfaceDestroyed方法中。

所以,我们只需继承SurfaceView来开发2D动画效果,并实现SurfaceHolder.Callback接口。

案例演示

下面通过一个2D动画的简单案例来具体演示,动画的场景是:一枚炮弹从屏幕的左下角发射,以抛物线的轨迹划过天空,并在一定的位置爆炸。

MainActivity

package com.surfaceviewdemo;

import android.content.pm.ActivityInfo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends AppCompatActivity {

    //游戏界面
    private MySurfaceView gameView ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //设置全屏模式
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
        //设置为横屏模式
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        gameView = new MySurfaceView(this);
        setContentView(gameView);
    }
}

MySurfaceView

package com.surfaceviewdemo;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/**
 * Created by Administrator on 2017/9/5.
 */

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback{

    //Activity的引用
    Activity activity ;
    //画笔引用
    Paint paint ;
    //绘制线程引用
    DrawThread drawThread ;
    //背景图片
    Bitmap bgBmp ;
    //炮弹位图
    Bitmap bulletBmp ;
    //爆炸位图数组
    Bitmap[] explodeBmps ;
    //炮弹对象引用
    Bullet bullet ;

    public MySurfaceView(Activity activity){
        super(activity);
        this.activity = activity ;
        //获取焦点
        this.requestFocus();
        //设置为可触控
        this.setFocusableInTouchMode(true);
        //注册回调接口
        getHolder().addCallback(this);
    }

    //重写onDraw方法
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制背景
        canvas.drawBitmap(bgBmp,0,0,paint);
        //绘制炮弹
        bullet.drawSelf(canvas,paint);

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //创建画笔
        paint = new Paint();
        //打开抗锯齿
        paint.setAntiAlias(true);
        //加载图片资源
        bulletBmp = BitmapFactory.decodeResource(getResources(),R.drawable.bullet);
        bgBmp = BitmapFactory.decodeResource(getResources(),R.drawable.bg);
        explodeBmps = new Bitmap[]{
               BitmapFactory.decodeResource(getResources(),R.drawable.explode0),
               BitmapFactory.decodeResource(getResources(),R.drawable.explode1),
               BitmapFactory.decodeResource(getResources(),R.drawable.explode2),
               BitmapFactory.decodeResource(getResources(),R.drawable.explode3),
               BitmapFactory.decodeResource(getResources(),R.drawable.explode4),
               BitmapFactory.decodeResource(getResources(),R.drawable.explode5),
        };
        //创建炮弹对象
        bullet = new Bullet(this, bulletBmp , explodeBmps , 0 , 290 , 1.3f , -5.9f);
        //创建绘制线程
        drawThread = new DrawThread(this);
        //启动绘制线程
        drawThread.start();


    }

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

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //停止绘制线程
        drawThread.setFlag(false);
    }
}

Bullet炮弹类

package com.surfaceviewdemo;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.transition.Explode;

/**
 * Created by Administrator on 2017/9/5.
 */

public class Bullet {
    MySurfaceView gameView ;
    //位图
    private Bitmap bitmap ;
    //爆炸动画数组
    private Bitmap[] bitmaps ;
    //x轴位置
    float x ;
    //y轴位置
    float y ;
    //x轴速度
    float vx ;
    //y轴速度
    float vy ;
    //生存时间
    private float t = 0 ;
    //时间间隔
    private float timeSpan = 0.5f ;
    //炮弹尺寸
    int size;
    //是否绘制炮弹的标记
    boolean explodeFlag = false ;
    //爆炸对象引用
    Explosion mExplosion ;
    public Bullet( MySurfaceView gameView , Bitmap bitmap , Bitmap[] bitmaps, float x , float y , float vx , float vy){
        this.gameView = gameView ;
        this.bitmap = bitmap;
        this.bitmaps = bitmaps ;
        this.x = x ;
        this.y = y ;
        this.vx = vx ;
        this.vy = vy ;
        size = bitmap.getHeight();
    }

    /**
     * 绘制炮弹的方法
     */
    public void drawSelf(Canvas canvas, Paint paint) {
        //如果已经爆炸,绘制爆炸动画
        if( explodeFlag && mExplosion != null ){
            mExplosion.drawSelf(canvas,paint);
        }else {
            //炮弹前进
            go();
            //绘制炮弹
            canvas.drawBitmap(bitmap,x,y,paint);
        }
    }

    /**
     * 炮弹前进的方法
     */
    public void go(){
        x += vx * t ;
        y += vy * t + 0.5f * Constant.G * t * t ;
        //特定位置爆炸
        if( x >= Constant.EXPLOSION_X || y >= Constant.SCREEN_HEIGHT ){
            //创建爆炸对象
            mExplosion = new Explosion(gameView,bitmaps,x,y);
            //不再绘制炮弹
            explodeFlag = true;
            return;
        }
        t += timeSpan ;
    }

}

Explosion类

package com.surfaceviewdemo;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;

/**
 * Created by Administrator on 2017/9/5.
 */

public class Explosion {
    MySurfaceView gameView;
    private Bitmap[] bitmaps ;
    float x;
    float y;

    //爆炸动画帧索引
    private int anmiIndex = 0 ;
    public Explosion( MySurfaceView gameView , Bitmap[] bitmaps , float x , float y){

        this.gameView = gameView ;
        this.bitmaps = bitmaps ;
        this.x = x ;
        this.y = y ;
    }

    //绘制背景的方法
    public void drawSelf( Canvas canvas , Paint paint){
        //如果动画播放完毕,不再绘制动画效果
        if( anmiIndex >= bitmaps.length - 1 ){
            return;
        }
        //绘制数组中某一幅图
        canvas.drawBitmap(bitmaps[anmiIndex],x,y,paint);
        //当前下标加1
        anmiIndex++;
    }
}

DrawThread绘制线程

package com.surfaceviewdemo;

import android.graphics.Canvas;
import android.view.SurfaceHolder;

/**
 * Created by Administrator on 2017/9/5.
 */

public class DrawThread extends Thread {
    //线程工作标志位
    private boolean flag = true ;
    //线程休眠时间
    private int sleepSpan = 100 ;
    //父界面引用
    MySurfaceView gameView ;
    //surfaceHolder引用
    SurfaceHolder surfaceHolder ;
    //构造器
    public DrawThread(MySurfaceView gameView){
        this.gameView = gameView ;
        //创建SurfaceHolder对象
        this.surfaceHolder = gameView.getHolder();
    }

    @Override
    public void run() {
        //声明画布
        Canvas c ;
        //循环执行刷帧任务
        while( flag ){
            c = null ;
            try {
                //锁定画布
                c = surfaceHolder.lockCanvas(null);
                //锁定surfaceHolder
                synchronized ( surfaceHolder ){
                    //绘制一帧画面
                    gameView.onDraw(c);
                }
            }finally {
                //释放锁
                if( c!= null){
                    surfaceHolder.unlockCanvasAndPost(c);
                }
            }
            try {
                Thread.sleep(sleepSpan);
            }catch ( Exception e){
                e.printStackTrace();
            }
        }
    }

    public void setFlag( boolean flag){
        this.flag = flag ;
    }
}

Constant常量类

package com.surfaceviewdemo;

/**
 * 用于统一管理常量的类
 */
public class Constant {
    //屏幕宽度
    public static final int SCREEN_WIDTH = 480 ;
    //屏幕高度
    public static final int SCREEN_HEIGHT = 320 ;
    //爆炸x位置
    public static final int EXPLOSION_X = 270 ;
    //重力加速度
    public static final float G = 1.0f ;
}

最后的效果如下面两幅图

扫描二维码关注公众号,回复: 3517039 查看本文章

这里写图片描述

这里写图片描述

猜你喜欢

转载自blog.csdn.net/SakuraMashiro/article/details/77854781