美文网首页
2018-01-07

2018-01-07

作者: 习生云 | 来源:发表于2018-11-11 23:40 被阅读0次

    Android中的SurfaceView详解

     

    冰鉴IT 关注

    2017.02.11 21:52* 字数 1261 阅读 7664评论 4喜欢 56

    Android中提供了View进行绘图处理,View可以满足大部分的绘图需求,但是有时候,View却显得力不从心,所以Android提供了SurfaceView给Android开发者,以满足更多的绘图需求。下面就让我们一起来了解一下SurfaceView。

    一、为什么要使用SurfaceView

    我们知道View是通过刷新来重绘视图,系统通过发出VSSYNC信号来进行屏幕的重绘,刷新的时间间隔是16ms,如果我们可以在16ms以内将绘制工作完成,则没有任何问题,如果我们绘制过程逻辑很复杂,并且我们的界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验,为此Android提供了SurfaceView来解决这一问题。

    View和SurfaceView的区别:

    1 . View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。

    2 . View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。

    3 . View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。

    这摘录了一段网上对于双缓冲技术的介绍

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

    二、如何使用SurfaceView

    要想使用SurfaceView需要经过创建、初始化、使用三个步骤,下面我们就一步步来说说这三个步骤。

    1 . 创建SurfaceView

    我们需要自定义一个类继承自SurfaceView,并且实现两个接口以及接口定义的方法,当然,与自定义View类似,还要重写三个构造函数。下面是代码:

    publicclassSurfaceViewTemplateextendsSurfaceViewimplementsSurfaceHolder.Callback,Runnable{publicSurfaceViewTemplate(Context context){this(context,null);    }publicSurfaceViewTemplate(Context context, AttributeSet attrs){this(context, attrs,0);    }publicSurfaceViewTemplate(Context context, AttributeSet attrs,intdefStyleAttr){super(context, attrs, defStyleAttr);    }@OverridepublicvoidsurfaceCreated(SurfaceHolder holder){//创建}@OverridepublicvoidsurfaceChanged(SurfaceHolder holder,intformat,intwidth,intheight){//改变}@OverridepublicvoidsurfaceDestroyed(SurfaceHolder holder){//销毁}@Overridepublicvoidrun(){//子线程}}

    前面三个构造函数的写法和自定义View是相同的,接下来的三个方法分别在SurfaceView创建、改变、销毁的时候进行调用,最后的run()方法中写我们子线程中执行的绘图逻辑即可。

    2 . 初始化SurfaceView

    这一步我们主要是定义三个成员变量以备后面绘图时使用,然后初始化这三个成员变量并且注册对应的回调方法。代码如下:

    privateSurfaceHolder mSurfaceHolder;//绘图的CanvasprivateCanvas mCanvas;//子线程标志位privatebooleanmIsDrawing;/**

    * 初始化View

    */privatevoidinitView(){    mSurfaceHolder = getHolder();//注册回调方法mSurfaceHolder.addCallback(this);//设置一些参数方便后面绘图setFocusable(true);    setKeepScreenOn(true);    setFocusableInTouchMode(true);}publicSurfaceViewSinFun(Context context, AttributeSet attrs,intdefStyleAttr){super(context, attrs, defStyleAttr);//在三个参数的构造方法中完成初始化操作initView();}

    上面的代码很简单,都有注释,相信很容易看懂,这里不再过多解释。

    3 . 使用SurfaceView

    经过上面两步的准备工作,下面就可以开始使用SurfaceView了。

    这一步又可以分为3步来完成:

    (1) 通过lockCanvas()方法获得Canvas对象

    (2) 在子线程中使用Canvas对象进行绘制

    (3) 使用unlockCanvasAndPost()方法将画布内容进行提交

    注意: lockCanvas() 方法获得的Canvas对象仍然是上次绘制的对象,由于我们是不断进行绘制,但是每次得到的Canvas对象都是第一次创建的Canvas对象。

    SurfaceView的绘制可以使用下面的模板代码来实现,唯一的不同就是绘制逻辑的不同,代码如下:

    publicclassSurfaceViewTemplateextendsSurfaceViewimplementsSurfaceHolder.Callback,Runnable{privateSurfaceHolder mSurfaceHolder;//绘图的CanvasprivateCanvas mCanvas;//子线程标志位privatebooleanmIsDrawing;publicSurfaceViewTemplate(Context context){this(context,null);    }publicSurfaceViewTemplate(Context context, AttributeSet attrs){this(context, attrs,0);    }publicSurfaceViewTemplate(Context context, AttributeSet attrs,intdefStyleAttr){super(context, attrs, defStyleAttr);        initView();    }@OverridepublicvoidsurfaceCreated(SurfaceHolder holder){        mIsDrawing =true;//开启子线程newThread(this).start();    }@OverridepublicvoidsurfaceChanged(SurfaceHolder holder,intformat,intwidth,intheight){    }@OverridepublicvoidsurfaceDestroyed(SurfaceHolder holder){        mIsDrawing =false;    }@Overridepublicvoidrun(){while(mIsDrawing){            drawSomething();        }    }//绘图逻辑privatevoiddrawSomething(){try{//获得canvas对象mCanvas = mSurfaceHolder.lockCanvas();//绘制背景mCanvas.drawColor(Color.WHITE);//绘图}catch(Exception e){        }finally{if(mCanvas !=null){//释放canvas对象并提交画布mSurfaceHolder.unlockCanvasAndPost(mCanvas);            }        }    }/**

        * 初始化View

        */privatevoidinitView(){        mSurfaceHolder = getHolder();        mSurfaceHolder.addCallback(this);        setFocusable(true);        setKeepScreenOn(true);        setFocusableInTouchMode(true);    }}

    我们在xml文件中的使用和自定义View是相同的,使用全路径名称即可:

    三、SurfaceView小案例

    下面我们通过两个小案例来展示SurfaceView的使用。先放上效果图

    正弦曲线

    手写板

    1 . 绘制正弦曲线

    大体的框架都是上面给的那个代码模板,区别只在于初始化画笔,和具体的绘图逻辑,所以这里不再赘述,直接上代码:

    publicclassSurfaceViewSinFunextendsSurfaceViewimplementsSurfaceHolder.Callback,Runnable{privateSurfaceHolder mSurfaceHolder;//绘图的CanvasprivateCanvas mCanvas;//子线程标志位privatebooleanmIsDrawing;privateintx =0, y =0;privatePaint mPaint;privatePath mPath;publicSurfaceViewSinFun(Context context){this(context,null);    }publicSurfaceViewSinFun(Context context, AttributeSet attrs){this(context, attrs,0);    }publicSurfaceViewSinFun(Context context, AttributeSet attrs,intdefStyleAttr){super(context, attrs, defStyleAttr);        mPaint =newPaint();        mPaint.setColor(Color.BLACK);        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setAntiAlias(true);        mPaint.setStrokeWidth(5);        mPath =newPath();//路径起始点(0, 100)mPath.moveTo(0,100);        initView();    }@OverridepublicvoidsurfaceCreated(SurfaceHolder holder){        mIsDrawing =true;newThread(this).start();    }@OverridepublicvoidsurfaceChanged(SurfaceHolder holder,intformat,intwidth,intheight){    }@OverridepublicvoidsurfaceDestroyed(SurfaceHolder holder){        mIsDrawing =false;    }@Overridepublicvoidrun(){while(mIsDrawing){            drawSomething();            x +=1;            y = (int)(100* Math.sin(2* x * Math.PI /180) +400);//加入新的坐标点mPath.lineTo(x, y);        }    }privatevoiddrawSomething(){try{//获得canvas对象mCanvas = mSurfaceHolder.lockCanvas();//绘制背景mCanvas.drawColor(Color.WHITE);//绘制路径mCanvas.drawPath(mPath, mPaint);        }catch(Exception e){        }finally{if(mCanvas !=null){//释放canvas对象并提交画布mSurfaceHolder.unlockCanvasAndPost(mCanvas);            }        }    }/**

        * 初始化View

        */privatevoidinitView(){        mSurfaceHolder = getHolder();        mSurfaceHolder.addCallback(this);        setFocusable(true);        setKeepScreenOn(true);        setFocusableInTouchMode(true);    }}

    2 . 手写板(随手指绘制轨迹)

    这个主要是涉及到触摸事件,在手指按下时将Path的起始点移动到按下的坐标点,手指移动时将移动的坐标点加入Path中,其他的代码是相同的。代码如下:

    publicclassSurfaceViewHandWritingextendsSurfaceViewimplementsSurfaceHolder.Callback,Runnable{privateSurfaceHolder mSurfaceHolder;//绘图的CanvasprivateCanvas mCanvas;//子线程标志位privatebooleanmIsDrawing;//画笔privatePaint mPaint;//路径privatePath mPath;privatestaticfinalString TAG ="pyh";publicSurfaceViewHandWriting(Context context){this(context,null);    }publicSurfaceViewHandWriting(Context context, AttributeSet attrs){this(context, attrs,0);    }publicSurfaceViewHandWriting(Context context, AttributeSet attrs,intdefStyleAttr){super(context, attrs, defStyleAttr);        mPaint =newPaint();        mPaint.setColor(Color.BLACK);        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setStrokeWidth(5);        mPaint.setAntiAlias(true);        mPath =newPath();        mPath.moveTo(0,100);        initView();    }@OverridepublicvoidsurfaceCreated(SurfaceHolder holder){        mIsDrawing =true;newThread(this).start();    }@OverridepublicvoidsurfaceChanged(SurfaceHolder holder,intformat,intwidth,intheight){    }@OverridepublicvoidsurfaceDestroyed(SurfaceHolder holder){        mIsDrawing =false;    }@Overridepublicvoidrun(){while(mIsDrawing) {longstart = System.currentTimeMillis();            drawSomething();longend = System.currentTimeMillis();if(end - start <100) {try{                    Thread.sleep(100- (end - start));                }catch(InterruptedException e) {                    e.printStackTrace();                }            }        }    }@OverridepublicbooleanonTouchEvent(MotionEvent event){intx = (int) event.getX();inty = (int) event.getY();switch(event.getAction()){caseMotionEvent.ACTION_DOWN:                mPath.moveTo(x, y);break;caseMotionEvent.ACTION_MOVE:                mPath.lineTo(x, y);break;caseMotionEvent.ACTION_UP:break;        }returntrue;    }/**

        * 初始化View

        */privatevoidinitView(){        mSurfaceHolder = getHolder();        mSurfaceHolder.addCallback(this);        setFocusable(true);        setKeepScreenOn(true);        setFocusableInTouchMode(true);    }privatevoiddrawSomething(){try{//获得canvas对象mCanvas = mSurfaceHolder.lockCanvas();//绘制背景mCanvas.drawColor(Color.WHITE);//绘制路径mCanvas.drawPath(mPath, mPaint);        }catch(Exception e){        }finally{if(mCanvas !=null){//释放canvas对象并提交画布mSurfaceHolder.unlockCanvasAndPost(mCanvas);            }        }    }}

    上面还有一个细节,在绘制的时候,我们并没有让线程一直运行,而是让它休眠一会,从而节约系统资源,一般建议判断的阈值为50-100之间即可保证用户体验同时节约系统资源。

    以上就是SurfaceView的使用介绍,希望可以帮助到需要的人。

    相关文章

      网友评论

          本文标题:2018-01-07

          本文链接:https://www.haomeiwen.com/subject/ajymnxtx.html