Android中SurfaceView简单使用

1.什么是SurfaceView?

    表面意为表层,表面,顾名思义SurfaceView就是指一个在表层的视图对象。为什么说是在表层呢,这是因为它有点特殊跟其他搜索不一样,其他视图是绘制在“表层”的上面,而它就是充当“表层”本身.SDK的文档说到:SurfaceView就是在窗口上挖一个洞,它就是显示在这个洞里,所以的视图是显示在窗口上,所以查看可以显式在SurfaceView之上,你也可以添加一些层在SurfaceView之上。从API中可以看出SurfaceView属于View的子类它是专门为制作游戏而产生的,它的功能非常强大,最重要的是它支持OpenGL ES库,2D和创建SurfaceView的时候需要实现SurfaceHolder.Callback接口,它可以用来监听SurfaceView的状态,比如:SurfaceView的改变,SurfaceView的创建,SurfaceView销毁等,我们可以在相应的方法中做一些比如初始化的操作或者清空的操作等等。

Android的系统提供了视图进行绘图处理,我们通过自定义的视图可以满足大部分的绘图需求,但是这有个问题就是我们通常自定义的视图是用于主动更新情况的,用户无法控制其绘制的速度,由于视图是通过无效方法通知系统去调用view.onDraw方法进行重绘,而安卓系统是通过发出VSYNC信号来进行屏幕的重绘,刷新的时间是16毫秒,如果在16毫秒内搜索完成不了执行的操作,用户就会看着卡顿,比如当绘制方法里执行的逻辑过多,需要频繁刷新的界面上,例如游戏界面,那么就会不断的阻塞主线程,从而导致画面卡顿。而SurfaceView相当于是另一个绘图线程,它是不会阻碍主线程,并且它在底层实现机制中实现了双缓冲机制。

2.如何使用SurfaceView?
        首先SurfaceView也是一个View,它也有自己的生命周期。因为它需要另外一个线程来执行绘制操作,所以我们可以在它生命周期的初始化阶段开辟一个新线程,然后开始执行绘制,当生命周期的结束阶段我们插入结束绘制线程的操作。这些是由其内部一个SurfaceHolder对象完成的。  

SurfaceView它的绘制原理是绘制前先锁定画布(获取画布),然后等都绘制结束以后在对画布进行解锁,最后在把画布内容显示到屏幕上。       

通常情况下,使用以下步骤来创建一个SurfaceView的模板:

(1)创建SurfaceView

创建自定义的SurfaceView继承自SurfaceView,并实现两个接口:SurfaceHolder.Callback和Runnable。代码如下:


public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable

通过实现这两个接口,就需要在自定义的SurfaceView中实现接口的方法,对于SurfaceHolder.Callback方法,需要实现如下方法,其实就是SurfaceView的生命周期:

   @Override
    public void surfaceCreated(SurfaceHolder holder) {
      
 
    }
   @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 
    }
 
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
   
    }

对于Runnable接口,需要实现run()方法,.

 @Override
    public void run() {
 
}

(2)初始化SurfaceView

在自定义的MySurfaceView的构造方法中,需要对SurfaceView进行初始化,包括SurfaceHolder的初始化、画笔的初始化等。在自定义的SurfaceView中,通常需要定义以下三个成员变量:

    private SurfaceHolder mHolder;
    private Canvas mCanvas;//绘图的画布
    private boolean mIsDrawing;//控制绘画线程的标志位

 SurfaceHolder,顾名思义,它里面保存了一个对Surface对象的引用,而我们执行绘制方法本质上就是操控Surface。SurfaceHolder因为保存了对Surface的引用,所以使用它来处理Surface的生命周期。(说到底 SurfaceView的生命周期其实就是Surface的生命周期)例如使用 SurfaceHolder来处理生命周期的初始化。

(3)使用SurfaceView
通过SurfaceHolder对象的lockCanvans()方法,我们可以获取当前的Canvas绘图对象。接下来的操作就和自定义View中的绘图操作一样了。需要注意的是这里获取到的Canvas对象还是继续上次的Canvas对象,而不是一个新的对象。因此,之前的绘图操作都会被保留,如果需要擦除,则可以在绘制前,通过drawColor()方法来进行清屏操作。

绘制的时候,在surfaceCreated()方法中开启子线程进行绘制,而子线程使用一个while(mIsDrawing)的循环来不停的进行绘制,在绘制的逻辑中通过lockCanvas()方法获取Canvas对象进行绘制,通过unlockCanvasAndPost(mCanvas)方法对画布内容进行提交。整体代码模板如下:

public class MyView extends SurfaceView implements SurfaceHolder.Callback {
    private Paint paint;//声明一个画笔
    public MyView(Context context) {
        super(context);
        paint=new Paint();//实例化画笔对象
        paint.setColor(Color.RED);

        // 为SurfaceHolder添加一个SurfaceHolder.Callback回调接口
        getHolder().addCallback(this);
    }

    public void draw() {

        Canvas canvas=getHolder().lockCanvas();//图形绘制之前锁定画布
        canvas.drawColor(Color.WHITE);//画布为白色
        canvas.save();//保存画布的状态
        canvas.drawRect(getWidth()/2,getHeight()/2,100,100,paint);
        //getWidth():获取view的宽度   drawRect画矩形
        canvas.restore();//重新使用画布
        canvas.rotate(90,getWidth()/2,getHeight()/2);//将线绕view中心旋转90度
        canvas.drawLine(0,getHeight()/2,getWidth(),getHeight(),paint);//画线
        getHolder().unlockCanvasAndPost(canvas);//图形绘制完之后解锁画布

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        draw();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
}

这里说一个优化的地方,这就是在run方法中。
在我们的draw()方法每一次更新所耗费的时间是不确定的。举个例子 比如第一次循环draw() 耗费了1000毫秒 ,第二次循环draw() 耗时2000毫秒。很明显这样就会造成运行刷新时间时快时慢,可能出现卡顿现象。为此最好保证每次刷新的时间是相同的,这样可以保证整体画面过渡流畅。

 	/**每30帧刷新一次屏幕**/
        public static final int TIME_IN_FRAME = 30;
	@Override
	public void run() {
	    while (mIsRunning) {
 
		/**取得更新之前的时间**/
		long startTime = System.currentTimeMillis();
 
		/**在这里加上线程安全锁**/
		synchronized (mSurfaceHolder) {
		    /**拿到当前画布 然后锁定**/
		    mCanvas =mSurfaceHolder.lockCanvas();
		    draw();
		    /**绘制结束后解锁显示在屏幕上**/
		    mSurfaceHolder.unlockCanvasAndPost(mCanvas);
		}
 
		/**取得更新结束的时间**/
		long endTime = System.currentTimeMillis();
 
		/**计算出一次更新的毫秒数**/
		int diffTime  = (int)(endTime - startTime);
 
		/**确保每次更新时间为30帧**/
		while(diffTime <=TIME_IN_FRAME) {
		    diffTime = (int)(System.currentTimeMillis() - startTime);
		    /**线程等待**/
		    Thread.yield();
		}
 
	    }
	}

这里说一下Thread.yield(): 与Thread.sleep(long millis):的区别:
Thread.yield(): 是暂停当前正在执行的线程对象 ,并去执行其他线程。

Thread.sleep(long millis):则是使当前线程暂停参数中所指定的毫秒数然后在继续执行线程。

3.SurfaceView的使用实例
(1)正弦曲线

要绘制一个正弦曲线,只需要不断修改横纵座标的值,并让他们满足正弦函数即可。因此,我们需要一个Path对象来保存正弦函数上的座标点,在子线程的while循环中,不断改变横纵座标值。代码如下:

public static final int TIME_IN_FRAME = 30;
    @Override
    public void run() {
        long startTime = System.currentTimeMillis();
         while(mIsDrawing){
             draw();
//             x+=1;
//             y=(int)(100*Math.sin(x*2*Math.PI/180)+400);
//             mPath.lineTo(x,y);
         }
        
        /**取得更新结束的时间**/
        long endTime = System.currentTimeMillis();
 
        /**计算出一次更新的毫秒数**/
        int diffTime  = (int)(endTime - startTime);
 
        /**确保每次更新时间为30帧**/
        while(diffTime <=TIME_IN_FRAME) {
            diffTime = (int)(System.currentTimeMillis() - startTime);
            /**线程等待**/
            Thread.yield();
        }
    }

(2)画图板

我们也可以通过使用SurfaceView来实现一个简单的绘图板,绘图的方法与View中进行绘图所使用的方法一样,也是通过Path对象记录手指滑动的路径来进行绘图。在SurfaceView的onTouchEvent()方法中记录Path路径,代码如下所示:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x=(int)event.getX();
        int y=(int)event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mPath.moveTo(x,y);
                break;
            case MotionEvent.ACTION_MOVE:
                mPath.lineTo(x,y);
                break;
            case MotionEvent.ACTION_UP:
                break;
 
        }
        return true;//表示此View拦截处理触摸事件
    }

并在draw方法中进行绘制:

 private void draw() {
        try{
            mCanvas=mHolder.lockCanvas();//获取Canvas对象进行绘制
            //SurfaceView背景
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath,mPaint);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (mCanvas!=null){
                mHolder.unlockCanvasAndPost(mCanvas);//保证绘制的画布内容提交
            }
        }
    }

4.SurfaceView和View的区别
总的归纳起来SurfaceView和View不同之处有:

1. SurfaceView允许其他线程更新视图对象(执行绘制方法)而View不允许这么做,它只允许UI线程更新视图对象。

2. SurfaceView是放在其他最底层的视图层次中,所有其他视图层都在它上面,所以在它之上可以添加一些层,而且它不能是透明的。

3. 它执行动画的效率比View高,而且你可以控制帧数。

4. SurfaceView在绘图时使用l了双缓冲机制,而View没有。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章