FontMetrics
FontMetrics是Paint的一个内部类,用于描述给定文本大小的字体的各项参数:
public static class FontMetricsInt {
/**
* 在给定字体大小的文本中,最高字符的顶部在基线上方的最大距离。
*/
public float top;
/**
* 对于单行文本,文本在基线上方的距离
*/
public float ascent;
/**
* 对于单行文本,文本在基线下方的距离
*/
public float descent;
/**
* 在给定字体大小的文本中,最低字符的顶部在基线上方的最大距离。
*/
public float bottom;
/**
* 两行文本之间的距离
*/
public float leading;
@Override public String toString() {
return "FontMetricsInt: top=" + top + " ascent=" + ascent +
" descent=" + descent + " bottom=" + bottom +
" leading=" + leading;
}
}
从网上找了个图片来描述各个参数:

BaseLine是基线,Android在绘制文本的基础行。为什么会有基线这个概念呢?外文并不像中文这样,它们是由字母而组成的,而多数字母排列是沿着基准线,但是,像”p”或者”y”之类的字母会超过基线向下延伸,超过的部分称为降部,反之,基线以上的称为升部。Baseline往上至字符“最高处”的距离我们称之为ascent(上坡度),Baseline往下至字符“最低处”的距离我们称之为descent(下坡度)。又因为Android系统中,对于坐标系的定义,Y值增加向下延伸,因此,获取到的descent为正值,而ascent为负值。
leading是两行文本之间的距离,官方并没有明确的解释,应该是上一行的descent与下一行的ascent之间的距离。
关于top、bottom描述的比较模糊,对于top而言,是指最高字符的顶部在基线上方的最大距离,可是,又与ascent的定义有所冲突。借鉴下TextView对于文本的控制,其总会为文本留下部分内边距,因为TextView在绘制文本时会考虑一些特殊符号,比如音调符号。我们是不是可以这样理解:
- top = ascent + 文本上方预留边距
- bottom = descent + 文本下方预留边距
为了验证FontMetrics中的各个参数,现自定义View,绘制 “yearξτβбшㄎěǔぬも测试a”这个字符串:
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
paintText.apply {
textSize = 96f
strokeWidth = 3f
}
// FontMetrics对象
val fontMetrics = paintText.fontMetricsInt
val topY = fontMetrics.top
val ascentY = fontMetrics.ascent
val descentY = fontMetrics.descent
val bottomY = fontMetrics.bottom
val leading = fontMetrics.leading
Log.i("123", "topY: $topY")
Log.i("123", "ascentY: $ascentY")
Log.i("123", "descentY: $descentY")
Log.i("123", "bottomY: $bottomY")
Log.i("123", "leading: $leading")
val baseLine = 128f
val widthText = paintText.measureText(text1)
canvas?.drawText(text1, 64f, baseLine, paintText)
paintLine.strokeWidth = 3f
// baseLine
paintLine.color = resources.getColor(android.R.color.holo_red_dark)
canvas?.drawLine(0f, baseLine, 128f + widthText, baseLine, paintLine)
// top
paintLine.color = resources.getColor(R.color.colorTop)
canvas?.drawLine(0f, topY + baseLine, 128f + widthText, topY + baseLine, paintLine)
// ascent
paintLine.color = resources.getColor(R.color.colorAscent)
canvas?.drawLine(0f, ascentY + baseLine, 128f + widthText, ascentY + baseLine, paintLine)
// descent
paintLine.color = resources.getColor(R.color.colorDescent)
canvas?.drawLine(0f, descentY + baseLine, 128f + widthText, descentY + baseLine, paintLine)
// bottom
paintLine.color = resources.getColor(R.color.colorBottom)
canvas?.drawLine(0f, bottomY + baseLine, 128f + widthText, bottomY + baseLine, paintLine)
// leading
paintLine.strokeWidth = leading.toFloat()
paintLine.color = resources.getColor(R.color.colorLeading)
canvas?.drawLine(0f, bottomY.toFloat(), 128f + widthText, bottomY.toFloat(), paintLine)
}
效果图:
Log如下:
topY: -102
ascentY: -8
descentY: 2
bottomY: 27
leading: 0
测量文本宽度
当指定文本的字体大小以后,Paint提供了两种测量文本宽度的方式:
- breakText
- measureText
breakText和measureText都可以测量指定文本的宽度。与measureText不同的是,breakText可以指定文本的最大宽度,当文本的宽度超过最大宽度时,会停止测量。如果传递的measuredWidth不为null,会将文本的宽度保存。
例如:在自定义View - PaintTextAPIView中,绘制了字符串”yaξβбшㄎěǔぬ中”,每个字符的大小为96f。
class PaintTextAPIView : View {
private val text: String = "yaξβбшㄎěǔぬ中"
***
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
paintText.apply {
textSize = 96f
strokeWidth = 3f
}
val baseLine = 128f
// 获取View的宽度
val width = width
Log.i("123", "view的宽度: $width")
val widthText = paintText.measureText(text)
Log.i("123", "文本的宽度: $widthText")
Log.i("123", "文本的个数: ${text.length}")
val measuredWidth = kotlin.FloatArray(1)
val countBreak = paintText.breakText(text, false, 540f, measuredWidth)
Log.i("123", "满足测量的条件的字符个数: $countBreak")
Log.i("123", "measuredWidth: ${measuredWidth.toList()}")
canvas?.drawText(text, 64f, baseLine, paintText)
}
}
在上述示例中,通过Paint的setTextSize方法指定了文本中字符的大小,然后调用了measureText方法,测量了文本的宽度为722f。在调用breakText方法时,指定的最大宽度为540f。当测量到第几个字符时的宽度为530f,如果在再加上第10个字符时,必然超过540f。此时停止了测量文本的宽度,返回了满足条件的字符个数,并将满足条件的文本的宽度保存在measuredWidth中。
view的宽度: 960
文本的宽度: 722.0
文本的个数: 11
满足测量的条件的字符个数: 9
measuredWidth: [530.0]
setLetterSpacing
该方法是在API 21新增的,最低使用API也是21.它用于设置文本的字符之间的间距,其默认值为0。值得注意的是它的单位不再是px或者dp,而是EM(1EM相当于当前字体的尺寸)。
比如, 将文本的字符间距设置为1EM:
paintText.letterSpacing = 1f
效果图:
此时,如果paintText设置的字体大小为14dp,那么它们的行间距应为14dp。假如将setLetterSpacing设置为1.5f,此时的字体间距应为21dp。
setStrikeThruText
该方法用于设置文本带有删除线。
setTextAlign(Paint.Align align)
该方法用于设置文本的对齐方式。在Paint.Align中定义了一系列的对齐方式:
- CENTER:居中对齐
- LEFT:居左对齐
- RIGHT:居右对齐
现定义了一个AlignView,一一使用了不同的对齐方式:
class AlignView : View {
***
private fun initData() {
paintDefault = Paint()
paintDefault.apply {
textSize = 48f
}
paintCenter = Paint(paintDefault)
paintCenter.textAlign = Paint.Align.CENTER
paintLeft = Paint(paintDefault)
paintLeft.textAlign = Paint.Align.LEFT
paintRight = Paint(paintDefault)
paintRight.textAlign = Paint.Align.RIGHT
paintLine = Paint()
paintLine.apply {
color = resources.getColor(R.color.colorLeading)
strokeWidth = 3f
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawText(text, 144f, 72f, paintDefault)
canvas?.drawText(text, 144f, 144f, paintCenter)
canvas?.drawText(text, 144f, 216f, paintLeft)
canvas?.drawText(text, 144f, 288f, paintRight)
canvas?.drawLine(144f, 0f, 144f, 360f, paintLine)
}
}
从效果图上,我们清晰的看出,默认的对齐方式是居左对齐。这个方法影响的是文本的哪一部分位于绘制的起始点。在调用drawText绘制文本时,要尤其注意绘制初始的X坐标,要不然就会出现文本绘制混乱,影响显示。
- CETER: (Xstart, Ystart)
- LEFT: (Xstart + text.lentgh/2, Ystart)
- RIGHT: Xstart + text.lentgh, Ystart)
setTextScaleX(float scaleX)
该方法用于设置Paint的字体的比例因子,默认为1f.当大于1时,表示横向拉伸;当小于1时,表示横向压缩。
setTextSize(float size)
该方法用来设置文本的字体大小,不必多说…
setTextSkewX(float skewX)
该方法用来设置文本的偏移因子,默认值为0.对于近似倾斜的文本,使用-0.25。正数表示向左倾斜,负数表示向右倾斜。
setUnderlineText (boolean underlineText)
该方法用来设置是否绘制文本的下划线。其中,underlineText表示是否显示下画线,ture表示显示下画线,false表示不显示下画线。
setTypeface(Typeface typeface)
该方法用来字体的样式。对于Typeface不了解的,可参考Android中的字体设置-Typeface.
val typeface = Typeface.defaultFromStyle(Typeface.ITALIC or Typeface.BOLD)
tcv.setTypeface(typeface)
其他API
- setSubpixelText(boolean subpixelText):设置该项为true,将有助于文本在LCD屏幕上的显示效果
- setFontFeatureSettings(String settings):设置字体功能设置。 格式与CSS font-feature-settings属性相同
- setFontVariationSettings(String fontVariationSettings):设置TrueType或OpenType字体变体设置
若想了解更多Paint相关的内容,请跳入: 自定义View系列文章目录
如果觉得我的文章对您有用,请随意点赞、评论。您的支持将鼓励我继续创作!
237



被折叠的 条评论
为什么被折叠?



