文字绘制
文字绘制的位置是基于基线(BaseLine)
// 0F, 0F 指的是基线的位置, 而不是文字的左上角位置, 基线在文字左下角偏上一点点的位置
canvas.drawText(mText, 0F, 0F, mTextPaint)
TextView
内容准确定位
-
getTextBounds
mTextPaint.getTextBounds
可以获取到这一行文字的准确坐标, 然后根据这个计算基线的位置// 绘制文字 mTextPaint.getTextBounds(mText, 0, mText.length, mTextBounds) val offset = (mTextBounds.top + mTextBounds.bottom) / 2F canvas.drawText( mText, width / 2F - (mTextBounds.left + mTextBounds.right) / 2F, height / 2F - offset, mTextPaint )
-
FontMetrics
mTextPaint.getFontMetrics(mFontMetrics) mTextPaint.getTextBounds(mText, 0, mText.length, mTextBounds) val offset = (mFontMetrics.ascent + mFontMetrics.descent) / 2F canvas.drawText( mText, width / 2F - (mTextBounds.left + mTextBounds.right) / 2F, height / 2F - offset, mTextPaint )
绘制多行文字
-
StaticLayout
val staticLayout = StaticLayout( mStr, mTextPaint, width, Layout.Alignment.ALIGN_NORMAL, // 行间距是否加倍: 大于1变大,小于1变小 1.2F, // 行间距是否添加固定值增大 0F, // 是否要上下额外的空余 false ) staticLayout.draw(canvas)
-
通过
breakText
找到换行的位置// 绘制全部 var startIndex = 0 var count: Int var yOffset = mPaint.fontSpacing while (startIndex < mStr.length) { count = mPaint.breakText(mStr, startIndex, mStr.length, true, width.toFloat(), measuredWidth) canvas.drawText(mStr, startIndex, startIndex + count, 0F, yOffset, mPaint) startIndex += count yOffset += mPaint.fontSpacing }
-
图文混排
// 绘制图片 canvas.drawBitmap(mBitmap, width - IMAGE_WIDTH, IMAGE_PADDING, mPaint) // 绘制文字 val measuredWidth = floatArrayOf(0F) var startIndex = 0 var count: Int var yOffset = mPaint.fontSpacing while (startIndex < mStr.length) { val textTop = yOffset + mMetrics.ascent val textBottom = yOffset + mMetrics.descent val usableWidth = if ((textTop > IMAGE_PADDING && textTop < IMAGE_PADDING + IMAGE_WIDTH) || (textBottom > IMAGE_PADDING && textBottom < IMAGE_PADDING + IMAGE_WIDTH) ) { width - IMAGE_WIDTH } else { width.toFloat() } count = mPaint.breakText(mStr, startIndex, mStr.length, true, usableWidth, measuredWidth) canvas.drawText(mStr, startIndex, startIndex + count, 0F, yOffset, mPaint) startIndex += count yOffset += mPaint.fontSpacing }
Canvas
裁切
这里裁切并没有移动坐标系, 裁切的时候要注意 save()
和 restore()
// 裁切
canvas.save()
canvas.clipRect(
IMAGE_PADDING,
IMAGE_PADDING,
IMAGE_PADDING + IMAGE_WIDTH,
IMAGE_PADDING + IMAGE_WIDTH
)
canvas.drawBitmap(mBitmap, IMAGE_PADDING, IMAGE_PADDING, mPaint)
canvas.restore()
canvas.drawBitmap(mBitmap, 0F, 0F, mPaint)
Canvas
平移, 旋转
这里操作的对象是坐标系, 而不是绘制的内容, 所以要反着写
canvas.translate(IMAGE_PADDING, 0F)
canvas.rotate(45F, IMAGE_PADDING, IMAGE_PADDING)
canvas.drawBitmap(mBitmap, 0F, 0F, mPaint)
Camera 坐标系
-
Android 中的 2D 坐标系
原点: 屏幕左上角为(0, 0) X 轴: 沿着屏幕左上角向右为 X 轴正方向 Y 轴: 沿着屏幕左上角向下为 Y 轴正方向
-
Camera 3D 坐标系
原点: 屏幕左上角为(0, 0, 0) X 轴: 沿着屏幕左上角向右为 X 轴正方向 Y 轴: 沿着屏幕左上角向上为 Y 轴正方向 Z 轴: 沿着屏幕左上角向里为 Z 轴正方向, 就是拿手指往屏幕里面戳的那个方向
Camera 旋转
canvas.save()
canvas.translate(IMAGE_WIDTH / 2F, IMAGE_WIDTH / 2F)
mCamera.save()
mCamera.rotateZ(30F)
mCamera.applyToCanvas(canvas)
mCamera.restore()
canvas.translate(-IMAGE_WIDTH / 2F, -IMAGE_WIDTH / 2F)
canvas.drawBitmap(mBitmap, 0F, 0F, mPaint)
canvas.restore()
-
mCamera.rotateX(30F): 意思是绕 X 轴
顺时针
旋转 30 度. 如果物体中间线和 X 轴重合的话,绕 X 轴顺时针
旋转 30 度就是指物体上半部分向里翻转,下半部分向外翻转; -
mCamera.rotateY(30F): 意思是绕 Y 轴
顺时针
旋转 30 度. 如果物体中间线和 Y 轴重合的话,绕 Y 轴顺时针
旋转 30 度就是指物体左半部分向外翻转,右半部分向里翻转; -
mCamera.rotateZ(30F): 意思是绕 Z 轴
逆时针
旋转 30 度. 如果物体中间线和 Z 轴重合的话,绕 Z 轴逆时针
旋转 30 度就是指物体上半部分向左旋转,下半部分向右旋转;/** * 斜着切 * * 这里的代码要倒着理解 * 正着看是在移动坐标系,不好理解 * 倒着看是在移动绘制的内容,好理解 */ private fun obliqueCut(canvas: Canvas) { // 绘制上半部分 canvas.save() canvas.translate(IMAGE_PADDING + IMAGE_WIDTH / 2F, IMAGE_PADDING + IMAGE_WIDTH / 2F) canvas.rotate(-30F) canvas.clipRect(-IMAGE_WIDTH, -IMAGE_WIDTH, IMAGE_WIDTH, 0F) canvas.rotate(30F) canvas.translate(-(IMAGE_PADDING + IMAGE_WIDTH / 2F), -(IMAGE_PADDING + IMAGE_WIDTH / 2F)) canvas.drawBitmap(mBitmap, IMAGE_PADDING, IMAGE_PADDING, mPaint) canvas.restore() // 绘制下半部分 canvas.save() // 把图片挪回去 canvas.translate(IMAGE_PADDING + IMAGE_WIDTH / 2F, IMAGE_PADDING + IMAGE_WIDTH / 2F) // 二维旋转回去 canvas.rotate(-30F) // 三维旋转 mCamera.save() mCamera.rotateX(45F) mCamera.applyToCanvas(canvas) mCamera.restore() // 因为旋转之后,范围变大了,所以裁切范围也要变大 canvas.clipRect(-IMAGE_WIDTH, 0F, IMAGE_WIDTH, IMAGE_WIDTH) // 二维顺时针旋转 canvas.rotate(30F) // 先把图片中心移动到原点 canvas.translate(-(IMAGE_PADDING + IMAGE_WIDTH / 2F), -(IMAGE_PADDING + IMAGE_WIDTH / 2F)) canvas.drawBitmap(mBitmap, IMAGE_PADDING, IMAGE_PADDING, mPaint) canvas.restore() }