有大佬指引着方向,才能顺着明亮的道路走的更远。核心的知识都在扔物线大佬的HenCoder中有写,并且真心赞!为了不给大佬和阅读这篇文章的同窗形成不适,就再也不过多引用大佬文章中的内容了。css
Canvas既然是画布,天然先从“画”看起。html
drawARGBandroid
drawColorios
drawRGBgit
drawArcgithub
drawCirclecanvas
drawDoubleRoundRectapi
绘制双框圆角矩形数组
drawLine缓存
drawOval
drawPoint
drawRect
drawRoundRect
drawPosText
能够分别指定每一个文字的位置
drawText
drawTextOnPath
根据路径绘制文字
drawTextRun
用于辅助一些文字结构比较特殊的语言的绘制,例如阿拉伯文字
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
将一个复杂的绘制场景进行拆分,能够进行部分重绘。
绘制3D图形利器
更多详细内容,请移步Hencoder-绘制基础,香!
将canvas裁剪成规定的形状,而后再绘制的内容就在这个裁剪区域内了。
clipOutPath
clipOutRect
clipPath
clipRect
translate
rotate
scale
skew
setMatrix
用 Matrix 直接替换 Canvas 当前的变换矩阵,即抛弃 Canvas 当前的变换。
concat(matrix)
用 Canvas 当前的变换矩阵和 Matrix 相乘,即基于 Canvas 当前的变换,叠加上 Matrix 中的变换。
详细内容,请移步HenCoder-Canvas对绘制的辅助,香!
须要注意,若是绘制过程须要对canvas进行屡次的几何变换,那么须要倒叙来写几何变换过程。好比须要先平移再旋转,那么在写代码的时候,就须要先旋转再平移。
这里主要是由于屏幕的坐标系和canvas坐标系是两个坐标系,须要进行必定的的空间想象。
固然,也能够初始化一个Matrix,合理的使用preXXX和postXXX,对该Matrix进行几何变换操做,而后将其应用到canvas上。
详细内容,请移步HenCoder-自定义View绘制顺序,这里就只引用两张大佬文章中的图片镇楼:
当咱们在使用canvas的辅助函数,对canvas进行操做时,这些操做都是不可逆的。好比,在绘制某个内容以前,使用clipRect(0,0,100,100),那么以后的绘制就只能在[0,0,100,100]这个矩形内,除非在经过手动调用api,让canvas回到以前的某个状态。
为了不发生这种状况,就能够在特定的位置进行保存和恢复,在进行变换前,使用save保存canvas当前的状态,而后进行变换,接着绘制咱们要绘制的内容,最后再经过restore恢复以前保存的状态。
若是在一次绘制中,屡次调用save方法,那么会将每次save时,canvas的状态压入相似一个栈中,每个状态都对应一个数字,表明其是栈中的第几个,能够经过方法restoreToCount(count),将canvas回退到指定的那个。也能够调用restore,一个一个的回退canvas的状态。
须要注意的是,不论是调用restore仍是restoreToCount,都须要在save的数量范围内,否者系统就会抛出异常。
当咱们使用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区域显示为黑色,再覆盖在已显示的内容上
既然只是直接使用paint.setXfermode设置的效果,会跟预期的不一致,那么应该怎么样才能得到预期的效果呢?
canvas提供了saveLayer方法,抽取一个透明区域,执行绘制方法,随后再一并将绘制的内容,覆盖在已显示内容上,使用和不使用saveLayer的大体工做流程:
在调用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())。这些绘制命令的其余实例都属于上表中的“复杂”形状。
前面说到了,在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。
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类型。