一、drawBitmap
Bitmap是我们Android开发者最熟悉有陌生的老朋友了。它是很多内存问题的万恶之源,但我们又常常不用去碰它,而是把关于图片的操作交给Glide之类的框架。这里不详细讲解关于Bitmap的知识,只讲如何在Canvas里绘制它。
照例看一下Canvas里关于绘制Bitmap的方法:
- drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
- drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
- drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
- drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
前两个方法只有dst参数类型一个是Rect一个是RectF的区别,都是把Bitmap的局部或这全部(由src参数确定)绘制到dst参数指定的区域内,可能会进行拉伸或者缩放。
这里随便照一张图片,放在相应的drawable目录内:
看如下示例:
private val paint = Paint()
private val bitmap = BitmapFactory.decodeResource(resources, R.drawable.poppy)
private val srcRect = Rect(0, 0, 1000, 800)
private val destRect = Rect(0, 0, 200, 500)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 假定View的宽高为500px
setMeasuredDimension(500, 500)
}
override fun onDraw(viewCanvas: Canvas) {
super.onDraw(viewCanvas)
// 给View一个背景颜色
viewCanvas.drawColor(Color.CYAN)
viewCanvas.drawBitmap(bitmap, srcRect, destRect, paint)
}
也就是要把下面红框选中部分绘制在画布(0, 0)和(200, 500)所确定的矩形内。由于两个矩形长宽比例不一致
效果图:
第三个方法则是从把(left, top)作为Bitmap的左上角开始绘制,不缩放。
private val paint = Paint()
private val bitmap = BitmapFactory.decodeResource(resources, R.drawable.poppy)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 假定View的宽高为500px
setMeasuredDimension(500, 500)
}
override fun onDraw(viewCanvas: Canvas) {
super.onDraw(viewCanvas)
// 给View一个背景颜色
viewCanvas.drawColor(Color.CYAN)
viewCanvas.drawBitmap(bitmap, 50f, 50f, paint)
}
效果:
至于第四个方法,等聊到Matrix类的时候再详细讲,此处暂时略过。
二、Canvas.drawPicture
关于Picture,官方文档描述如下:
A Picture records drawing calls (via the canvas returned by beginRecording) and can then play them back into Canvas (via Picture#draw(Canvas) or Canvas#drawPicture(Picture)).For most content (e.g. text, lines, rectangles), drawing a sequence from a picture can be faster than the equivalent API calls, since the picture performs its playback without incurring any method-call overhead.
大意是说,如果我们需要重复N次某些绘制操作,那么我们可以把这些绘制操作记录在Picture中,然后通过调用N次Picture.draw或者Canvas.drawPicture来实现。对于大多数内容(例如文本,线条,矩形),使用Picture要比每次都挨个去调用drawLine、drawText效率要高,因为少了很多方法调用的开销。
Picture的基本用法也比较简单,在beginRecording和endRecording方法调用之间通过返回的Canvas对象执行绘制操作即可:
val picture = Picture()
//使用500*500的画布
val pictureCanvas = picture.beginRecording(500, 500)
// 此处一顿乱draw
pictureCanvas.drawOval(...)
pictureCanvas.drawLines(...)
pictureCanvas.drawXXX(...)
...
picture.endRecording()
targetCanvas.drawPicture(picture)
//或者使用下面的方法
//picture.draw(targetCanvas)
需要注意的是,endRecording调用之后,不要再去操作pictureCanvas进行内容绘制了。
Canvas里用于绘制Picture的方法如下:
- drawPicture(Picture picture)
- drawPicture(Picture picture, RectF dst)
- drawPicture(Picture picture, Rect dst)
第一个方法用于在画布当前位置开始绘制Picture,不缩放。看如下示例:
private val paint = Paint().apply {
color = Color.BLACK
style = Paint.Style.FILL
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 假定View的宽高为500px
setMeasuredDimension(500, 500)
}
override fun onDraw(viewCanvas: Canvas) {
super.onDraw(viewCanvas)
// 给View一个背景颜色
viewCanvas.drawColor(Color.CYAN)
//将画布平移
viewCanvas.translate(50f, 50f)
//原则上不要在onDraw的时候new对象,这里是demo为了省事,大家别学我
val picture = Picture()
val pictureCanvas = picture.beginRecording(500, 500)
pictureCanvas.drawCircle(250f, 250f, 250f, paint)
picture.endRecording()
viewCanvas.drawPicture(picture)
}
其中viewCanvas.translate(50f, 50f)是将画布平移至(50,50),后续讲画布操作会讲到。平移后,画布“当前位置”为(50,50)。所以效果如下:
后两个方法与Bitmap绘制类似,也可能缩放变形。由于创建Picture画布时已经指定了宽高,也就不需要指定src参数了。示例:
private val paint = Paint().apply {
color = Color.BLACK
style = Paint.Style.FILL
}
private val dst = Rect(100, 100, 300, 200)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 假定View的宽高为500px
setMeasuredDimension(500, 500)
}
override fun onDraw(viewCanvas: Canvas) {
super.onDraw(viewCanvas)
// 给View一个背景颜色
viewCanvas.drawColor(Color.CYAN)
//将画布平移
viewCanvas.translate(50f, 50f)
//原则上不要在onDraw的时候new对象,这里为了省事,大家别学我
val picture = Picture()
val pictureCanvas = picture.beginRecording(500, 500)
pictureCanvas.drawCircle(250f, 250f, 250f, paint)
picture.endRecording()
viewCanvas.drawPicture(picture, dst)
}
效果:
三、Drawable
Drawable,可绘制对象。Canvas里并没有类似drawDrawable的方法,但是Drawable类提供了draw(Canvas canvas)方法。需要注意的是,绘制之前需要先Drawable的setBounds方法设置边界。
定义一个drawable的xml文件:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FF8888" />
<size android:width="100dp" android:height="50dp"/>
</shape>
绘制:
private val dst = Rect(100, 100, 300, 200)
private val oval = resources.getDrawable(R.drawable.oval)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// 假定View的宽高为500px
setMeasuredDimension(500, 500)
}
override fun onDraw(viewCanvas: Canvas) {
super.onDraw(viewCanvas)
// 给View一个背景颜色
viewCanvas.drawColor(Color.CYAN)
oval.bounds = dst
oval.draw(viewCanvas)
}
效果: