Android SurfaceView使用详解(很好的实战例子)

article/2025/11/3 15:17:38

一、surfaceview 
在显示时才会调用callback中的surfaceCreated。注意,是在显示时,在初始化时不会调用
在隐藏时会调用callback中的surfaceDestroyed

二、清屏操作

public void clearDraw(SurfaceHolder holder,int color) {Log.w("tan","clearDraw");Canvas canvas = null;try {canvas = holder.lockCanvas(null);canvas.drawColor(color);}catch (Exception e) {// TODO: handle exceptione.printStackTrace();}finally {if(canvas != null) {holder.unlockCanvasAndPost(canvas); }}
}

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 implementsSurfaceHolder.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) {
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

 

精灵类主要是根据当前位置判断行走的方向,然后根据行走的方向更新精灵的位置,再绘制自身的动画。由于精灵的动画是一帧一帧的播放图片,所以这里封装了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工程,应该就可以看到一个手持宝剑的武士在沿着屏幕不停的走了。


http://chatgpt.dhexx.cn/article/oenWey0G.shtml

相关文章

android绘制黑色画面,Android音视频技术入门之绘制一张图片

Android 的音视频入门学习&#xff0c;首先了解一下绘制图片。在 Android 平台绘制一张图片&#xff0c;使用至少 3 种不同的 API&#xff0c;ImageView&#xff0c;SurfaceView&#xff0c;自定义 View作绘制图片。下面我以SurfaceView作重点来讲&#xff0c;为什么不用其他的…

Android图形系统之Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback之间的联系

SurfaceHolder 版本&#xff1a;Android 2.2 r1 结构 继承关系 public interface SurfaceHolder android.view.SurfaceHolder 概述 抽象接口持有人显示表面。允许您控制面的大小和格式&#xff0c;编辑在suface的橡树&#xff0c;并监测到变化。此接口通常可通过SurfaceView类…

Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback之间的关系

一、Surface Surface就是“表面”的意思。在SDK的文档中&#xff0c;对Surface的描述是这样 的&#xff1a;“Handle onto a raw buffer that is being managed by the screen compositor”&#xff0c;翻译成中文就是“由屏幕显示内容合成器(screen compositor)所管理的原生缓…

android surfaceview学习(一)

推荐阅读&#xff1a; https://blog.csdn.net/luoshengyang/article/details/8661317 https://www.cnblogs.com/xuling/archive/2011/06/06/android.html https://blog.csdn.net/zcmain/article/details/14454953 1、surfaceview默认是黑色的背景&#xff0c;并且给sfv设置背景…

Activity、Fragment和Surface的生命周期

这里首先推荐大家一本Android的学习书籍&#xff1a;《第一行代码》&#xff0c;这本书还是写得非常好的。好了&#xff0c;进入正题&#xff0c;我们先来了解一下Activity、Fragment的的生命周期中经历的几个阶段。 ActivityFragment 下面我们假设有如下的场景&#xff0c;Act…

Java进度条

转自&#xff1a;点击打开链接 进度条用在很多的地方&#xff0c;比如登录验证等待过程、程序加载、数据下载、数据更新与删除等&#xff0c;它可以一目了然的让用户知道程序执行的进度。下面给大家看一个登录验证的进度条&#xff0c;首先上效果图&#xff1a; 在代码中未连接…

java窗口进度条_java进度条窗口具体如何制作?

可视化窗口是大部分游戏的常态&#xff0c;今天我们就来了解下如何编写进度条窗口&#xff0c;快跟小编一起看看吧。 一、在包右击new->other->WindowBuilder->JFrame->name叫Guithreadpackage guithread; import java.awt.BorderLayout; import java.awt.EventQue…

java进度条_「java进度条」Java JProgressBar类(进度条) - seo实验室

java进度条 简介 利用JprogressBar类可以实现一个进度条。它一般是一种颜色部分或完全填充的矩形。缺省情况下,进度条配备了一个凹陷的边框,并水平放置。 进度条还可以选择显示一个字符串,这个字符串在进度条矩形的中央位置上显示。这个字符串缺省时为耗时任务已完成的百分比…

Java 进度条

效果 代码 思路&#xff1a;使用NumberFormat类来格式化数字&#xff0c;显示完后&#xff0c;退格覆盖重新显示新的数字 import java.text.NumberFormat;public class Test {public static void main(String[] args) throws Exception {System.out.print("Progress:"…

Java图形化界面---进度条

目录 一、进度条的介绍 二、创建进度条 三、简单案例 四、子线程创建进度条 &#xff08;1&#xff09;为什么要使用子线程创建进度条 &#xff08;2&#xff09;子线程创建进度条的代码 五、创建进度对话框 &#xff08;1&#xff09;如何创建进度对话框 &#xff08;2&…

Java图形化界面编程超详细知识点(7)——进度条

目录 4 JProgressBar、ProgressMonitor、BoundedRangeModel实现进度条 4.1 创建进度条 4 JProgressBar、ProgressMonitor、BoundedRangeModel实现进度条 进度条是图形界面中广泛使用的GUI组件&#xff0c;当复制一个较大的文件时&#xff0c;操作系统会显示一个进度条&#…

Java制作进度条

【效果图】 【描述】 进度条宽度保持和窗口宽度一致&#xff0c;进度最小值为0&#xff0c;最大值为100&#xff0c;每100毫秒进度值1&#xff0c;进度条中间显示进度百分比 【代码】 package test;import java.awt.*; import java.awt.event.*;import javax.swing.*;public…

QT:QGraphicsScene与QGraphicsView使用render()函数渲染图片

render()函数看官方文档&#xff0c;会有很多不懂得地方&#xff0c;以下为实践出来的效果。 当我们想要用QPaint() 绘制我们在QGraphicsView看到的部分时&#xff0c;不仅要绘制正确的图片大小&#xff0c;还需要使用 render() 渲染正确的大小。当我们只想要绘制坐标系中矩形…

Qt图形特效:QGraphicsOpacityEffect

一、描述 不透明度效果使源具有不透明度。该效果对于使源半透明非常有用&#xff0c;类似于淡入/淡出序列。可以使用setOpacity()函数修改不透明度。 二、属性成员 1、opacity : qreal。此属性保存不透明度。该值应在0.0到1.0的范围内&#xff0c;其中0.0是完全透明的&#…

图形视图(02):【类】QGraphicsScene [官翻]

文章目录 详述事件处理和传播 公共类型enum ItemIndexMethodenum SceneLayer 属性backgroundBrush: QBrushbspTreeDepth: intfocusOnTouch: boolfont: QFontforegroundBrush: QBrushitemIndexMethod: ItemIndexMethodminimumRenderSize: qrealpalette: QPalette**sceneRect**: …

Qt中使用QGraphicsScene重写drawBackGround绘制背景

Qt中使用QGraphicsScene重写drawBackGround绘制背景 需求解释 我是想这学习Qt的界面设计&#xff0c;希望能够对界面背景进行优化然后使用Qt的界面与图形画背景网格。首先查资料 通过查找博客发现很简单&#xff0c;就是新建一个类继承于QGraphicsScene&#xff0c;于是我就…

图形视图框架QGraphicsScene(场景,概念)

QGraphicsScene 该类充当 QGraphicsItems 的容器。它与 QGraphicsView 一起使用&#xff0c;用于在 2D 表面上可视化图形项目&#xff0c;例如线条、矩形、文本甚至自定义项目。 QGraphicsScene具有的功能&#xff1a; 提供用管理大量数据项的高速接口传播事件到每一个图形项…

qt学习笔记(五) QGraphicsPixmapItem与QGraphicsScene的编程实例 图标拖动渐变效果

应大家的要求&#xff0c;还是把完整的工程文件贴出来&#xff0c;大家省点事&#xff1a;http://www.kuaipan.cn/file/id_48923272389086450.htm 先看看运行效果&#xff0c;我用的群创7寸屏&#xff0c;主机是mini2440&#xff0c;分辨率是800*480&#xff0c;程序写比较粗糙…

Qt QGraphicsScene、QGraphicsView类实现仪表盘

Qt QGraphicsScene、QGraphicsView类实现仪表盘 【1】UI界面设计【2】效果【3】QGraphicsScene简介【4】QGraphicsEllipseItem简介【5】QGraphicsPolygonItem简介【6】QGraphicsLineItem简介【7】QGraphicsView简介【8】仪表源码头文件源码 【1】UI界面设计 【2】效果 【3】QGr…

qt QGraphicsScene 简单例子

坐标 graphicsView是Widget这个界面的子控件&#xff0c; 他的位置是左上角坐标为&#xff08;30,30&#xff09; 他的宽度为431&#xff0c;高度为431. 如果在graphicsView 这个子控件中&#xff0c; 继续加入 子控件A&#xff0c;由于graphicsView比较特殊。 根据它的align…