213  
查询码:00000967
SurfaceView和普通view的区别及简单使用
来源:https://blog.csdn.net/u010126792/article/details/86249399
作者: 朱凡 于 2021年05月18日 发布在分类 / FM组 / FM_App 下,并于 2021年05月18日 编辑
surfaceview android surface 一个 mediaplayer surfaceholder 学习 线程 使用 绘图

SurfaceView和普通view的区别及简单使用


1 SurfaceView介绍

在这里插入图片描述
SurfaceView第一印象它是一个view,因为它继承了View,有两个直接子类GLSurfaceView,VideoView。但根据SDK文档SurfaceView和普通的view又有较大区别。

最显著的区别就是普通view和它的宿主窗口共享一个绘图表面(Surface),SurfaceView虽然也在View的树形结构中,但是它有属于自己的绘图表面,Surface 内部持有一个Canvas,可以利用这个Canvas绘制。

SurfaceView提供一个直接的绘图表面(Surface)嵌入到视图结构层次中。你可以控制这个Surface的格式,大小,SurfaceView负责在屏幕上正确的摆放Surface。简单说就是SurfaceView拥有自己的Surface,它与宿主窗口是分离的。
我们知道窗口中的view共享一个window,window又对应一个Surface,所以窗口中的view共享一个Surface,而SurfaceView拥有自己的Surface。SurfaceView会创建一个置于应用窗口之后的新窗口,SurfaceView相当于在Window上挖一个洞,它就是显示在这个洞里,其他的View是显示在Window上,所以View可以显示在 SurfaceView之上,也可以添加一些层在SurfaceView之上。

SurfaceView的窗口刷新的时候不需要重绘应用程序的窗口而android普通窗口的视图绘制机制是一层一层的,任何一个子元素或者是局部的刷新都会导致整个视图结构全部重绘一次。

对于普通的view,Android中的窗口界面包括多个View组成的View Hierachy的树形结构,只有最顶层的DecorView才对WMS可见,这个DecorView在WMS中有一个对应的WindowState,此时APP请求创建Surface时,会在SurfaceFlinger内部建立对应的Layer。而对于SurfaceView它自带一个Surface,这个Surface在WMS有自己对应的WindowState,在SurfaceFlinger中有自己对应的layer。SurfaceView从APP端看它仍然在View hierachy结构中,但在WMS和SurfaceFlinger中它与宿主窗口是分离的。因此SurfaceView的Surface的渲染可以放到单独线程去做,不会影响主线程对事件的响应。但因为这个Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换(对SurfaceView进行ScrollBy,ScrollTo操作没有效果(还有透明度,旋转),普通View进行平移操作,内部内容会移动,SurfaceView进行这些操作内部不会移动。如果对包裹它的ViewGroup进行平移旋转等操作,也无法达到我们想要的效果。同时SurfaceView不能放在类似RecyclerView或ScrollView中,一些View中的特性也无法使用。
SurfaceView不支持平移,缩放,旋转等动画,但是当我们利用SurfaceView测试这些不支持的动画时,如果使用的是7.0 甚至更高版本的Android系统,会发现SurfaceView也支持平移,缩放的动画操作。

View和SurfaceView的区别:
View适用主动更新,SurfaceView 适用被动更新,如频繁的刷新
View在UI线程更新,在非UI线程更新会报错,当在主线程更新view时如果耗时过长也会出错, SurfaceView在子线程刷新不会阻塞主线程,适用于界面频繁更新、对帧率要求较高的情况。
SurfaceView可以控制刷新频率。
SurfaceView底层利用双缓存机制,绘图时不会出现闪烁问题。

双缓冲技术是游戏开发中的一个重要的技术,主要是为了解决 反复局部刷屏带来的闪烁。游戏,视频等画面变化较频繁,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。双缓冲技术会把要处理的图片在内存中处理好之后,把要画的东西先画到一个内存区域里,然后整体的一次性画出来,将其显示在屏幕上。

2 SurfaceView 使用步骤

使用SurfaceView的步骤:

  • 首先要继承SurfaceView,实现SurfaceHolder.Callback接口。

  • 重写方法:
    surfaceChanged:surface大小或格式发生变化时触发,在surfaceCreated调用后该函数至少会被调用一次。
    surfaceCreated:Surface创建时触发,一般在这个函数开启绘图线程(新的线程,不要再这个线程中绘制Surface)。
    surfaceDestroyed:销毁时触发,一般不可见时就会销毁。

  • 利用getHolder()获取SurfaceHolder对象,调用SurfaceHolder.addCallback添加回调

  • SurfaceHolder.lockCanvas 获取Canvas对象并锁定画布,调用Canvas绘图,SurfaceHolder.unlockCanvasAndPost 结束锁定画布,提交改变。

3 SurfaceHolder

SurfaceView的双缓冲的机制非常消耗系统内存,Android规定SurfaceView不可见时,会立即销毁SurfaceView的SurfaceHolder,以达到节约系统资源的目的,所以需要利用SurfaceHolder的回调函数对SurfaceHolder进行维护。
提供了三个回调函数让我们知道SurfaceHolder的创建、销毁或者改变
void surfaceDestroyed(SurfaceHolder holder):当SurfaceHolder被销毁的时候回调。
void surfaceCreated(SurfaceHolder holder):当SurfaceHolder被创建的时候回调。
void surfaceChange(SurfaceHolder holder):当SurfaceHolder的尺寸或格式发生变化的时候被回调。

abstract Canvas lockCanvas():
获取一个Canvas对象,并锁定,得到的Canvas对象,就是Surface中一个成员。

** abstract Canvas lockCanvas(Rectdirty):**
仅仅锁定dirty所指定的矩形区域。

abstract void unlockCanvasAndPost(Canvascanvas)
当改动Surface中的数据后,释放同步锁,并提交改变,然后将新的数据进行展示,同一时候Surface中相关数据会被丢失。

public abstract void setType (int type):
设置Surface的类型,高版本中,setType这种方法已经被depreciated了,系统会自动设置。

  • SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
  • SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface
  • SURFACE_TYPE_GPU:适用于GPU加速的Surface
  • SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包括原生数据,Surface用到的数据由其它对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,生成图像更流畅。

兼容性:
SurfaceView的兼容性
  Android4.0以下SurfaceView不会自动维护缓冲区,播放视频时,如果使用SurfaceView开发游戏应用,就需要我们自己维护这个缓冲区了。

// 4.0版本之下需要设置的属性
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

4 SurfaceView的简单使用

绘制圆形:

public class SurfaceViewDemo extends SurfaceView implements SurfaceHolder.Callback{  private SurfaceHolder mSurfaceHolder; private Canvas mCanvas; private Paint paint; public SurfaceViewDemo(Context context) {  this(context,null,0); } public SurfaceViewDemo(Context context, AttributeSet attrs) {  this(context, attrs,0); } public SurfaceViewDemo(Context context, AttributeSet attrs, int defStyleAttr) {  super(context, attrs, defStyleAttr); init(); } private void init() {  mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.RED); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.STROKE); } @Override public void surfaceCreated(SurfaceHolder holder) {  System.out.println("=========surfaceCreated========"); new Thread(new Runnable() {  @Override public void run() {  draw(); } }).start(); } private void draw() {  try {  System.out.println("============draw========"); mCanvas = mSurfaceHolder.lockCanvas(); mCanvas.drawCircle(500,500,300,paint); mCanvas.drawCircle(100,100,20,paint); } catch (Exception e) {  e.printStackTrace(); } finally {  if (mCanvas != null) mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {  System.out.println("=========surfaceChanged========"); } @Override public void surfaceDestroyed(SurfaceHolder holder) {  System.out.println("=========surfaceDestroyed========"); } } 

在这里插入图片描述

回调函数的调用:
首次进入:
=surfaceCreated
=surfaceChanged
====draw
点击Home键:
=surfaceDestroyed

再次回到原来页面:
=surfaceCreated
=surfaceChanged
====draw

所以当SurfaceView不可见时会销毁SurfaceHolder,再次进入会重新调用surfaceCreated生成新的SurfaceHolder。surfaceChanged函数在surfaceCreated调用后该函数至少会被调用一次。

SurfaceView播放视频,不要忘记存储权限
mediaPlayer.setDisplay(getHolder());
视频资源为利用模拟器录制的mp4视频

public class SurfaceViewDemo2 extends SurfaceView implements SurfaceHolder.Callback{  private SurfaceHolder mSurfaceHolder; private Canvas mCanvas; private Paint paint; private MediaPlayer mediaPlayer; public SurfaceViewDemo2(Context context) {  this(context,null,0); } public SurfaceViewDemo2(Context context, AttributeSet attrs) {  this(context, attrs,0); } public SurfaceViewDemo2(Context context, AttributeSet attrs, int defStyleAttr) {  super(context, attrs, defStyleAttr); init(); } private void init() {  mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); setFocusable(true); setFocusableInTouchMode(true); this.setKeepScreenOn(true); setZOrderOnTop(true); paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.RED); paint.setStrokeWidth(5); paint.setStyle(Paint.Style.STROKE); } @Override public void surfaceCreated(SurfaceHolder holder) {  System.out.println("=========surfaceCreated========"); new Thread(new Runnable() {  @Override public void run() {  //draw(); play(); } }).start(); } private void draw() {  try {  System.out.println("============draw========"); mCanvas = mSurfaceHolder.lockCanvas(); mCanvas.drawCircle(500,500,300,paint); mCanvas.drawCircle(100,100,20,paint); } catch (Exception e) {  e.printStackTrace(); } finally {  if (mCanvas != null) mSurfaceHolder.unlockCanvasAndPost(mCanvas); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {  System.out.println("=========surfaceChanged========"); } @Override public void surfaceDestroyed(SurfaceHolder holder) {  System.out.println("=========surfaceDestroyed========"); if (mediaPlayer != null ){  stop(); } } protected void stop() {  if (mediaPlayer != null && mediaPlayer.isPlaying()) {  mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } } protected void play() {  String path = "/sdcard/DCIM/Camera/VID_20190110_102218.mp4"; File file = new File(path); if (!file.exists()) {  return; } try {  mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置播放的视频源 mediaPlayer.setDataSource(file.getAbsolutePath()); // 设置显示视频的SurfaceHolder mediaPlayer.setDisplay(getHolder()); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {  @Override public void onPrepared(MediaPlayer mp) {  mediaPlayer.start(); } }); mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {  @Override public void onCompletion(MediaPlayer mp) {  replay(); } }); mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {  @Override public boolean onError(MediaPlayer mp, int what, int extra) {  play(); return false; } }); } catch (Exception e) {  e.printStackTrace(); } } protected void replay() {  if (mediaPlayer!=null){  mediaPlayer.start(); }else{  play(); } } protected void pause() {  if (mediaPlayer != null && mediaPlayer.isPlaying()) {  mediaPlayer.pause(); }else{  mediaPlayer.start(); } } } 

图片7

补充 对SurfaceView进行平移,旋转等操作

添加 mSurfaceView.scrollBy(10,10);
效果如下:完全没有效果
在这里插入图片描述
对包裹它的viewgroup添加缩放动画。
mContainer.animate().scaleX(0.4f).scaleY(0.7f);
在这里插入图片描述
此时拍摄出来的照片为:

在这里插入图片描述
拍摄出来的照片完全没有受缩放的影响。




 推荐知识

 历史版本

修改日期 修改人 备注
2021-05-18 20:33:42[当前版本] 朱凡 创建版本

 附件

附件类型

GIFGIF PNGPNG

知识分享平台 -V 4.8.7 -wcp