前言
本文将围绕三点展开介绍:
- 文字如何贴边绘制
- 文字如何纵向居中对齐
- 文字如何多行绘制
Canvas 绘制文字
drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
看一下参数:text和paint都很好理解,x , y 乍一看是就是坐标嘛,似乎没那么简单。
分析x,y参数
下面以代码 + 效果截图 + 文字绘制bounds截图三者结合起来分析。
代码
class DrawTextView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam sed nibh a eros efficitur ultricies. "
private val bounds = Rect()
private val paint = TextPaint().also {
it.isAntiAlias = true
it.textSize = 200f
it.textAlign = Paint.Align.LEFT
}
override fun onDraw(canvas: Canvas) {
canvas.drawColor(Color.parseColor("#ffe00ccc"))
// 获取文字的bounds,然后我们在打印一下,看一下
paint.getTextBounds(text, 0, text.length, bounds)
println("rect = $bounds")
paint.color = Color.LTGRAY
// 绘制一个背景
canvas.drawRect(bounds.left.toFloat(), 0f, bounds.right.toFloat(), abs(bounds.bottom).toFloat() + abs(bounds.top).toFloat(), paint)
paint.color = Color.BLACK
// 这下绘制文字
canvas.drawText(text, 0f, -bounds.top.toFloat(), paint)
// 绘制 -bounds.top.toFloat(),看看y的起始点
canvas.drawLine(0f, -bounds.top.toFloat()- paint.strokeWidth/2f, width.toFloat(), -bounds.top.toFloat()- paint.strokeWidth/2f, paint)
}
}
文字 bounds 打印截图
从截图中我们可以看到rect的的left = 14, top = -161, right = 9091,bottom = 41,至于为什么top是负数,Android 就是这么设计的,我也不知道为什么。
效果展示截图
再分析
x, y坐标之前,我们先看看一下我们绘制的灰色的矩形,坐标是left = 14, top = 0, right = 9091, bottom = 202,因为文字是刚好绘制这个矩形区域内的。
分析x坐标
先分析x坐标, 我们在代码中传入的x的值为0f,但是实际效果并不是从0f开始的。从上面的代码上我绘制文字的bounds大小的矩形,矩形的左边刚好和文字的x的坐标点重合。
那么我们就可以得出结论,x并不是文字开始绘制的点,而是包含一点点间距的。如果不想要这个间距怎么办?x = - bounds.left.toFloat()就可以了。
分析y坐标
我们在代码中传入y的值为-bounds.top.toFloat(),也就是161,和绘制的黑色线刚好重合,由此可以得出这就是绘制文字y轴的起始点,即基线baseline,也就是绘制文字的0点,向上为负数,向下为正数。
绘制文字的时候,如何使得文字纵向居中呢? y = 容器的高度/2f - (bounds.top + bounds.bottom)/2f
绘制文字的时候,如何使文字贴顶呢?y = -bounds.top.toFloat()
小结
通过上面的学习,我们知道了两点:
- 文字如何纵向居中显示
- 文字如何贴边显示
但是以上都是绘制单行文字,那如何绘制多行文字呢?
绘制多行文字
- 绘制的绘制多行文字,用
StaticLayout就可以了,这个API可以自动折行。 - 也可以使用
paint.breakText,这个API给了开发者更大的自由,可以随意定制。
现在我们看看breakText
int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
参数介绍:
measureForwards: 文字绘制的方向,true从左往右,maxWidth: 最大绘制的宽度measuredWidth: 实际绘制的宽度- 返回值
int: 绘制了多少个文字
绘制多行文字的时候,还需要使用paint.getFontSpacing(),这个是行距,就是两行文字baseline的距离。
总结测量文字的几种方法
paint.getTextBounds()获取绘制的字符串的边界,这个和文字有关系paint.getFontMetrics()获取绘制的字符串的上下边界,比getTextBounds的上下边界大一点,这个和字体有关系paint.measureText()获取绘制的字符的左右边界,比getTextBounds计算的宽度大一点paint.getTextWidth()获取单个字的宽度