Android 中的文字绘制

围巾🧣 2022年01月14日 1,019次浏览

准备画布、画笔

自定义一个 TextView,在布局中使用

class SimpleColorChangeTextView : AppCompatTextView {
  // ……
}
    <top.xlxs.drawtext.view.SimpleColorChangeTextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

在自定义 View 中准备好画笔,onDraw 方法画需要的内容

配置画笔

一般为配置字体尺寸、反锯齿、填充方式

        // 画笔配置
        mTextPaint.apply {
            textSize = 80f
            isAntiAlias = true
            style = Paint.Style.FILL
        }

文字绘制辅助线

  1. top 文字绘制空间的最高处
  2. bottom 文字绘制空间的最低处
  3. baseline 文字绘制的基线,例如写单词时,每个字母都会参照倒数第二条线的位置
  4. ascent baseline 到文字顶部的平均距离,一般比 top 小一点(非数值,因为为负的)
  5. descent baseline 到文字底部的平均距离,一般比 bottom 小一点

FontMetrics

FontMetrics

一个封装了 top、bottom、ascent、descent 的对象,配置好画笔字体尺寸后,就可以从画笔取到该对象,直接使用

val fontMetrics = mTextPaint.fontMetrics

水平居中绘制

x 值设为中间值,对齐方式设置为居中

mTextPaint.textAlign = Paint.Align.CENTER
canvas.drawText(mText, x, baseline + mTextPaint.fontSpacing, mTextPaint)

垂直居中绘制

  1. 首先新建一个图层(保存原位置,画好新的再恢复)
  2. 获取文字宽度,对齐方式,计算 x 轴
  3. 计算 baseline 位置:屏幕中间位置 + (ascent + descent)/2 - descent ,加上之后刚好多出 descent 的位置,所以最后要减去
val fontMetrics = mTextPaint.fontMetrics
// height一半 加上 descent、ascent和的一半 刚好会多出descent
val baseline = height / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent

Canvas 绘制

层、保存和恢复

跟 PS 里的图层相近,可以这样看:

每次 canvas.save() 后,后面再绘制是在一个新的图层,当再次调用后 cavas.restore() 后,又会恢复到之前的图层

文字颜色渐变绘制

覆盖的原理是画布仅在文字上方的矩形区域画。实现通过裁剪画布,仅覆盖在文字上方需要的部分。

在文字上方矩形绘制相同的文字,只是颜色不一样。通过动画动态改变上方文字绘制的区域,达到渐变的效果

覆盖绘制

private fun drawCenterText(canvas: Canvas) {
  canvas.save()
  val textWidth = mTextPaint.measureText(mText)
  mTextPaint.textAlign = Paint.Align.LEFT
  val left = width / 2 - textWidth / 2
  val fontMetrics = mTextPaint.fontMetrics
  // height一半 加上 descent、ascent和的一半 刚好会多出descent
  val baseline = height / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent
  canvas.drawText(mText, left, baseline, mTextPaint)

  canvas.restore()
}

private fun drawCenterTextCover(canvas: Canvas) {
  canvas.save()
  mCoverTextPaint.color = Color.RED
  val textWidth = mTextPaint.measureText(mText)
  mTextPaint.textAlign = Paint.Align.LEFT
  val left = width / 2 - textWidth / 2
  val right = left + textWidth * mPercent
  val fontMetrics = mTextPaint.fontMetrics
  // height一半 加上 descent、ascent和的一半 刚好会多出descent
  val baseline = height / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent

  // 覆盖的原理是画布仅在文字上方的矩形区域画。实现通过裁剪画布,仅覆盖在文字上方需要的部分
  val rect = Rect(textWidth.toInt(), 0, right.toInt(), height)
  canvas.clipRect(rect)
  canvas.drawText(mText, left, baseline, mCoverTextPaint)

  canvas.restore()
}

属性动画动态改变绘制矩形

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)
    textView = findViewById(R.id.tv)

    // 属性动画
    Handler(Looper.getMainLooper()).post {
        onStartLeft()
    }
}

private fun onStartLeft() {
    // 利用属性动画渐变改变 翻盖百分比
    ObjectAnimator.ofFloat(textView, "mPercent", 0.0f, 1.0f).setDuration(5000).start()
}

过渡绘制补充

真彩色:没有过度绘制
蓝色:过度绘制 1 次
绿色:过度绘制 2 次
粉色:过度绘制 3 次
红色:过度绘制 4 次或更多次

完整代码实现参考

https://gitlab.xlxs.top/xlxsui/drawtext