Android 文字绘制”基线”★★★

1.文字基线

文字“基线”就如同写英语字母用的“四线本” 。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_19,color_FFFFFF,t_70,g_se,x_16在canvas利用drawText绘制文字时,也是有规则的,这个规则就是基线baseLine :给文字定了一条基准线,通过Paint.FontMetricsInt获取的top和bottom值就与基线有关。FontMetrics 基线上面的值为负数,基线下面的值为正数。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

 可见基线就是四线格中的第三条线!

也就是说,只要基线的位置定了,那文字的位置必然是定了的!

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16可以通过测试把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)坐标应当是下图中绿色小点的位置。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

再强调一遍:y所代表的是基线的位置!

3.paint.setTextAlign(Paint.Align.XXX);

先看一张图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

我们知道,在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);  

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_18,color_FFFFFF,t_70,g_se,x_16

可见,原点(x,y)在矩形的左侧,即矩形从(x,y)的点开始绘制。

②setTextAlign(Paint.Align.CENTER)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_18,color_FFFFFF,t_70,g_se,x_16

此时原点(x,y)就在所要绘制文字所在矩形区域的正中间,换句话说,系统会根据(x,y)的位置和文字所在矩形大小,会计算出当前开始绘制的点。以使(x,y)正好在所要绘制的矩形的正中间。

③setTextAlign(Paint.Align.RIGHT)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_18,color_FFFFFF,t_70,g_se,x_16

此时原点(x,y)应当在所要绘制矩形的右侧,在上面的代码中,也就是说整个矩形都在(0,200)的左侧,所以我们看到的是什么都没有。

④注意

下面,我们再看一个例子:

我们只写一个大写字母A,然后将其相对位置设置为paint.setTextAlign(Paint.Align.CENTER)

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_18,color_FFFFFF,t_70,g_se,x_16

 可以看到字母A在原点(0,200)的正中间。

所以,要记住:相对位置是根据所要绘制文字所在矩形来计算的。

3.drawText的四线格

前面讲了基线,其实除了基线,系统在绘制Text时,还是有其它线的,来看个图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

除了基线以外,另外还有四条线,分别是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坐标;

再来看个图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

从这个图中,先说明两点,然后再回过头来看上面的公式:

①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绘图中各条线的位置的。

先来看一下效果图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_18,color_FFFFFF,t_70,g_se,x_16

 在这个例子中,我们先写一行字,然后画出这行字中的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.所绘文字宽度、高度和最小矩形获取

这部分,我们将讲解如何获取所绘制字符串所占区域的高度、宽度和仅包裹字符串的最小矩形。

来看张图:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_18,color_FFFFFF,t_70,g_se,x_16

 在这张图中,文字底部的绿色框就是所绘制字符串所占据的大小。我们要求的宽度和高度也就是这个绿色框的宽度和高度。

从图中也可以看到,红色框部分,它的宽和高紧紧包围着字符串,所以红色框就是我们要求的最小矩形。即能包裹字符串的最小矩形。

①字符串所占高度和宽度

字符串所占高度很容易得到,直接用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()得到最小矩形,最后,我将其打印出来

结果如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_14,color_FFFFFF,t_70,g_se,x_16

 可以看到这个矩形的左上角位置为(8,-90),右下角的位置为(634,26);

大家可能会有疑问,为什么左上角的Y坐标是个负数?从代码中,我们也可以看到,我们并没有给getTextBounds()传递基线位置。那它就是以(0,0)为基线来得到这个最小矩形的!所以这个最小矩形的位置就是以(0,0)为基线的结果!

③得到最小矩形的实际位置

我们先来看一个原理:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

 在上面这个图中,我们将黑色矩形平行下移距离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;  

猜你喜欢

转载自blog.csdn.net/zenmela2011/article/details/123844220