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,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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,然后设置全屏显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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工程,应该就可以看到一个手持宝剑的武士在沿着屏幕不停的走了。

Android SurfaceView使用详解的更多相关文章

  1. Android之canvas详解

    首先说一下canvas类: Class Overview The Canvas class holds the "draw" calls. To draw something, y ...

  2. android:ToolBar详解

    android:ToolBar详解(手把手教程) 泡在网上的日子 发表于 2014-11-18 12:49 第 124857 次阅读 ToolBar 42 来源 http://blog.mosil.b ...

  3. 【转】Android Canvas绘图详解(图文)

    转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android Canvas绘图详解(图文) 泡 ...

  4. Android 核心分析 之八Android 启动过程详解

    Android 启动过程详解 Android从Linux系统启动有4个步骤: (1) init进程启动 (2) Native服务启动 (3) System Server,Android服务启动 (4) ...

  5. Android GLSurfaceView用法详解(二)

    输入如何处理       若是开发一个交互型的应用(如游戏),通常需要子类化 GLSurfaceView,由此可以获取输入事件.下面有个例子: java代码: package eoe.ClearTes ...

  6. Android编译过程详解(一)

    Android编译过程详解(一) 注:本文转载自Android编译过程详解(一):http://www.cnblogs.com/mr-raptor/archive/2012/06/07/2540359 ...

  7. android屏幕适配详解

    android屏幕适配详解 官方地址:http://developer.android.com/guide/practices/screens_support.html 一.关于布局适配建议 1.不要 ...

  8. Android.mk文件详解(转)

    源:Android.mk文件详解 从对Makefile一无所知开始,折腾了一个多星期,终于对Android.mk有了一个全面些的了解.了解了标准的Makefile后,发现Android.mk其实是把真 ...

  9. Android Studio 插件开发详解四:填坑

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78265540 本文出自[赵彦军的博客] 在前面我介绍了插件开发的基本流程 [And ...

随机推荐

  1. 【hdu1573-X问题】拓展欧几里得-同余方程组

    http://acm.hdu.edu.cn/showproblem.php?pid=1573 求小于等于N的正整数中有多少个X满足: X mod a0 = b0 X mod a1 = b1 …… X  ...

  2. hdu 3590 PP and QQ

    知识储备: Anti-SG 游戏和 SJ 定理  [定义](anti-nim 游戏)  桌子上有 N 堆石子,游戏者轮流取石子.  每次只能从一堆中取出任意数目的石子,但不能不取.  取走最后一 ...

  3. 8 simple things that will make you sexy

    8 simple things that will make you sexy8种方法教你不动声色的性感What makes a women sexy? Is it her body? Is it t ...

  4. Java学习笔记之:Java 流

    一.介绍 Java.io包几乎包含了所有操作输入.输出需要的类.所有这些流类代表了输入源和输出目标. Java.io包中的流支持很多种格式,比如:基本类型.对象.本地化字符集等等. 一个流可以理解为一 ...

  5. Qt之窗体透明 (三种不同的方法和效果)

    关于窗体透明,经常遇到,网上的资料倒不少,也不知道写的时候是否验证过,很多都不正确...今天就在此一一阐述!       以下各效果是利用以前写过的一个小程序作为示例进行讲解!(代码过多,贴主要部分) ...

  6. php 写入数据到MySQL以及从MySQL获取数据,页面出现乱码的解决方法

    现象如标题. 解决思路: 1确定数据库charset是否是utf-8 a. charset不是utf-8 1, 更改数据库charset为utf-8 ALTER DATABASE db_name DE ...

  7. ILog JRules 标识符 X 未定义

    在定义枚举值时,属性“转译”(“translation”),定义字符串时需要双引号,相当于value值. 其属性值如果只有字符串,而没有双引号,在编译或者执行时就会报“标识符X未定义”的错误信息.

  8. 关于c#字典key不存在的测试

    之前一直隐约记得没有创建key会报异常,测试了下. 测试结果: 写入值,如果不存在key,会自动创建. 取值,如果不存在key,会报异常. 一般用c#提供了尝试取值方法,不过有out参数,考虑写扩展 ...

  9. 《c程序设计语言》读书笔记--大于8 的字符串输出

    #include <stdio.h> #define MAXLINE 100 #define MAX 8 int getline(char line[],int maxline); voi ...

  10. poj-3176 Cow Bowling &&poj-1163 The Triangle && hihocoder #1037 : 数字三角形 (基础dp)

    经典的数塔模型. 动态转移方程:  dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+p[i][j]; #include <iostream> #include ...