`
mmdev
  • 浏览: 12916385 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

Android SurfaceView使用详解

 
阅读更多

1. SurfaceView的定义
前面已经介绍过View了,下面来简单介绍一下SurfaceView,参考SDK文档和网络资料:SurfaceView是View的子类,它内嵌了一个专门用于绘制的Surface,你可以控制这个Surface的格式和尺寸,Surfaceview控制这个Surface的绘制位置。surface是纵深排序(Z-ordered)的,说明它总在自己所在窗口的后面。SurfaceView提供了一个可见区域,只有在这个可见区域内的surface内容才可见。surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果surface上面有透明控件,那么每次surface变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。
SurfaceView默认使用双缓冲技术的,它支持在子线程中绘制图像,这样就不会阻塞主线程了,所以它更适合于游戏的开发。

2. SurfaceView的使用
首先继承SurfaceView,并实现SurfaceHolder.Callback接口,实现它的三个方法:surfaceCreated,surfaceChanged,surfaceDestroyed。
surfaceCreated(SurfaceHolder holder):surface创建的时候调用,一般在该方法中启动绘图的线程。
surfaceChanged(SurfaceHolder holder, int format, int width,int height):surface尺寸发生改变的时候调用,如横竖屏切换。
surfaceDestroyed(SurfaceHolder holder) :surface被销毁的时候调用,如退出游戏画面,一般在该方法中停止绘图线程。
还需要获得SurfaceHolder,并添加回调函数,这样这三个方法才会执行。

3. SurfaceView实战
下面通过一个小demo来学习SurfaceView的使用,绘制一个精灵,该精灵有四个方向的行走动画,让精灵沿着屏幕四周不停的行走。游戏中精灵素材和最终实现的效果图:

首先创建核心类GameView.java,源码如下:

public class GameView extends SurfaceView implements
        SurfaceHolder.Callback {
 
    //屏幕宽高
    public static int SCREEN_WIDTH;
    public static int SCREEN_HEIGHT;
 
    private Context mContext;
    private SurfaceHolder mHolder;
    //最大帧数 (1000 / 30)
    private static final int DRAW_INTERVAL = 30;
 
    private DrawThread mDrawThread;
    private FrameAnimation []spriteAnimations;
    private Sprite mSprite;
    private int spriteWidth = 0;
    private int spriteHeight = 0;
    private float spriteSpeed = (float)((500  * SCREEN_WIDTH / 480) * 0.001);
    private int row = 4;
    private int col = 4;
 
    public GameSurfaceView(Context context) {
        super(context);
        this.mContext = context;
        mHolder = this.getHolder();
        mHolder.addCallback(this);
        initResources();
 
        mSprite = new Sprite(spriteAnimations,0,0,spriteWidth,spriteHeight,spriteSpeed);
    }
 
    private void initResources() {
        Bitmap[][] spriteImgs = generateBitmapArray(mContext, R.drawable.sprite, row, col);
        spriteAnimations = new FrameAnimation[row];
        for(int i = 0; i < row; i ++) {
            Bitmap []spriteImg = spriteImgs[i];
            FrameAnimation spriteAnimation = new FrameAnimation(spriteImg,new int[]{150,150,150,150},true);
            spriteAnimations[i] = spriteAnimation;
        }
    }
 
    public Bitmap decodeBitmapFromRes(Context context, int resourseId) {
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inPreferredConfig = Bitmap.Config.RGB_565;
        opt.inPurgeable = true;
        opt.inInputShareable = true;
 
        InputStream is = context.getResources().openRawResource(resourseId);
        return BitmapFactory.decodeStream(is, null, opt);
    }
 
    public Bitmap createBitmap(Context context, Bitmap source, int row,
            int col, int rowTotal, int colTotal) {
        Bitmap bitmap = Bitmap.createBitmap(source,
                (col - 1) * source.getWidth() / colTotal,
                (row - 1) * source.getHeight() / rowTotal, source.getWidth()
                        / colTotal, source.getHeight() / rowTotal);
        return bitmap;
    }
 
    public Bitmap[][] generateBitmapArray(Context context, int resourseId,
            int row, int col) {
        Bitmap bitmaps[][] = new Bitmap[row][col];
        Bitmap source = decodeBitmapFromRes(context, resourseId);
        this.spriteWidth = source.getWidth() / col;
        this.spriteHeight = source.getHeight() / row;
        for (int i = 1; i <= row; i++) {
            for (int j = 1; j <= col; j++) {
                bitmaps[i - 1][j - 1] = createBitmap(context, source, i, j,
                        row, col);
            }
        }
        if (source != null && !source.isRecycled()) {
            source.recycle();
            source = null;
        }
        return bitmaps;
    }
 
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
    }
 
    public void surfaceCreated(SurfaceHolder holder) {
        if(null == mDrawThread) {
            mDrawThread = new DrawThread();
            mDrawThread.start();
        }
    }
 
    public void surfaceDestroyed(SurfaceHolder holder) {
        if(null != mDrawThread) {
            mDrawThread.stopThread();
        }
    }
 
    private class DrawThread extends Thread {
        public boolean isRunning = false;
 
        public DrawThread() {
            isRunning = true;
        }
 
        public void stopThread() {
            isRunning = false;
            boolean workIsNotFinish = true;
            while (workIsNotFinish) {
                try {
                    this.join();// 保证run方法执行完毕
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                workIsNotFinish = false;
            }
        }
 
        public void run() {
            long deltaTime = 0;
            long tickTime = 0;
            tickTime = System.currentTimeMillis();
            while (isRunning) {
                Canvas canvas = null;
                try {
                    synchronized (mHolder) {
                        canvas = mHolder.lockCanvas();
                        //设置方向
                        mSprite.setDirection();
                        //更新精灵位置
                        mSprite.updatePosition(deltaTime);
                        drawSprite(canvas);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (null != mHolder) {
                        mHolder.unlockCanvasAndPost(canvas);
                    }
                }
 
                deltaTime = System.currentTimeMillis() - tickTime;
                if(deltaTime < DRAW_INTERVAL) {
                    try {
                        Thread.sleep(DRAW_INTERVAL - deltaTime);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                tickTime = System.currentTimeMillis();
            }
 
        }
    }
 
    private void drawSprite(Canvas canvas) {
        //清屏操作
        canvas.drawColor(Color.BLACK);
        mSprite.draw(canvas);
    }
 
}

GameView.java中包含了一个绘图线程DrawThread,在线程的run方法中锁定Canvas、绘制精灵、更新精灵位置、释放Canvas等操作。因为精灵素材是一张大图,所以这里进行了裁剪生成一个二维数组。使用这个二维数组初始化了精灵四个方向的动画,下面看Sprite.java的源码:
public class Sprite {
 
    public static final int DOWN = 0;
    public static final int LEFT = 1;
    public static final int RIGHT = 2;
    public static final int UP = 3;
 
    public float x;
    public float y;
    public int width;
    public int height;
    //精灵行走速度
    public double speed;
    //精灵当前行走方向
    public int direction;
    //精灵四个方向的动画
    public FrameAnimation[] frameAnimations;
 
    public Sprite(FrameAnimation[] frameAnimations, int positionX,
            int positionY, int width, int height, float speed) {
        this.frameAnimations = frameAnimations;
        this.x = positionX;
        this.y = positionY;
        this.width = width;
        this.height = height;
        this.speed = speed;
    }
 
    public void updatePosition(long deltaTime) {
        switch (direction) {
        case LEFT:
            //让物体的移动速度不受机器性能的影响,每帧精灵需要移动的距离为:移动速度*时间间隔
            this.x = this.x - (float) (this.speed * deltaTime);
            break;
        case DOWN:
            this.y = this.y + (float) (this.speed * deltaTime);
            break;
        case RIGHT:
            this.x = this.x + (float) (this.speed * deltaTime);
            break;
        case UP:
            this.y = this.y - (float) (this.speed * deltaTime);
            break;
        }
    }
 
    /**
     * 根据精灵的当前位置判断是否改变行走方向
     */
    public void setDirection() {
        if (this.x <= 0
                && (this.y + this.height) < GameSurfaceView.SCREEN_HEIGHT) {
            if (this.x < 0)
                this.x = 0;
            this.direction = Sprite.DOWN;
        } else if ((this.y + this.height) >= GameSurfaceView.SCREEN_HEIGHT
                && (this.x + this.width) < GameSurfaceView.SCREEN_WIDTH) {
            if ((this.y + this.height) > GameSurfaceView.SCREEN_HEIGHT)
                this.y = GameSurfaceView.SCREEN_HEIGHT - this.height;
            this.direction = Sprite.RIGHT;
        } else if ((this.x + this.width) >= GameSurfaceView.SCREEN_WIDTH
                && this.y > 0) {
            if ((this.x + this.width) > GameSurfaceView.SCREEN_WIDTH)
                this.x = GameSurfaceView.SCREEN_WIDTH - this.width;
            this.direction = Sprite.UP;
        } else {
            if (this.y < 0)
                this.y = 0;
            this.direction = Sprite.LEFT;
        }
 
    }
 
    public void draw(Canvas canvas) {
        FrameAnimation frameAnimation = frameAnimations[this.direction];
        Bitmap bitmap = frameAnimation.nextFrame();
        if (null != bitmap) {
            canvas.drawBitmap(bitmap, x, y, null);
        }
    }
}

精灵类主要是根据当前位置判断行走的方向,然后根据行走的方向更新精灵的位置,再绘制自身的动画。由于精灵的动画是一帧一帧的播放图片,所以这里封装了FrameAnimation.java,源码如下:
public class FrameAnimation{
    /**动画显示的需要的资源 */
    private Bitmap[] bitmaps;
    /**动画每帧显示的时间 */
    private int[] duration;
    /**动画上一帧显示的时间 */
    protected Long lastBitmapTime;
    /**动画显示的索引值,防止数组越界 */
    protected int step;
    /**动画是否重复播放 */
    protected boolean repeat;
    /**动画重复播放的次数*/
    protected int repeatCount;
 
    /**
     * @param bitmap:显示的图片<br/>
     * @param duration:图片显示的时间<br/>
     * @param repeat:是否重复动画过程<br/>
     */
    public FrameAnimation(Bitmap[] bitmaps, int duration[], boolean repeat) {
        this.bitmaps = bitmaps;
        this.duration = duration;
        this.repeat = repeat;
        lastBitmapTime = null;
        step = 0;
    }
 
    public Bitmap nextFrame() {
        // 判断step是否越界
        if (step >= bitmaps.length) {
            //如果不无限循环
            if( !repeat ) {
                return null;
            } else {
                lastBitmapTime = null;
            }
        }
 
        if (null == lastBitmapTime) {
            // 第一次执行
            lastBitmapTime = System.currentTimeMillis();
            return bitmaps[step = 0];
        }
 
        // 第X次执行
        long nowTime = System.currentTimeMillis();
        if (nowTime - lastBitmapTime <= duration[step]) {
            // 如果还在duration的时间段内,则继续返回当前Bitmap
            // 如果duration的值小于0,则表明永远不失效,一般用于背景
            return bitmaps[step];
        }
        lastBitmapTime = nowTime;
        return bitmaps[step++];// 返回下一Bitmap
    }
 
}

FrameAnimation根据每一帧的显示时间返回当前的图片帧,若没有超过指定的时间则继续返回当前帧,否则返回下一帧。
接下来需要做的是让Activty显示的View为我们之前创建的GameView,然后设置全屏显示。
public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
 
     getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
             WindowManager.LayoutParams.FLAG_FULLSCREEN);
     requestWindowFeature(Window.FEATURE_NO_TITLE);
     getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
             WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
 
     DisplayMetrics outMetrics = new DisplayMetrics();
     this.getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
     GameSurfaceView.SCREEN_WIDTH = outMetrics.widthPixels;
     GameSurfaceView.SCREEN_HEIGHT = outMetrics.heightPixels;
     GameSurfaceView gameView = new GameSurfaceView(this);
     setContentView(gameView);
 }

现在运行Android工程,应该就可以看到一个手持宝剑的武士在沿着屏幕不停的走了。


转载请注明来自:Alex Zhou,本文链接:http://codingnow.cn/android/603.html


分享到:
评论

相关推荐

    【Android基础】(12)SurfaceView绘图API详解

    【Android基础】(12)SurfaceView绘图API详解代码

    Android 自定义SurfaceView详解

    本文主要介绍Android SurfaceView自定义方法,这里对SurfaceView的基础知识做了详解,并附简单的示例代码,以便参考,有需要的小伙伴可以参考下

    SurfaceView运行机制详解

    SurfaceView运行机制详解 剖析BACK与HOME按键及切入后台等异常处理

    Android切换至SurfaceView时闪屏(黑屏闪一下)以及黑屏移动问题的解决方法

    1.最近的项目中,有一个...详解:  I think I found the reason for the black flash. In my case I’m using a SurfaceView inside a Fragment and dynamically adding this fragment to the activity after som

    Android SurfaceView运行机制剖析--处理切换到后台再重新进入程序时的异常

    本文主要介绍Android SurfaceView运行机制,这里整理了详细的资料来讲解SurfaceView的运行原理,并附示例代码参考,有需要的小伙伴可以参考下

    Android编程之SurfaceView实例详解

    本文实例讲述了Android编程之SurfaceView用法。分享给大家供大家参考,具体如下: 关于surfaceView相关知识: View和SurfaceView主要区别: 1. View只能在UI线程中刷新,而SurfaceView可以在子线程中刷新 2. ...

    Android View类与SurfaceView类详解

    本文主要介绍Android View类与SurfaceView类,这里提供了详细的Android View类和SurfaceView类的使用方法,有兴趣的小伙伴可以参考下

    Android编程之SurfaceView学习示例详解

    主要介绍了Android编程之SurfaceView学习示例,结合实例分析了SurfaceView的功能、使用方法与注意事项,具有一定参考借鉴价值,需要的朋友可以参考下

    Android开发教程之调用摄像头功能的方法详解

    本文实例讲述了Android调用摄像头功能的方法。分享给大家供大家参考,具体如下: 我们要调用摄像头的拍照功能,显然 第一步必须加入调用摄像头硬件的权限,拍完照后我们要将图片保存在SD卡中,必须加入SD卡读写权限...

    Android游戏开发20回合

    Android游戏开发三 View类详解 Android游戏开发四 Canvas和Paint实例 Android游戏开发五Path和Typeface Android游戏开发六 自定义View Android游戏开发七 自定义SurfaceView Android游戏开发八 SurfaceView类实例 ...

    Android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)demo实例详解

    本文通过实例代码给大家讲解android仿微信清理内存图表动画(解决surfaceView屏幕闪烁问题)的相关资料,本文介绍的非常详细,具有参考借鉴价值,需要的朋友可以参考下

    Android代码-Android相机预览拍照录像滤镜详解

    Android Camera 本系列会包括Android Camera预览,拍照,视频录制,播放,滤镜及渲染等,还会对相机系统原理做深入分析。 一、相机预览 序号 项目名称 内容简介 1 GLSurfacePreview GLSurfaceView OpenGL...

    Android游戏开发之旅

    3.Android游戏开发之旅三 View类详解 4.Android游戏开发之旅四 Canvas和Paint实例 5.Android游戏开发之旅五 Path和Typeface 6.Android游戏开发之旅六 自定义View 7.Android游戏开发之旅七 自定义SurfaceView 8....

    android开发揭秘PDF

    2.3.2 运行HelloAndroid及模拟器的使用 2.3.3 调试HelloAndroid 2.4 小结 第二部分 基础篇 第3章 Android程序设计基础 3.1 Android程序框架 3.1.1 Android项目目录结构 3.1.2 Android应用解析 3.2 Android的生命...

    Android适配底部虚拟按键的方法详解

    在没有虚拟按键的设备上影响了SurfaceView全屏显示图传(原本全屏显示的图传在切出去再进来时变成了小屏显示) 通过google了很多方法并尝试终于解决了这个问题,达到如下效果: 每次进入界面时虚拟按键自动隐藏 手动...

    android开发资料大全

    【Android系统原理与开发要点详解】/底层 应用 框架 Android核心分析28篇,强烈推荐android初学者,android进阶者看看这个系列教程 Android应用开发者指南:性能优化 android开发教程合集(推荐新手看下这一季教程)...

    《Android应用开发揭秘》附带光盘代码.

    《Android应用开发揭秘》全部实例源代码,配合《Android应用开发揭秘》使用 前言  第一部分 准备篇  第1章 Android开发简介  1.1 Android基本概念  1.1.1 Android简介  1.1.2 Android的系统构架  1.1.3 ...

Global site tag (gtag.js) - Google Analytics