Android自定义View之定点写文字

前言:有经验的Android开发者,应该都会遇到在自定义View的时候,在View的某个地方写文字,那么当你在自定义的View中写文字的时候,能够做到定点写文字吗?能够指哪写哪吗?写出来的文字的位置和自己想要的位置一样吗?即使你最后写的文字的位置和自己想象的位置是一样的,那么你知道其中的原理吗?如果其中有一个你不能回答出来,那就认真的阅读本文吧!本文会给出你想要的答案…

一个小例子

  看下下面的图,假如下面的图是我们要做出的效果

很简单,有没有?就是在一个圆的中心写文字。红色的圆形很好画出来,那么我们怎么将文字写在圆的中心点上呢?第一时间想到的是拿到圆中心点的坐标,然后直接调用drawText()方法来写文字。实现的代码大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int measuredHeight = getMeasuredHeight();
int measuredWidth = getMeasuredWidth();
//cx,cy为圆的中心点的坐标
int cx = measuredWidth / 2;
int cy = measuredHeight / 2;
canvas.drawCircle(cx, cy, mRadius, mPaint);
mPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.sp18));
mPaint.setColor(Color.WHITE);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.dp1));
canvas.drawText("wizardev", cx, cy, mPaint);
}

现在,看下上面代码实现的效果

上图中的黄线是在圆的中心点画的线,可以发现上面代码实现的效果,明显不是我们想要的效果,为什么会这样呢?下文会给出答案。

drawText()中的基线

  在Android中自定义View的时候,怎么让系统知道应该在哪里画出我们想要的图形呢?比如画上面的圆,这时我们就要告诉系统,我们要画的圆形的圆心在什么位置,告诉系统我们想要的圆的半径是多少,然后系统就能在合适的位置画出你想要的圆了。同样,在画文字的时候我们要指定文字在什么位置,而指定的坐标的位置就是文字的基线。

  要理解drawText()中的基线是什么,需要先了解一下darwText()方法,darwText()方法有四个参数,如下

1
drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

第一个参数为你想要写的内容,第二个参数为文字开始的X轴坐标,第三个参数为文字开始的Y轴坐标,第四个参数为画笔。以第二个第三个参数画的一条水平线,就是drawText()的基线。如上文中的第二张图,黄色的线即为drawText()基线,

注:第二和第三个参数不一定为文字开始的坐标,也可能为文字中心的坐标或则文字结尾的坐标,具体是哪种坐标与Paint中的setTextAlign()方法有关。

  可以得出结论,只要确定了基线的位置就确定了要画的文字的位置了。那么给定一个坐标,怎么确定基线的位置呢?其实画文字的时候,除了基线以外,还有其他几条线,其他几条线的位置如下图

这几条线的意义分别是:

  • ascent: 系统建议的,字符所占的最高高度所在线
  • descent:系统建议的,字符所占的最低高度所在线
  • top: 可绘制的最高高度所在线
  • bottom: 可绘制的最低高度所在线

这几条线的位置可以通过FontMetrics对象获得。

FontMetrics

(1)FontMetrics概述

  描述给定文本的各种度量值的类,它有五个成员变量,分别为topascentdescentbottomleading。这几个成员变量的值都是相对基线位置的距离,如:

FontMetrics.top = top的Y坐标 - 基线

(2)获取FontMetrics对象

想要获取FontMetrics,可以通过getFontMetrics()方法获取,具体代码如下

1
2
3
4
5
6
Paint mPaint = new Paint()
mPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.sp18));
mPaint.setColor(Color.WHITE);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setStrokeWidth(1);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();

(3)FontMetrics注意事项

  使用FontMetrics获取到的topascentdescentbottomleading成员变量的值,是相对于基线的距离,并不是坐标,可以看下下图,方便理解

可以发现topascent的值为负数,descentbottom的值为正数,为什么会这样呢?因为top线和ascent线在基线的上方,FontMetrics对象中的几个成员变量的值都是表示相对基线的位置。

指定位置写文字

  了解了FontMetrics再结合下图

可以得到下面的公式:

  • top的Y坐标 = 基线 + fontMetrics.top
  • ascent的Y坐标 = 基线 + fontMetrics.ascent
  • decent的Y坐标 = 基线 + fontMetrics.descent
  • bottom的Y坐标 = 基线 + fontMetrics.bottom

(1)给定左上方点写字

根据得出的公式可以计算出基线的Y坐标

top的Y坐标 = 基线 + fontMetrics.top

基线 = top的Y坐标 - fontMetrics.top

实现的代码如下

1
2
3
4
5
6
7
Paint mPaint = new Paint();
mPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.sp18));
mPaint.setStrokeWidth(1);
mPaint.setColor(Color.RED);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float baseLine = cy-fontMetrics.top;//cy指定点的Y坐标
canvas.drawText("wizardev", 0, baseLine, mPaint);

(2)给定中间线写文字

给定中间线写文字,可以说是自定义view写文字时用的最多的了,如,将文字写在圆的正中间,如上图,圆的中心线将文字平分,这种就是本文说的给定中间线写文字。文章前面说了,只要确定了基线的位置,文字的位置也就确定了,那么像这种,怎样来确定基线的位置呢?这时我们可以借助其他的几条线来计算出基线的位置。

如上图,将topcenter之间的间距设为A,将centerbaseline之间的距离设为B,将centerbottom之间的距离设为C。这是就可以得出下面的公式

A = C = (bottom - top)/2

B = baseline - center

B = C - (bottom - baseline )

然后根据上文得到的公式:

bottom = fontMetrics.bottom + baseline

top = fontMetrics.top + baseline

可以将最上面的公式修改为:

baseline - center = (fontMetrics.bottom + baseline - fontMetrics.top - baseline) / 2 - (fontMetrics.bottom + baseline - baseline)

最后的到的公式为:

baseline = center - (fontMetrics.bottom - fontMetrics.top) / 2 + fontMetrics.bottom

上面的到的公式就是给出中心线的位置,最后计算出的基线所在位置的公式。

(3)给定底部线线文字

  这种情况应该是最简单的了,直接把给定点的Y坐标作为基线的Y坐标就行了。

结束语

  文章到这里,已经回答了文章开始的几个问题,相信阅读本文之后,你也对自定义View中画文字有了更清晰的理解。如果仍有什么疑问,可以在文章下方留言。

Wizardev wechat
一个有价值的公众号