Android Surface & Canvas简介

#.Surface

    是图形缓冲区(GraphicBuffer)的封装类,一般用作图像绘制的载体,例如用于承载View画面、承载SurfaceView画面、承载相机拍摄的图像、或者承载MediaCodec的输入/输出画面、承载ImageReader输入缓存画面等等。
     Surface都是双缓冲的,从概念上讲,可以简化理解为有两个缓冲区引用,一个frontBuffer和一个backBuffer,backBuffer指向后置缓冲区,用于缓存正在绘制的画面;而frontBuffer指向前置缓冲区,用于缓存最近绘制完毕、要提交使用的画面。  
    绘制中不断地循环这个过程:
    1.执行lockCanvas()后,获取Surface的Canvas对象,用于在backBuffer上进行绘制;
    2.在backBuffer上绘制完毕后,调用unlockCanvasAndPost(),互换二者身份,原来的backBuffer变为frontBuffer用于提交给外部使用,而原来的frontBuffer变为backBuffer去参与下一轮绘制。
    这里的“外部使用”所指代的,具体要看画面消费者是谁,例如当SurfaceFinger消费画面时,会被用于合成屏幕画面,最终输出到屏幕上;当MediaCodec编码器消费画面时,会被用于编码器编码,然后输出到编码器输出缓冲区。        
(大致过程可以这么理解,其实底层在利用BufferQueue来进行Buffer的分配和循环使用,具体细节跟上面会有一些差异,但一般情况下画面生产方速度不会快于画面消费方,BufferQueue中一般只有两个GraphicBuffer,效果的确是前后缓冲区互换索引。)
    
    

    其中第一步也可通过lockCanvas(Rect dirty)时可以传入一个脏区,这样在lockCanvas()中会把frontBuffer中“上一轮脏区-本轮脏区”那部分画面复制到backBuffer上,而绘制时只需要绘制本轮脏区范围对应的内容,没必要全部重新绘制。如果lockCanvas(null),则意味着本轮整个backBuffer范围都是脏区。这是Surface双缓冲特性的一个重要应用。

    不过Surface双缓冲设计的主要作用,应该是当Surface的画面生产方 与 画面消费方 异步工作时,始终能提供一个图形缓冲区(backBuffer)供生产方绘制,同时始终能提供一个图形缓冲区(frontBuffer)存储完整的画面供消费方消费。
 (如果是同步工作的话,其实一个缓冲区就能胜任工作流程,让生产方先在缓冲区上绘制完,然后提交给消费者消费,然后继续循环利用缓冲区就行了。
    无论lockCanvas()还是lockCanvas(Rect dirty),最终都会执行到nativeLockCanvas(),分配缓冲区和创建Canvas画布都是在native层完成的,会在native层用backBuffer指向的图形缓冲区来初始化SkBitmap,并作为画布创建SkCanvas返回给Java层。
    Android中,每个Window都对应一个Surface,Window上的所有View都在这个Surface上绘制,它们绘制的图像合在一起生成Window的最终图像 。而层层叠叠各个Window的图像最终被SurfaceFlinger服务整合在一起,形成屏幕最后要显示的图像。
    普通View绘制时,draw()/onDraw()获取到的Canvas都是从上层View树中传递过来的,而这个Canvas最初也是从Window对应的Surface中获取的。整个Window上的所有View都是在Surface的backBuffer上绘制,这些绘制操作必须在 UI 线程中进行
    至于SurfaceView,本身就直接管理一个独立的Surface,同时SurfaceView又属于某个View树,附在对应的Window上,所以它与两个Surface相关联。SurfaceView自己独自的Surface的显示层级较低,而View树所在Window的显示层级较高,所以前者在后者的下面。
    SurfaceView真实画面绘制在自己独自拥有的Surface上,这个绘制操作可以进行在非UI线程中。而这张Surface位于Window下面,是如何显示出来的呢?
    因为Window上的View树绘制时,SurfaceView也会参与,它会把Window的Surface上自己对应的区域绘制成透明色,于是Window下面SurfaceView独立的Surface就可以显示出来了。

#.Canvas

        直译为画布
        用于Android中绘制各种图形和文字,提供了绘制各种图形和内容的一系列API。
        会用一个Bitmap来保存像素点的信息,保存绘制结果。
        在调用一些列drawXXX()方法时,需要传入一个Paint对象,该对象相当于画笔,通过设置其属性值来确定画笔的特点。
      (不光可以通过Paint对象设置画笔特点,还可以用于丈量绘制出的文字宽度或产生一些特性。
##.常用的API大致分为三类:
1.通过一些列drawXXX()方法来绘制图形、文字或指定bitmap图片;
2.图形变化方法:平移、缩放、旋转、裁剪等方法;
3.状态保存和恢复:
    例如save()方法调用后,才会将前面进行的绘制或变化保存起来;而restore()调用后,会读取并恢复到最近一次save()后的状态。
##.其它补充
1.绘制普通图形用Paint对象做画笔,绘制文字用TextPaint对象做画笔,TextPaint继承自Paint,专门用于绘制文字。
2.如在View的onDraw()中绘制内容,当需要再次调用onDraw()中的逻辑绘制界面时,可调用View的invalidate()方法,触发其重新绘制界面。
    当不在View线程中,要触发View的重新绘制,可调用其postInvalidate()方法。
##.简单的示例代码
1.创建新的Canvas:    
        
Bitmap b = Bitmap.createBitmap(100,100, Bitmap.Config.ARGB_8888);  
Canvas c = new Canvas(b);   
2.覆写View的onDraw(Canvas canvas)自定义View的绘制内容:    
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        TextPaint textPaint = new TextPaint();
        textPaint.setColor(Color.RED);
        textPaint.setTextSize(48);
        textPaint.setTypeface(Typeface.DEFAULT_BOLD);
        canvas.drawText("普通一段文字", 100, 100, textPaint);
    }
3.稍复杂些的例子,绘制一行歌词,根据当前播放时间进度,已唱歌词绘制高亮颜色文字,未唱文字普通颜色
float startX = mPaddingLeft;
int textBaseline = (mViewHeight / 2 - mFontMetrics.bottom + mFontMetrics.top) / 2 - mFontMetrics.top;
// 画有切割的
if (percent > 0.0f && percent < 1.0f) {
    float highlightTextWidth = textWidth * percent;
    //首先,确定左边文字选中的裁剪区域,然后用高亮色绘制文字
    mCanvas.save();
    mCanvas.clipRect(startX, 0, startX + highlightTextWidth, lineHeight);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setColor(mStrokeTextColor);
    mCanvas.drawText(text, startX, textBaseline, mPaint);
    mPaint.setColor(mHighlightTextColor);
    mPaint.setStyle(Paint.Style.FILL);
    mCanvas.drawText(text, startX, textBaseline, mPaint);
    mCanvas.restore();
    //确定右边文字的选中裁剪区域,紧邻左边已绘制的文字,然后用普通色绘制文字。
    //这样最终效果是,一行文字,左边部分是高亮色,右边文字是普通色
    mCanvas.save();
    mCanvas.clipRect(startX + highlightTextWidth, 0, startX + textWidth, lineHeight);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setColor(mStrokeTextColor);
    mCanvas.drawText(text, startX, textBaseline, mPaint);
    mPaint.setColor(mNormalTextColor);
    mPaint.setStyle(Paint.Style.FILL);
    mCanvas.drawText(text, startX, textBaseline, mPaint);
    mCanvas.restore();
}

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124652108
今日推荐