Android(安卓)SurfaceView使用详解(很好的实战例子)
一、 surfaceview 主要作用
(1)视频输出的屏幕。(这个用得比较多)
(2)绘制图像
二、注意事项
surfaceCreated有特定的生命周期,注意在callback中进行操作。(具体请看例子)
三、清屏操作(绘制图像)
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 exception e.printStackTrace(); }finally { if(canvas != null) { holder.unlockCanvasAndPost(canvas); } }}
------------------------------------------------- 正文 ----------------------------------
一、视频输出的屏幕
(1)设置surfaceview的参数
(2)在SurfaceCallback方法进行视频的播放控制。播放控制时,一般会进行播放器与surfaceview的绑定操作(如下)。注意这个getHolder。无论是surfaceview 还是TextureView都有这个holder。是视频输出的 关键。
mediaPlayer.setDisplay(surfaceView.getHolder());
代码:
public class MainActivity extends Activity { private EditText nameText; private String path; private MediaPlayer mediaPlayer; private SurfaceView surfaceView; private boolean pause; private int position; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mediaPlayer = new MediaPlayer(); nameText = (EditText) this.findViewById(R.id.filename); surfaceView = (SurfaceView) this.findViewById(R.id.surfaceView); //把输送给surfaceView的视频画面,直接显示到屏幕上,不要维持它自身的缓冲区 surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); surfaceView.getHolder().setFixedSize(176, 144); surfaceView.getHolder().setKeepScreenOn(true); surfaceView.getHolder().addCallback(new SurfaceCallback()); } private final class SurfaceCallback implements Callback{ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } public void surfaceCreated(SurfaceHolder holder) { if(position>0 && path!=null){ play(position); position = 0; } } public void surfaceDestroyed(SurfaceHolder holder) { if(mediaPlayer.isPlaying()){ position = mediaPlayer.getCurrentPosition(); mediaPlayer.stop(); } } } @Override protected void onDestroy() { mediaPlayer.release(); mediaPlayer = null; super.onDestroy(); } public void mediaplay(View v){ switch (v.getId()) { case R.id.playbutton: String filename = nameText.getText().toString(); if(filename.startsWith("http")){ path = filename; play(0); }else{ File file = new File(Environment.getExternalStorageDirectory(), filename); if(file.exists()){ path = file.getAbsolutePath(); play(0); }else{ path = null; Toast.makeText(this, R.string.filenoexsit, 1).show(); } } break; case R.id.pausebutton: if(mediaPlayer.isPlaying()){ mediaPlayer.pause(); pause = true; }else{ if(pause){ mediaPlayer.start(); pause = false; } } break; case R.id.resetbutton: if(mediaPlayer.isPlaying()){ mediaPlayer.seekTo(0); }else{ if(path!=null){ play(0); } } break; case R.id.stopbutton: if(mediaPlayer.isPlaying()){ mediaPlayer.stop(); } break; } } private void play(int position) { try { mediaPlayer.reset(); mediaPlayer.setDataSource(path); mediaPlayer.setDisplay(surfaceView.getHolder()); mediaPlayer.prepare();//缓冲 mediaPlayer.setOnPreparedListener(new PrepareListener(position)); } catch (Exception e) { e.printStackTrace(); } } private final class PrepareListener implements OnPreparedListener{ private int position; public PrepareListener(int position) { this.position = position; } public void onPrepared(MediaPlayer mp) { mediaPlayer.start();//播放视频 if(position>0) mediaPlayer.seekTo(position); } }}
-----------------------------------------------------------------------------------
二、绘制图像
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的源码。
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 |
|
FrameAnimation根据每一帧的显示时间返回当前的图片帧,若没有超过指定的时间则继续返回当前帧,否则返回下一帧。
接下来需要做的是让Activty显示的View为我们之前创建的GameView,然后设置全屏显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
现在运行Android工程,应该就可以看到一个手持宝剑的武士在沿着屏幕不停的走了。
更多相关文章
- Android的图形与图像处理之一 使用简单图片&绘图
- Android(安卓)OpenGL 纹理绘制图像---Native实现
- [Android]MPAndroidChart的实战使用讲解
- 《你知道android的MessageQueue.IdleHandler吗?》
- Android自定义控件实战——滚动选择器PickerView
- 继承Animation自定义动画
- Android滑动动画ViewFlipper简单使用
- 学Android(安卓)Space控件,只看这篇文章就行了
- 针对android游戏截图分享