为了研究这个canvas,花了好几天,因为网上对这个概念的描述,可以说是百家争鸣,我也是一度建立观念再因为现实的不符而推翻,最后现在的理解是这样的。如果大家觉得本文的描述有问题,欢迎指正。
在Android关于Canvas的API描述中,一开始就如下描述:
To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
翻译:
想画一些东西的时候,你需要4个基本的组件,一个Bitmap来存储像素,一个Canvas来接收draw的调用(draw的结果是将像素给画到前面所讲的Bitmap中),一个源,即是你想画的东西(比如矩形,路径,文本,亦或另一个位图),最后就是一个画笔(描述想画的颜色和风格等)
首先明确一下canvas,是用来接受drawXXXX的调用,也就是一个媒介,接受一个Bitmap(之所以传入Bitmap是通过jni对其进行光栅化,而光栅化是将图片等几何图元转化为一格格像素点的像素图,也就是二维图像)。
那么canvas对象从何而来?
可以看到在方法View.onDraw(Canvas canvas)里面传了一个canvas过来,而这个canvas从哪来了,可以看这篇文章,锁定到了JNI。(并且其实canvas的save方法也都最终指向了native method)https://juejin.im/post/5a3b51e36fb9a04503104161#heading-0
我们自己也可以自定一个Canvas自定义一个Canvas,但是需要我们传入一个bitmap去记录我们之后的绘制内容,并且这个Bitmap需要是可变的,否则会触发throwIfCannotDraw(bitmap)。
哪些bitmap是可变,哪些是不可变的呢。当你用BitmapFactory.decodeResource,返回的bitmap是默认状态下的mIsMutable=false;(其他方法应该也是一样的,你自己可以看看)而Bitmap.createBitmap()中可以看到返回的是Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);这最后一个参数是true的也就是 mutable,说可以改变的位图。所以传给Canvas的Bitmap,需要通过Bitmap.CreateBitmap()创建。
在分析具体的方法之前,容我提一些drawable相关的内容。可以去我的另一篇文章看一眼。
Drawable是一个能画出来的物体的抽象,使用前需要调用setBounds确定位置和大小,通过getIntrinsicHeight和getIntrinsicWidth取到实际大小。Drawable可以有几种形式存在:Bitmap、Nine Patch、Vector、Shape、Layers等。
View 有一个Drawable(mBackground)成员(背景)。而ImageView在继承View的基础上多了一个Drawable(mDrawable)成员,就是我们通过setImageXX设置的内容。
View(draw(Canvas))和ImageView的(onDraw(Canvas))中调用了Drawable:draw(Canvas),让人感觉是把这个drawable绘制到了canvas上,网上很多文章也是这么说的,但是其实不太准确,其实根据我上面的解释,canvas只是一个媒介,而Canvas的内部有一个私有成员bitmap,之前也说了Bitmap是用于存储像素的,所以其实drawable的内容是被绘制到了Bitmap里。
然后说说,canvas相关的方法,其实如果细说内容还挺多的,不过这部分网上的资源比较多,如果只是简单使用照着api来就行,我这里就不详细记录具体使用,只是挑我觉得重要的进行理解。
canvas.drawXXXX() 和drawClip()系列的方法,一个是绘制,一个是裁剪,输入前面的部分,自动补全看参数大概就知道了什么意思了。另外canvas还有这些方法。
canvas.translate(平移)、canvas.rotate(旋转)、canvas.scale(缩放)、canvas.skew(扭曲)这些都是利用位置矩阵matrix实现的。
主要来谈谈:
canvas的 save()系列。
主要是
public int save()
public int save(int saveFlags)
public int saveLayer(RectF bounds, Paint paint, int saveFlags)
public int saveLayer(float left, float top, float right, float bottom,Paint paint, int saveFlags)
public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)
public int saveLayerAlpha(float left, float top, float right, float bottom,int alpha, int saveFlags)
这几个方法。
首先大概的说一下这些的作用。save(XXXX) ():带save的方法,都有一个相同的作用就是保存画布(canvas)的信息;saveLayer(XXXX)():带saveLayer的方法,都会生成一个新的画布(canvas);而saveLayerAlpha()系列方法能够设置透明值。(后面的函数都是前面的函数基础上,增加自己的特性。)
然后我们看一下,这些函数带的参数,前面的部分就不谈了,规定区域用的,重点看一下最后的saveFlags,简单来说,就是告诉它,你想要记录的内容是什么。详情可以查看下面的表格。(不传入savaFlags默认是ALL_SAVE_FLAG)
从上面的saveFlags我们可以看出来其实canvas在save()的时候,就是保存两种信息,一个是裁剪信息(也就是canvas现在所剩的大小),一个是位置信息(具体保存哪个或都保存则是根据你传入的saveFlags)。saveLayer()函数,传入的坐标值是新建画布(canvas)中的bitmap的大小。当使用saveLayer()方法时,因为会新建一个canvas所以多出了一个参数记录与上一层(这是个逻辑概念,注意避免与空间概念混淆,指的前一个canvas(旧的那个!))合成时的处理方式。
对应的说restore()和restoreToCount()。这里会结合save()再详细些讲这个有趣的canvas。首先,我们要先了解canvas.save()保存的信息会被一个独立的栈管理,save()就相当于压栈动作,将这次save之前的canvas信息都保存起来,然后restore()相对的是一个出栈动作,返回管理canvas信息这个栈的栈顶,然后赋给canvas。这里要注意一下,别晕了,canvas栈不是用来保存绘制内容的,绘制内容都在bitmap上,canvas栈管理的信息只有裁剪信息和位置信息!然后说一下restoreToCount(),之前的restore()就是出栈一次,而restoreToCount()就是由我们来指定退出到什么地方。
讲讲如何使用,因为canvas.saveXXX()都会返回一个int值, 可以认作是这个canvas信息在栈内id,而你调用restoreToCount()的时候传回这个id,canvas就会回到没进行这次保存前的状态。简单写两行。
int sc = canvas.saveLayer(…);
restoreToCount(id);
其实,管理canvas的这个栈里一开始就有1层,可以认为就是初始层(0层),你保存了一层后索引变成1。而storeToCount(int count)的作用是持续退栈,直到count层出栈。所以save()动作应该要比restore()先执行,且次数要大于等于。
然后举个例子,描述一下canvas的位置信息和裁剪信息具体是指什么。首先复写onDraw()方法里面时会有一个canvas对象,canvas对象在被初始化出来的时候可以认为它的坐标原点就是与屏幕的左上角重合的,也就是初始情况下调用canvas.drawRect(0,0,100,100,paint)是在屏幕的左上角绘制一个边长为100的矩形。其实这个很好理解,但是为什么我不说canvas是跟屏幕重合的呢,其实canvas的大小可以认为是无限大的,但是只有坐标在屏幕范围的部分会被显示出来。
可以想象屏幕是个幕布,canvas是个投影仪,幕布是固定的,但是投影仪可以旋转,平移,裁剪(裁剪就当作投影的口被遮住好了。)只有投影到幕布上的内容会被显示出来。而save()其实就是保存了这个投影仪被你操作的情况,restore()就是还原你操作之前的情况。
讲到这里,还有一个点要提一下。每一次进行drawXXX后,canvas会新建一个图层,然后这个新图层进行移动或裁剪与之前图层无关。这么说有点难理解,换一种说法。绘制的内容会立即保存到canvas的bitmap中,然后你再对canvas进行操作,影响不到之前已保存的内容,这样就清楚了。
其实绘制这块无论是Xfermode还是各种层次关系和传入的参数,大家都可以自己去试试,我仅给出我现在的认知。本文旨在理解canvas,而不是快速使用。
上面是我对canvas的理解,如果有错误或者没说清楚的地方欢迎指正,谢谢!
参考资料
https://blog.csdn.net/harvic880925/article/details/51317746?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
https://blog.csdn.net/harvic880925/article/details/39080931
https://www.jianshu.com/p/d59d0deab15b
https://blog.csdn.net/qq_27489007/article/details/79353328
https://www.jianshu.com/p/b93339d0522d
https://blog.csdn.net/lu1024188315/article/details/77524308