自定义View③文字的绘制

404 阅读2分钟

DP和SP

  • SP受像素密度和用户设置(手机字体大小设置)影响
  • DP只受像素密度影响

文字纵向居中显示

下面完成一个运动视图的绘制,比如运动量统计

  • 首先我们画一个圆环
  • 然后绘制进度条
  • 然后在圆环中心绘制文字,效果如下
//绘制环
//绘制进度条
//绘制文字
paint.textSize = 80.dp
paint.style = Paint.Style.FILL
paint.getFontMetrics(fontMetrics)
canvas.drawText("abp", width / 2f, height / 2f , paint)
13

显然文字并没有居中显示,这是因为我们绘制文字的时候是基于baseline绘制的,文字的对齐有5个纬度,如下图

所以这就是文字测量难点之一:居中的纵向测量
有两种方式解决这个问题

  • 方式一:Paint.getTextBounds() 之后,使用 (bounds.top + bounds.bottom) / 2
  • 方式二:Paint.getFontMetrics() 之后,使用 (fontMetrics.ascend + fontMetrics.descend) / 2

所以我们需要将文字向下偏移一定的像素,使用方式二,偏移量就是ascent+descent的一半,修改代码

canvas.drawText("center", width / 2f, height / 2f - (fontMetrics.ascent + fontMetrics.descent) / 2f, paint)

现在显示正常了
14

文字的对齐

    // 绘制文字2
    paint.textSize = 150.dp
    paint.textAlign = Paint.Align.LEFT
    paint.getFontMetrics(fontMetrics)
    paint.getTextBounds("abab", 0, "abab".length, bounds)
    canvas.drawText("abab", - bounds.left.toFloat(), - bounds.top.toFloat(), paint)
    //canvas.drawText("abab", 0.dp, - fontMetrics.top, paint)

// 绘制文字3
paint.textSize = 15.dp
paint.getTextBounds("abab", 0, "abab".length, bounds)
canvas.drawText("abab", - bounds.left.toFloat(), - bounds.top.toFloat(), paint)
16

完整代码

private val CIRCLE_COLOR = Color.parseColor("#90A4AE")
private val HIGHLIGHT_COLOR = Color.parseColor("#FF4081")
private val RING_WIDTH = 20.dp
private val RADIUS = 150.dp

class SportView(context: Context, attrs: AttributeSet?) :
  View(context, attrs) {
  private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    textSize = 100.dp
    typeface = ResourcesCompat.getFont(context, R.font.font)
    textAlign = Paint.Align.CENTER
  }
  private val bounds = Rect()
  private val fontMetrics = Paint.FontMetrics()

  override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    // 绘制环
    paint.style = Paint.Style.STROKE
    paint.color = CIRCLE_COLOR
    paint.strokeWidth = RING_WIDTH
    canvas.drawCircle(width / 2f, height / 2f, RADIUS, paint)

    // 绘制进度条
    paint.color = HIGHLIGHT_COLOR
    paint.strokeCap = Paint.Cap.ROUND
    canvas.drawArc(width / 2f - RADIUS, height / 2f - RADIUS, width / 2f + RADIUS, height / 2f + RADIUS, -90f, 225f, false, paint)

    // 绘制文字
    paint.textSize = 80.dp
    paint.style = Paint.Style.FILL
    paint.getFontMetrics(fontMetrics)
    canvas.drawText("center", width / 2f, height / 2f - (fontMetrics.ascent + fontMetrics.descent) / 2f, paint)

    // 绘制文字2
    paint.textSize = 150.dp
    paint.textAlign = Paint.Align.LEFT
    paint.getFontMetrics(fontMetrics)
    paint.getTextBounds("abab", 0, "abab".length, bounds)
    canvas.drawText("abab", - bounds.left.toFloat(), - bounds.top.toFloat(), paint)
//    canvas.drawText("abab", 0.dp, - fontMetrics.top, paint)

    // 绘制文字3
    paint.textSize = 15.dp
    paint.getTextBounds("abab", 0, "abab".length, bounds)
    canvas.drawText("abab", - bounds.left.toFloat(), - bounds.top.toFloat(), paint)
  }
}

文字换行图文排版

  • canvas.drawText绘制文字
  • paint.breakText计算每行绘制的文字个数

完整代码

class MultilineTextView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
  val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur tristique urna tincidunt maximus viverra. Maecenas commodo pellentesque dolor ultrices porttitor. Vestibulum in arcu rhoncus, maximus ligula vel, consequat sem. Maecenas a quam libero. Praesent hendrerit ex lacus, ac feugiat nibh interdum et. Vestibulum in gravida neque. Morbi maximus scelerisque odio, vel pellentesque purus ultrices quis. Praesent eu turpis et metus venenatis maximus blandit sed magna. Sed imperdiet est semper urna laoreet congue. Praesent mattis magna sed est accumsan posuere. Morbi lobortis fermentum fringilla. Fusce sed ex tempus, venenatis odio ac, tempor metus."
  private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
    textSize = 16.dp
  }
  private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    textSize = 16.dp
  }
  private val bitmap = getAvatar(IMAGE_SIZE.toInt())
  private val fontMetrics = Paint.FontMetrics()

  override fun onDraw(canvas: Canvas) {
//    val staticLayout = StaticLayout(text, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false)
//    staticLayout.draw(canvas)
    canvas.drawBitmap(bitmap, width - IMAGE_SIZE, IMAGE_PADDING, paint)
    paint.getFontMetrics(fontMetrics)
    val measuredWidth = floatArrayOf(0f)
    var start = 0
    var count: Int
    var verticalOffset = - fontMetrics.top
    var maxWidth: Float
    while (start < text.length) {
      maxWidth = if (verticalOffset + fontMetrics.bottom < IMAGE_PADDING
        || verticalOffset + fontMetrics.top > IMAGE_PADDING + IMAGE_SIZE) {
        width.toFloat()
      } else {
        width.toFloat() - IMAGE_SIZE
      }
      count = paint.breakText(text, start, text.length, true, maxWidth, measuredWidth)
      canvas.drawText(text, start, start + count, 0f, verticalOffset, paint)
      start += count
      verticalOffset += paint.fontSpacing
    }
  }

  fun getAvatar(width: Int): Bitmap {
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeResource(resources, R.mipmap.slmh, options)
    options.inJustDecodeBounds = false
    options.inDensity = options.outWidth
    options.inTargetDensity = width
    return BitmapFactory.decodeResource(resources, R.mipmap.slmh, options)
  }

 }

效果如下:
17