Android绘制文本
下面以绘制"Hello Word"在屏幕中间位置为例。
已知
高度 height
宽度 width
1.准备
//画笔
val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textSize = 100f
color = Color.BLACK
}
//获取文本宽度
val textWidth = paint.measureText("Hello Word")
//获取绘制文本时所需descent ascent top 和 bottom坐标
val fontMetrics = paint.fontMetrics // 直接获取Paint的FontMetrics,无需额外变量
val ascent = fontMetrics.ascent // 获取ascent值
val descent = fontMetrics.descent // 获取descent值
val top = fontMetrics.top
val bottom = fontMetrics.bottom
这些对象需要了解,才能对文本进行绘制
2.计算
2.1 绘制文本
绘制文本方法,使用Canvas进行绘制。
/**
* Draw the
text, with origin at (x,y), using the specified paint. The origin is interpreted
* based on the Align setting in the paint.
*
* @param text The text to be drawn
* @param x The x-coordinate of the origin of the text being drawn
* @param y The y-coordinate of the baseline of the text being drawn
* @param paint The paint used for the text (e.g. color, size, style)
*/
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
super.drawText(text, x, y, paint);
}
参数1:文本。
参数2: x坐标,文本最左侧坐标。
参数3: y坐标,需要通过基线进行定位。
参数4:画笔
2.2 计算x,y坐标位置
- 2.2.1 计算X坐标
//获取文本宽度
val textWidth = paint.measureText("Hello Word")
val x = width/2 - textWidth
x坐标为: width/2 - textWidth/2
- 2.2.2 计算Y坐标
- 2.2.2.1 五根线
绘制文本需对top线、ascent线、descent线、baseLine线和bottom线了解,线的大致位置如图。
top线到bottom线之间的距离是文字可以绘制的最大高度。但是一般绘制的文字高度只有ascent线到descent线距离大小。图中还可以看到第五根线BaseLine线,这根线就是我们需要寻找的坐标Y,但是Android未直接提供获取接口。 Android SDK接口中,只为我们提供了关于top线、ascent线、descent线、baseLine线和bottom线相关的几个值。
val ascent = fontMetrics.ascent
val descent = fontMetrics.descent
val top = fontMetrics.top
val bottom = fontMetrics.bottom
从图中可以看到top线、ascent线、descent线、baseLine线和bottom线四条线的大致位置。
代码块中的几个数据是根据他们的Y坐标计算得出,公式如下:
fontMetrics.ascent = ascent线坐标 - BaseLine线Y坐标
fontMetrics.descent = descent - BaseLine线Y坐标
fontMetrics.top = top - BaseLine线Y坐标
fontMetrics.bottom = bottom - BaseLine线Y坐标
因为Android坐标Y轴由上到下大小逐渐增大,所以:fontMetrics.ascent和fontMetrics.top为负值,fontMetrics.descent和fontMetrics.bottom为正值。
- 2.2.2.2 计算中间线到基线位置
假设在ascent线和descent线有一条height/2线,这条线到ascent线与到descent线距离相等。
- 如图:
注意: 如图ABC ,A代表ascent线到BaseLine线距离。B代表height/2线到BaseLine线距离。C为baseLine线到descent线距离。
图中如果想要将文本绘制在ascent线和descent线中间,只需要使用BaseLine线作为Y坐标就可以了。由于Android没有直接提供相关接口,我们需要使用fontMetrics.ascent、fontMetrics.descent、fontMetrics.top、 fontMetrics.bottom计算获取得出所需值。由于想把文字绘制到中间位置,只需要将height/2线定位到视图中间位置,中间位置加上距离B就可以做到。
中间height/2线到基线距离B公式:
中间height/2线到基线距离 = (fontMetrics.descent-fontMetrics.ascent)/2 -fontMetrics.descent
- 2.2.2.3 推导中间线到基线距离公式:
从上面2.2.2.2图中可以看到这样关系A = 2(B+C),故B = A/2-C。
由2.2.2.1中fontMetrics.descent和fontMetrics.ascent计算过程,可以看出fontMetrics.descent为BaseLine线到descent线的距离,-fontMetrics.ascent为BaseLine线到ascent线距离。所以A = fontMetrics.descent-fontMetrics.ascent。
C = fontMetrics.descent
所以B = A/2-C = (fontMetrics.descent-fontMetrics.ascent)/2 - fontMetrics.descent。
注意:fontMetrics.ascent为负数
- Y绘制坐标
Y= height/2+B = height/2 + (fontMetrics.descent-fontMetrics.ascent)/2 - fontMetrics.descent
3.代码
- 3.1 布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.kt.view.textview.CustomTextView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- 3.2 代码
package com.kt.view.textview
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.widget.TextView
import kotlin.math.abs
class CustomTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0):
TextView(context, attrs, defStyleAttr, defStyleRes) {
val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textSize = 100f
color = Color.BLACK
}
override fun onDraw(canvas: Canvas) {
val baseLine = height/2 + getBaseline(paint)
val textWidth = paint.measureText("Hello Word")
//绘制最上面的top
//canvas.drawLine(0f,)
canvas.drawText("Hello Word", width/2 - textWidth/2, baseLine, paint)
}
private fun getBaseline(paint: Paint): Float {
val fontMetrics = paint.fontMetrics
val ascent = fontMetrics.ascent // 获取ascent值
val descent = fontMetrics.descent // 获取descent值
return (descent - ascent) / 2 - descent
}
}
- 3.3 展示
4.总结
- 1.获取文本宽度
val textWidth = paint.measureText("Hello Word")
- 2.获取基线位置
private fun getBaseline(paint: Paint): Float {
val fontMetrics = paint.fontMetrics
val ascent = fontMetrics.ascent // 获取ascent值
val descent = fontMetrics.descent // 获取descent值
return (descent - ascent) / 2 - descent
}