android之surfaceView学习
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android之surfaceView学习相关的知识,希望对你有一定的参考价值。
最近做项目遇到一个问题,需要在屏幕上实时的显示手的坐标,这样话就涉及到一个实时画图的问题了。对于实时更新UI这个问题,懂点android的都知道,android的UI更新都需要在主线程中更新,但是如果将一个实时绘图的操作放在主线程,必定会出现阻塞主线程的问题,即便是不阻塞主线程,也会降低程序运行的速度。因此,我就想到android的一个控件,surfaceView。这个控件在官网上是这样描述的:
SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface。你可以控制这个Surface的格式和尺寸。Surfaceview控制这个Surface的绘制位置。
surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。surfaceview提供了一个可见区域,只有在这个可见区域内 的surface部分内容才可见,可见区域外的部分不可见。surface的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。这意味者 surface的内容会被它的兄弟视图遮挡,这一特性可以用来放置遮盖物(overlays)(例如,文本和按钮等控件)。注意,如果surface上面 有透明控件,那么它的每次变化都会引起框架重新计算它和顶层控件的透明效果,这会影响性能。
你可以通过SurfaceHolder接口访问这个surface,getHolder()方法可以得到这个接口。
surfaceview变得可见时,surface被创建;surfaceview隐藏前,surface被销毁。这样能节省资源。如果你要查看 surface被创建和销毁的时机,可以重载surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)。
surfaceview的核心在于提供了两个线程:UI线程和渲染线程。这里应注意:
1> 所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程。渲染线程所要访问的各种变量应该作同步处理。
2> 由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的surface。
通过这段话,我们可以知道,surfaceView提供了UI线程。可以自己更新UI,因此,这样我们在surfaceView中进行实时的绘画,然后通过更改其中的绘画的数据,既可以实现我们想要的实时的更新UI的这个问题了,并且消耗较小的资源。那么接下来我们来具体谈下实现的问题。
那么如何使用surfaceView实现我们的需求呢?首先我们需要自定义一个控件,并且继承surfaceView这个类。并且要在其中实现SurfaceHolder.Callback的接口。之所以实现接口,是因为我们要确保我们所做的操作需要在surfaceView创建后才能开始。然后我们来具体看下这个函数需要实现的三个函数。
new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { //创建时需要执行的操作 } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { //surfaceView大小改变时需要执行的操作 } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { //销毁时进行的操作。 } }
然后我们如果要绘制内容,并且实时的更新,需要在surfaceCreated中开一个线程,然后实时的进行绘制。这样即完成我们所需要的需求了。但是在绘制的时候需要有一些注意的地方。这个在接下来呈现出代码后我们来具体分析和具体的看,接下来是自定义view的具体的代码。我们来具体的看下。
public class MyView extends SurfaceView { private final String TAG = "acmTest"; private SurfaceHolder mSurfaceHolder; private Canvas mCanvas;//画布 public boolean isRunning;//用来标识是否开启绘画的线程。 Context context; int positionX;//所绘制图形的x坐标 int positionY;//所绘制图形的y坐标 public MyView(Context context) { super(context); init(); } public MyView(Context context, AttributeSet attributeSet) { super(context,attributeSet); this.context = context; init(); } //初始化surfaceView,在这个函数中,实现surfaceHolder.Callback public void init() { setBackgroundColor(Color.parseColor("#6A14b7f5"));//设置surfaceView的背景颜色 setZOrderOnTop(true);//使surfaceview放到最顶层 getHolder().setFormat(PixelFormat.TRANSLUCENT);//使窗口支持透明度 mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { //启动开始绘画的线程,这个线程中的函数不需要再在主线程中执行,可以直接执行并更新UI。 new Thread(new Runnable() { @Override public void run() { while (isRunning) { drawPoint(); } } }).start(); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { } }); } //具体绘画的操作 public void drawPoint() { //需要注意这里需要通过这个方式获得canvas,不能够自己申明,为了保证从始至终用的一个canvas。 mCanvas = mSurfaceHolder.lockCanvas(); if (mCanvas != null) { try { mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//是清理上次画的内容,这个根据需求选择是否添加。因为我的需求是跟踪位置,如果不加,会将上次所画的内容保留。 Paint paint = new Paint();//声明画笔,在此基础上进行绘制 paint.setColor(Color.RED);//为红色画笔 mCanvas.drawCircle(positionX,positionY,10,paint); } catch (Exception e) { e.printStackTrace(); } finally { mSurfaceHolder.unlockCanvasAndPost(mCanvas);//提交后会将提交的画布更新,然后显示 } } } public void setPosition(int positionX,int positionY) { this.positionX = positionX; this.positionY = positionY; } }
这是通过继承surfaceView来进行实现实时的更新UI,然后占用少量内存。值得注意的时候,我们在初始化的时候需要考虑啊init的前三行代码,第一个设置背景颜色,不要再xml中设置,需要在这设置,否则默认背景为纯黑的。然后后两句是支持为透明的颜色,和保持能够在布局的最上方。
还有一点就是在获取画布这,需要严格的按照规定,先获取,再提交更新,在其中不可重新声明,并且提交和获取的需要为同一个canvas。
以上是关于android之surfaceView学习的主要内容,如果未能解决你的问题,请参考以下文章
Android 音视频开发之基础篇 使用 SurfaceView绘制一张图片