日常安卓开发中,经常会碰到下面这种样式的TextView。
UI给的原本设计,文字是需要完全居中的,不知道有没有眼尖的小朋友会发现文字的上下边距是不一样的,上面边距比下面边距大一些。我的TextView明明设置了android:gravity="center"属性,为啥不是居中的?要想搞清楚这个问题,就要看看安卓系统是如何绘制我们文字的。
这里涉及到几个概念,分别是文本的top,bottom,ascent,descent,baseline。 看下面的图:
baseline是基线,在Android中绘制文本都是从baseline处开始的,从baseline往上至文本最高处的距离称之为ascent,baseline至文本最低处的距离称之为descent。top和bottom是绘制文本时在最外层留出的一些内边距。
baseline以上是负值,baseline以下是正值,因此ascent和top都是负值,descent和bottom都是正值。
文本的实际高度应该就是descent-asscent,但是一般都是以top-bottom作为文本的高度。 为什么要这么做呢?因为Android要考虑到适配世界上的各种语言,有些语言的文字顶部是会带有各种符号的,如下图的拉丁文文字,就是带有读音符号的。
ascent,descent,top,bottom都是文本内容的属性,这些属性都是FontMetrics这个类中的属性,FontMetrics对象可以通过Paint画笔对象mPaint.getFontMetrics()方法来获取,但是要是在paint.setTextSize(mTextSize)之后获取。 获取到Paint的FontMetrics对象之后,就可以获取到ascent,descent,top,bottom这些属性值了。
Android在自定义View中,使用drawText(String text, float x, float y, Paint paint)方法绘制文字,这里的x参数是绘制的文本的最左边的横坐标,y是baseline的纵坐标,注意这里的y不是绘制文本的纵坐标,而是baseline的纵坐标。
我们经常碰到一个需求就是在View的中心处绘制文本,那么以前看到网上看到最多的在View的中心处绘制文本的公式:
x = 控件的宽度 / 2 - 文本的宽度 / 2
y = 控件的高度 / 2 + 文本的高度 / 2
都是把y当作是绘制文本的纵坐标,其实这是不正确的,这种公式绘制出的文本并不是居中的。因为y参数并不是绘制文本的纵坐标,而是baseline的纵坐标。
那么这里根据上面ascent,descent,top,bottom概念的学习,给出在View中心处绘制文字的x,y值的计算公式:
Paint mPaint = new Paint();
midAngelPaint.setTextSize(textSize);
x = getWidth() / 2 - mPaint.measureText(mText) / 2;
Paint.FontMetricsInt fm = mPaint.getFontMetricsInt();
y = getHeight() / 2 + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
// getWidth(): 控件的宽度(view的宽度)
// getHeight():控件的高度(view的高度)
// mPaint.measureText(mText)是精确的测出绘制文本的宽度
// fontMetrics.bottom-fontMetrics.top就是绘制文本的高度。
// y计算出来就是baseline的纵坐标
如果我们只需要绘制单行的中文文字或者数字,为了追求绝对的居中,我们是可以不考虑其他语言的兼容性,所以y也可以这样计算:
y = getHeight() / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;