1.文字基线
文字“基线”就如同写英语字母用的“四线本” 。
在canvas利用drawText绘制文字时,也是有规则的,这个规则就是基线baseLine :给文字定了一条基准线,通过Paint.FontMetricsInt获取的top和bottom值就与基线有关。FontMetrics 基线上面的值为负数,基线下面的值为正数。
可见基线就是四线格中的第三条线!
也就是说,只要基线的位置定了,那文字的位置必然是定了的!
可以通过测试把top、bottom值打印出来,发现 top 为 - 317,bottom 为 82 ,因此可以计算出基线(偏移)为117。
这里的top和bottom的值,实际是基于基线而得。可理解为:以文字基线为准,向上平移317像素为绘制区域的顶,向下平移82像素为绘制区域的底(基线向上为负 向下为正)。
已知基线与绘制区顶相距317,与底相距82,可以得出绘制区高度为bottom - top:82 - (-317) = 399。
绘制区中线高度为(bottom - top)/ 2 : 399 / 2 = 199 (int)。
绘制区中线高度等价于中线与绘制区底之间的距离,这个距离减去 基线与绘制区底 之间的距离,就是中线与基线之间的距离 (bottom - top) / 2 - bottom : 199 - 82 = 117。
2.canvas.drawText
那这个值和我们在android中绘制文字有什么关系呢?
其实在drawText方法中,中传入的第三个参数Y的实际意义是文字的基线所在的位置!
public void drawText(String text, float x, float y, Paint paint)
上面这个构造函数是最常用的drawText方法,传进去一个String对象就能画出对应的文字。这里x、y两个参数需要非常注意,(x,y)是并不是绘制文字所在矩形的左上角的点。比如,要画"harvic's blog"这几个字,这个(x,y)坐标应当是下图中绿色小点的位置。
再强调一遍:y所代表的是基线的位置!
3.paint.setTextAlign(Paint.Align.XXX);
先看一张图:
我们知道,在drawText(text, x, y, paint)中传进去的源点坐标(x,y);其中,y表示的基线的位置。那x代表什么呢?从上面的例子运行结果来看,应当是文字开始绘制的地方。
并不是!x代表所要绘制文字所在矩形的相对位置。相对位置就是指指定点(x,y)在所要绘制矩形的位置。我们知道所绘制矩形的纵坐标是由Y值来确定的,而相对x坐标的位置,只有左、中、右三个位置了。也就是所绘制矩形可能是在x坐标的左侧绘制,也有可能在x坐标的中间,也有可能在x坐标的右侧。而定义x坐标在所绘制矩形相对位置的函数是:
Paint::setTextAlign(Align align);
其中Align的取值为:Panit.Align.LEFT、Paint.Align.CENTER、Paint.Align.RIGHT
现在来看一下,当设置为不同的值时,绘制结果是怎样的。
①setTextAlign(Paint.Align.LEFT)
int baseLineY = 200;
int baseLineX = 0 ;
paint.setColor(Color.GREEN);
paint.setTextSize(120); //以px为单位
paint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("harvic\'s blog", baseLineX, baseLineY, paint);
可见,原点(x,y)在矩形的左侧,即矩形从(x,y)的点开始绘制。
②setTextAlign(Paint.Align.CENTER)
此时原点(x,y)就在所要绘制文字所在矩形区域的正中间,换句话说,系统会根据(x,y)的位置和文字所在矩形大小,会计算出当前开始绘制的点。以使(x,y)正好在所要绘制的矩形的正中间。
③setTextAlign(Paint.Align.RIGHT)
此时原点(x,y)应当在所要绘制矩形的右侧,在上面的代码中,也就是说整个矩形都在(0,200)的左侧,所以我们看到的是什么都没有。
④注意
下面,我们再看一个例子:
我们只写一个大写字母A,然后将其相对位置设置为paint.setTextAlign(Paint.Align.CENTER)
可以看到字母A在原点(0,200)的正中间。
所以,要记住:相对位置是根据所要绘制文字所在矩形来计算的。
3.drawText的四线格
前面讲了基线,其实除了基线,系统在绘制Text时,还是有其它线的,来看个图:
除了基线以外,另外还有四条线,分别是ascent,descent,top,bottom,他们的意义分别是:
ascent: 系统建议的,绘制单个字符时,字符应当的最高高度所在线;
descent:系统建议的,绘制单个字符时,字符应当的最低高度所在线;
top: 可绘制的最高高度所在线;
bottom: 可绘制的最低高度所在线;
绘制文字时,ascent是推荐的绘制文字的最高高度,就表示在绘制文字时,尽量要在这个最高高度以下绘制文字。descent是推荐的绘制文字的最底高度线,同样表示是在绘制文字时尽量在这个descent线以上来绘制文字。而top线则指该文字可以绘制的最高高度线,bottom则是表示该文字可以绘制的最低高度线。ascent,descent是系统建议上的绘制高度,而top,bottom则是物理上屏幕最高,最低可以画的高度值。
4.FontMetrics
上面我们讲了,系统在画文字时的五条线,baseline、ascent、descent、top、bottom。我们知道baseline的位置是在构造drawText()时的参数y来决定的,那ascent,descent,top,bottom这些线的位置要怎么计算出来呢?
Android给我们提供了一个类:FontMetrics,它里面有四个成员变量:
FontMetrics::ascent;
FontMetrics::descent;
FontMetrics::top;
FontMetrics::bottom;
他们的意义与值的计算方法分别如下:
ascent = ascent线的y坐标 - baseline线的y坐标;
descent = descent线的y坐标 - baseline线的y坐标;
top = top线的y坐标 - baseline线的y坐标;
bottom = bottom线的y坐标 - baseline线的y坐标;
再来看个图:
从这个图中,先说明两点,然后再回过头来看上面的公式:
①X轴、Y轴的正方向走向是:X轴向右是正方向,Y轴向下是正方向,所以越往下Y坐标越大!
②千万不要将FontMetrics中的ascent,descent,top,bottom与现实中的ascent,descent,top,bottom所在线混淆!这几条线是真实存在的,而FontMetrics中的ascent,descent,top,bottom这个变量的值就是用来计算这几条线的位置的。
①计算这几条线的位置
ascent = ascent线的y坐标 - baseline线的y坐标;
FontMetrics的这几个变量的值都是以baseline为基准的,对于ascent来说,baseline线在ascent线之下,所以必然baseline的y值要大于ascent线的y值,所以ascent变量的值是负的。
同理,对于descent而言:
descent = descent线的y坐标 - baseline线的y坐标;
descent线在baseline线之下,所以必然descent线的y坐标要大于baseline线的y坐标,所以descent变量的值必然是正数。
下面就来看如何通过这些变量来得到对应线所在位置吧。
我们先列出来一个公式:
ascent线Y坐标 = baseline线Y坐标 + fontMetric.ascent;
推算过程如下:
因为ascent线的Y坐标等于baseline线的Y坐标减去从baseline线到ascent线的这段距离。
也就是:(|fontMetric.ascent|表示取绝对值)
ascent线Y坐标 = baseline线Y坐标 - |fontMetric.ascent|;
又因为fontMetric.ascent是负值,所以:
ascent线Y坐标 = baseline线Y坐标 - |fontMetric.ascent|;
ascent线Y坐标 = baseline线Y坐标 - ( -fontMetric.ascent);
ascent线Y坐标 = baseline线Y坐标 + fontMetric.ascent;
这就是整个推算过程,没什么难度,同理可以得到:
ascent线Y坐标 = baseline线的y坐标 + fontMetric.ascent;
descent线Y坐标 = baseline线的y坐标 + fontMetric.descent;
top线Y坐标 = baseline线的y坐标 + fontMetric.top;
bottom线Y坐标 = baseline线的y坐标 + fontMetric.bottom;
②获取FontMetrics对象
FontMetrics对象是根据paint对象来获取的:
Paint paint = new Paint();
Paint.FontMetrics fm = paint.getFontMetrics();
Paint.FontMetricsInt fmInt = paint.getFontMetricsInt();
可以看到,通过paint.getFontMetrics()得到对应的FontMetrics对象。这里还有另外一个FontMetrics同样的类叫做FontMetricsInt,它的意义与FontMetrics完全相同,只是得到的值的类型不一样而已,FontMetricsInt中的四个成员变量的值都是Int类型,而FontMetrics得到的四个成员变量的值则都是float类型的。
5.实例:计算Text四线格位置
举个例子来看一下如何计算Text绘图中各条线的位置的。
先来看一下效果图:
在这个例子中,我们先写一行字,然后画出这行字中的top线,ascent线,baseline线,descent线和bottom线。
直接上完整代码:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int baseLineY = 200;
int baseLineX = 0 ;
Paint paint = new Paint();
//写文字
paint.setColor(Color.GREEN);
paint.setTextSize(120); //以px为单位
paint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("harvic\'s blog", baseLineX, baseLineY, paint);
//计算各线在位置
Paint.FontMetrics fm =paint.getFontMetrics();
float ascent = baseLineY + fm.ascent;
float descent = baseLineY + fm.descent;
float top = baseLineY + fm.top;
float bottom = baseLineY + fm.bottom;
//画基线
paint.setColor(Color.RED);
canvas.drawLine(baseLineX, baseLineY, 3000, baseLineY, paint);
//画top
paint.setColor(Color.BLUE);
canvas.drawLine(baseLineX, top, 3000, top, paint);
//画ascent
paint.setColor(Color.GREEN);
canvas.drawLine(baseLineX, ascent, 3000, ascent, paint);
//画descent
paint.setColor(Color.YELLOW);
canvas.drawLine(baseLineX, descent, 3000, descent, paint);
//画bottom
paint.setColor(Color.RED);
canvas.drawLine(baseLineX, bottom, 3000, bottom, paint);
}
6.所绘文字宽度、高度和最小矩形获取
这部分,我们将讲解如何获取所绘制字符串所占区域的高度、宽度和仅包裹字符串的最小矩形。
来看张图:
在这张图中,文字底部的绿色框就是所绘制字符串所占据的大小。我们要求的宽度和高度也就是这个绿色框的宽度和高度。
从图中也可以看到,红色框部分,它的宽和高紧紧包围着字符串,所以红色框就是我们要求的最小矩形。即能包裹字符串的最小矩形。
①字符串所占高度和宽度
字符串所占高度很容易得到,直接用bottom线所在位置的Y坐标减去top线所在位置的Y坐标就是字符串所占的高度。
代码如下:
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
int top = baseLineY + fm.top;
int bottom = baseLineY + fm.bottom;
//所占高度
int height = bottom - top;
宽度直接利用下面的函数就可以得到:
int width = paint.measureText(String text);
使用示例如下:
Paint paint = new Paint();
//设置paint
paint.setTextSize(120); //以px为单位
//获取宽度
int width = (int)paint.measureText("harvic\'s blog");
②最小矩形
要获取最小矩形,也是通过系统函数来获取的,函数及意义如下:
/**
* 获取指定字符串所对应的最小矩形,以(0,0)点所在位置为基线
* @param text 要测量最小矩形的字符串
* @param start 要测量起始字符在字符串中的索引
* @param end 所要测量的字符的长度
* @param bounds 接收测量结果
*/
public void getTextBounds(String text, int start, int end, Rect bounds);
我们简单展示下使用代码及结果:
String text = "harvic\'s blog";
Paint paint = new Paint();
//设置paint
paint.setTextSize(120); //以px为单位
Rect minRect = new Rect();
paint.getTextBounds(text,0,text.length(),minRect
Log.e("qijian",minRect.toShortString());
在这段代码中,首先设置字体大小,然后利用paint.getTextBounds()得到最小矩形,最后,我将其打印出来
结果如下:
可以看到这个矩形的左上角位置为(8,-90),右下角的位置为(634,26);
大家可能会有疑问,为什么左上角的Y坐标是个负数?从代码中,我们也可以看到,我们并没有给getTextBounds()传递基线位置。那它就是以(0,0)为基线来得到这个最小矩形的!所以这个最小矩形的位置就是以(0,0)为基线的结果!
③得到最小矩形的实际位置
我们先来看一个原理:
在上面这个图中,我们将黑色矩形平行下移距离Y(黄色线依照的是基线的位置),那么平移后的左上角点的y坐标就是 y2 = y1 + Y;
同样的道理,由于paint.getTextBounds()得到最小矩形的基线是y = 0;那我们直接将这个矩形移动baseline的距离就可以得到这个矩形实际应当在的位置了。
所以矩形应当所在实际位置的坐标是:
Rect minRect = new Rect();
paint.getTextBounds(text,0,text.length(),minRect);
//最小矩形,实际top位置
int minTop = bounds.top + baselineY;
//最小矩形,实际bottom位置
int minBottom = bounds.bottom + baselineY;