Android必知必会——Canvas

有大佬指引着方向,才能顺着明亮的道路走的更远。核心的知识都在扔物线大佬的HenCoder中有写,并且真心赞!为了不给大佬和阅读这篇文章的同窗形成不适,就再也不过多引用大佬文章中的内容了。css

Canvas既然是画布,天然先从“画”看起。html

基本绘制drawXXX系列

颜色

  • drawARGBandroid

  • drawColorios

  • drawRGBgit

基础图形

  • drawArcgithub

  • drawCirclecanvas

  • drawDoubleRoundRectapi

    绘制双框圆角矩形数组

  • drawLine缓存

  • drawOval

  • drawPoint

  • drawRect

  • drawRoundRect

文字

  • drawPosText

    能够分别指定每一个文字的位置

  • drawText

  • drawTextOnPath

    根据路径绘制文字

  • drawTextRun

    用于辅助一些文字结构比较特殊的语言的绘制,例如阿拉伯文字

Bitmap

  • drawBitmap(bitmap,left,top,paint)

    指定bitmap的左边界和上边界,而后绘制bitmap

  • drawBitmap(bitmap,srcRect,dstRect,paint)

    用srcRect去切割bitmap指定区域内容,dst指定绘制到canvas的区域边界。

  • drawBitmap(bitmap,matrix,paint)

    绘制bitmap时,将matrix设置的几何变换应用上。

  • drawBitmapMesh

    将整个Bitmap分红若干个网格,再对每个网格进行相应的扭曲处理。可实现水波纹效果

特殊

  • drawPaint

    用画笔当前的配置,填充画布。

  • drawPath

  • drawPicture

  • drawRenderNode

    将一个复杂的绘制场景进行拆分,能够进行部分重绘。

  • drawVertices

    绘制3D图形利器

更多详细内容,请移步Hencoder-绘制基础,香!

对绘制的辅助

裁切

将canvas裁剪成规定的形状,而后再绘制的内容就在这个裁剪区域内了。

  • clipOutPath

  • clipOutRect

  • clipPath

  • clipRect

几何变换

  • translate

  • rotate

  • scale

  • skew

  • setMatrix

    用 Matrix 直接替换 Canvas 当前的变换矩阵,即抛弃 Canvas 当前的变换。

  • concat(matrix)

    用 Canvas 当前的变换矩阵和 Matrix 相乘,即基于 Canvas 当前的变换,叠加上 Matrix 中的变换。

使用Camera作三维变换

详细内容,请移步HenCoder-Canvas对绘制的辅助,香!

绘制过程当中需屡次进行几何变换时

须要注意,若是绘制过程须要对canvas进行屡次的几何变换,那么须要倒叙来写几何变换过程。好比须要先平移再旋转,那么在写代码的时候,就须要先旋转再平移。

这里主要是由于屏幕的坐标系canvas坐标系是两个坐标系,须要进行必定的的空间想象。

固然,也能够初始化一个Matrix,合理的使用preXXX和postXXX,对该Matrix进行几何变换操做,而后将其应用到canvas上。

View的绘制顺序

详细内容,请移步HenCoder-自定义View绘制顺序,这里就只引用两张大佬文章中的图片镇楼:

Canvas的回退栈

当咱们在使用canvas的辅助函数,对canvas进行操做时,这些操做都是不可逆的。好比,在绘制某个内容以前,使用clipRect(0,0,100,100),那么以后的绘制就只能在[0,0,100,100]这个矩形内,除非在经过手动调用api,让canvas回到以前的某个状态。

save和restore

为了不发生这种状况,就能够在特定的位置进行保存和恢复,在进行变换前,使用save保存canvas当前的状态,而后进行变换,接着绘制咱们要绘制的内容,最后再经过restore恢复以前保存的状态。

若是在一次绘制中,屡次调用save方法,那么会将每次save时,canvas的状态压入相似一个栈中,每个状态都对应一个数字,表明其是栈中的第几个,能够经过方法restoreToCount(count),将canvas回退到指定的那个。也能够调用restore,一个一个的回退canvas的状态。

须要注意的是,不论是调用restore仍是restoreToCount,都须要在save的数量范围内,否者系统就会抛出异常。

Canvas.drawXXX工做过程

当咱们使用canvas.drawXXX时,系统会在一个新的透明区域,绘制咱们要绘制的内容,而后迅速与屏幕当前显示内容进行重叠,这个重叠的过程也会受xfermode或blendmode的影响。以下示例,就演示了这个状况:

不设置xfermode:

override fun onDraw(canvas: Canvas) {
    //先将背景涂红
    canvas.drawColor(Color.RED)
    //在中心画一个绿色的圆
    paint.color = Color.GREEN
    canvas.drawCircle(width/2f,height/2f,radios/2f,paint)
}
复制代码

获得的结果是这样的:

设置xfermode为DST:

override fun onDraw(canvas: Canvas) {
    //先将背景涂红
    canvas.drawColor(Color.RED)
    //在中心画一个绿色的圆
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST)
    paint.color = Color.GREEN
    canvas.drawCircle(width/2f,height/2f,radios/2f,paint)
    paint.xfermode = null
}
复制代码

获得的结果是这样的:

若是在绘制过程当中只是给paint设置xfermode,而没有进行操做,如保存Canvas状态信息等,那么:

  • 若是设置的mode须要削掉DST(即已经在屏幕上显示的)部分或所有内容,那么这个mode不会生效

  • 若是设置的mode为SRC_OUT、DST_OUT或XOR时,那么SRC区域显示为黑色,再覆盖在已显示的内容上

使用layer综合绘制操做

既然只是直接使用paint.setXfermode设置的效果,会跟预期的不一致,那么应该怎么样才能得到预期的效果呢?

canvas提供了saveLayer方法,抽取一个透明区域,执行绘制方法,随后再一并将绘制的内容,覆盖在已显示内容上,使用和不使用saveLayer的大体工做流程:

  • 不使用layer

  • 使用layer

在调用saveLayer时,能够传入一个saveFlags参数,它有以下几个参数能够设置:

  • MATRIX_SAVE_FLAG

    只保存图层的matrix矩阵

  • CLIP_SAVE_FLAG

    只保存大小信息

  • HAS_ALPHA_LAYER_SAVE_FLAG

    代表该图层有透明度,和下面的标识冲突,都设置时如下面的标志为准

  • FULL_COLOR_LAYER_SAVE_FLAG

    彻底保留该图层颜色(和上一图层合并时,清空上一图层的重叠区域,保留该图层的颜色)

  • CLIP_TO_LAYER_SAVE_FLAG

    建立图层时,会把canvas(全部图层)裁剪到参数指定的范围,若是省略这个flag将致使图层开销巨大(实际上图层没有裁剪,与原图层同样大)

  • ALL_SAVE_FLAG

    保存全部信息

再来看一下canvas.save()源码:

public int save() {
    return nSave(mNativeCanvasWrapper, 
    //保留矩阵信息(记录了canvas位移、缩放、旋转状况)
    //保留clipXXX信息(clipRect、clipPath等)
    //其余信息不保留,如已绘制内容信息等
    MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
}
复制代码

硬件加速

从 Android 3.0(API 级别 11)开始,Android 2D 渲染管道支持硬件加速,也就是说,在 View 的画布上执行的全部绘制操做都会使用 GPU。启用硬件加速须要更多资源,所以应用会占用更多内存。若是最低 版本为 14 及更高级别,则硬件加速默认处于启用状态。

什么是硬件加速

所谓硬件加速,指的是把某些计算工做交给专门的硬件来作,而不是和普通的计算工做同样交给 CPU 来处理。这样不只减轻了 CPU 的压力,并且因为有了「专人」的处理,这份计算工做的速度也被加快了。这就是「硬件加速」。

而对于 Android 来讲,硬件加速有它专属的意思:在 Android 里,硬件加速专指把 View 中绘制的计算工做交给 GPU 来处理。进一步地再明确一下,这个「绘制的计算工做」指的就是把绘制方法中的那些 Canvas.drawx3X() 变成实际的像素这件事。

怎么就加速了

  • 用了 GPU(自身的设计原本就对于不少常见类型内容的计算,例如简单的圆形、简单的方形等具备优点),绘制变快了

  • 绘制机制的改变,致使界面内容改变时的刷新效率极大提升

若是想了解更多关于硬件加速的底层原理,能够查看这篇文章——理解Android硬件加速原理的小白文

硬件加速开关

  • Application

    <application android:hardwareAccelerated="true" ...>
    复制代码
  • Activity

    <application android:hardwareAccelerated="true">
          <activity ... />
          <activity android:hardwareAccelerated="false" />
      </application>
    复制代码
  • Window

    //window层级,只能开启,没法进行关闭
    window.setFlags(
          WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
          WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
      )
    复制代码
  • View

    //控制当前view图层是否使用硬件加速,若是应用整体未开启硬件加速,那么即使设置type为LAYER_TYPE_HARDWARE,也没法开启硬件加速
    myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
    复制代码
  • 程序运行中

    1.view.isHardwareAccelerated(),判断当前是否开启硬件加速

    2.canvas.isHardwareAccelerated(),判断当前是否开启硬件加速

    因为一般状况下,canvas是绘制的载体,因此应该经过canvas进行判断

硬件加速使用限制

硬件加速虽好,但开启硬件加速后,绘制Canvas的某些方法失效或无效,在使用时须要注意这些方法:

  • Canvas

    方法 开始支持API版本
    drawBitmapMesh()(颜色数组) 18
    drawPicture() 23
    drawPosText() 16
    drawTextOnPath() 16
    drawVertices()
    setDrawFilter() 16
    clipPath() 18
    clipRegion() 18
    clipRect(Region.Op.XOR) 18
    clipRect(Region.Op.Difference) 18
    clipRect(Region.Op.ReverseDifference) 18
    clipRect()(经过旋转/透视) 18
  • Paint

    方法 开始支持API版本
    setAntiAlias()(适用于文本)(颜色数组) 18
    setAntiAlias()(适用于线条) 16
    setFilterBitmap() 17
    setLinearText()
    setMaskFilter()
    setPathEffect()(适用于线条) 28
    setShadowLayer()(除文本以外) 28
    setStrokeCap()(适用于线条) 18
    setStrokeCap()(适用于点) 19
    setSubpixelText() 28
  • Xfermode

    方法 开始支持API版本
    PorterDuff.Mode.DARKEN(帧缓冲区) 28
    PorterDuff.Mode.LIGHTEN(帧缓冲区) 28
    PorterDuff.Mode.OVERLAY(帧缓冲区) 28
  • Shader

    方法 开始支持API版本
    ComposeShader 内的 ComposeShader 28
    ComposeShader 内相同类型的着色器) 26
    ComposeShader 上的本地矩阵 18
  • Scale

    方法 开始支持API版本
    drawText() 18
    drawPosText() 28
    drawTextOnPath() 28
    简单的形状 17
    复杂的形状 28
    drawPath() 28
    阴影层 28

    简单形状,是指使用 Paint 发出的 drawRect()、drawCircle()、drawOval()、drawRoundRect() 和 drawArc()(其中 useCenter=false)命令,该 Paint 不包含 PathEffect,也不包含非默认联接(经过 setStrokeJoin()/setStrokeMiter())。这些绘制命令的其余实例都属于上表中的“复杂”形状。

离屏缓冲 —— 引用自Hencoder

前面说到了,在view中进行的操做,若是不被硬件加速支持,那么就须要适时的关闭硬件加速:

view.setLayerType(LAYER_TYPE_SOFTWARE, null);
复制代码

可是这个方法的本意是设置view layer的类型,若是类型设置为LAYER_TYPE_SOFTWARE,那么会顺便关闭硬件加速。

所谓 View Layer,又称为离屏缓冲(Off-screen Buffer),它的做用是单独启用一块地方来绘制这个 View ,而不是使用软件绘制的 Bitmap 或者经过硬件加速的 GPU。这块「地方」多是一块单独的 Bitmap,也多是一块 OpenGL 的纹理(texture,OpenGL 的纹理能够简单理解为图像的意思),具体取决于硬件加速是否开启。

采用什么来绘制 View 不是关键,关键在于当设置了 View Layer 的时候,它的绘制会被缓存下来,并且缓存的是最终的绘制结果,而不是像硬件加速那样只是把 GPU 的操做保存下来再交给 GPU 去计算。经过这样更进一步的缓存方式,View 的重绘效率进一步提升了:只要绘制的内容没有变,那么不管是 CPU 绘制仍是 GPU 绘制,它们都不用从新计算,而只要只用以前缓存的绘制结果就能够了。

基于这样的原理,在进行移动、旋转等(无需调用 invalidate())的属性动画的时候开启 Hardware Layer 将会极大地提高动画的效率,由于在动画过程当中 View 自己并无发生改变,只是它的位置或角度改变了,而这种改变是能够由 GPU 经过简单计算就完成的,并不须要重绘整个 View。因此在这种动画的过程当中开启 Hardware Layer,可让原本就依靠硬件加速而变流畅了的动画变得更加流畅。

view.setLayerType(LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);

animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(LAYER_TYPE_NONE, null);
    }
});

animator.start();

或者

view.animate()
        .rotationY(90)
        .withLayer(); // withLayer() 能够自动完成上面这段代码的复杂操做

复制代码

不过必定要注意,只有你在对 translationX translationY rotation alpha scale等无需调用 invalidate() 的属性作动画的时候,这种方法才适用,由于这种方法自己利用的就是当界面不发生时,缓存未更新所带来的时间的节省。

使用硬件加速的提示和技巧

  • 减小应用中的视图数量

    系统须要绘制的视图越多,运行速度越慢。这也适用于软件渲染。减小视图是优化界面最简单的方法之一。

  • 避免过分绘制

    请勿在彼此上方绘制过多层。移除全部被上方的其余不透明视图彻底遮挡的视图。若是须要在彼此上方混合绘制多个层,请考虑将它们合并为一个层。

  • 不要在绘制方法中建立渲染对象

    一个常见的错误是,每次调用渲染方法时都建立新的 Paint 或 Path。这会强制GC更频繁地运行,同时还会绕过硬件管道中的缓存和优化。

  • 不要过于频繁地修改形状

    例如,使用纹理遮罩渲染复杂的形状、路径和圆圈。每次建立或修改路径时,硬件管道都会建立新的遮罩,成本可能比较高。

  • 不要过于频繁地修改位图

    每次更改位图的内容时,系统都会在下次绘制时将其做为 GPU 纹理再次上传。

  • 谨慎使用 Alpha

    当使用 setAlpha()、AlphaAnimation 或 ObjectAnimator 将视图设置为半透明时,该视图会在屏幕外缓冲区渲染,致使所需的填充率翻倍。在超大视图上应用 Alpha 时,请考虑将视图的层类型设置为 LAYER_TYPE_HARDWARE。

BitmapShader可能引发崩溃

Canvas的父类是BaseCanvas,canvas的一系列drawXXX行为,都是调用的super.drawXXX,在BaseCanvas中,各个drawXXX都会对paint的shader进行一个校验:

private void throwIfHasHwBitmapInSwMode(Paint p) {
    if (isHardwareAccelerated() || p == null) {
        return;
    }
    //若是当前canvas没有使用硬件加速,那么会进入这个方法
    throwIfHasHwBitmapInSwMode(p.getShader());
}

private void throwIfHasHwBitmapInSwMode(Shader shader) {
    if (shader == null) {
        return;
    }
    if (shader instanceof BitmapShader) {
        //若是paint设置了shaer且为BitmapShader,那么再进一步判断
        throwIfHwBitmapInSwMode(((BitmapShader) shader).mBitmap);
    }
    if (shader instanceof ComposeShader) {
        throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderA);
        throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderB);
    }
}

private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
    if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
        //当前没有启用硬件加速,可是bitmap须要硬件加速
        onHwBitmapInSwMode();
    }
}

protected void onHwBitmapInSwMode() {
    //检验是否容许硬件加速bitmap存在于非硬件加速的环境绘制,若是不容许,那么会直接抛出异常
    if (!mAllowHwBitmapsInSwMode) {
        throw new IllegalArgumentException(
                "Software rendering doesn't support hardware bitmaps");
    }
}

//这个属性默认为false
private boolean mAllowHwBitmapsInSwMode = false;

//setter方法也被hide标记
/**
* @hide
*/
public void setHwBitmapsInSwModeEnabled(boolean enabled) {
    mAllowHwBitmapsInSwMode = enabled;
}

复制代码

那么在给Paint设置shader时,若是是BitmapShader必定要注意,Config是否为Bitmap.Config.HARDWARE类型。

相关文章
相关标签/搜索