Android绘图之drawText,getTextBounds,measureText,FontMetrics,基线(14)

Android 绘图学习

1如何测量一段文本占用的长度和宽度

Paint类提供了测量宽高的方法:
getTextBounds(String text, int start, int end, Rect bounds)
返回一个包含所有字符,默认从(0,0)开始的最小矩形的矩形框。
measureText(String text, int start, int end):
返回text的宽度。

2问题1 drawText函数指定的坐标(x,y)在哪个位置

很多人会以为指定的坐标都是在左上角,但绘制文本时指定的坐标却不是左上角。
drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
利用Canvas.drawText绘制文本,并在(x,y)处绘制两个点

mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(120);
mPaint.setTextAlign(Paint.Align.LEFT);

mPaint2 = new Paint();
mPaint2.setColor(Color.RED);
mPaint2.setAntiAlias(true);
mPaint2.setStrokeWidth(10);
mPaint2.setStyle(Paint.Style.FILL);

canvas.drawText(textStr,100,200,mPaint);
canvas.drawText(textStr,100,500,mPaint);
canvas.drawPoint(100,200,mPaint2);
canvas.drawPoint(100,500,mPaint2);
Rect rect1 = new Rect();

可以看到canvas调用drawText时指定的坐标点不是绘制文字是的左上角的坐标,而是左下角的坐标,从drawText的函数说明中可以知道,调用drawText时指定的坐标(x,y)其中y坐标被称为文本的baseline(基准线)。

3 问题2利用getTextBounds获取文本的宽高,measureText获取宽度

利用函数获取宽高:

mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(120);
mPaint.setTextAlign(Paint.Align.LEFT);

mPaint2 = new Paint();
mPaint2.setColor(Color.RED);
mPaint2.setAntiAlias(true);
mPaint2.setStrokeWidth(10);
mPaint2.setStyle(Paint.Style.FILL);

canvas.drawText(textStr,100,200,mPaint);
canvas.drawText(textStr,100,500,mPaint);
canvas.drawPoint(100,200,mPaint2);
canvas.drawPoint(100,500,mPaint2);
Rect rect1 = new Rect();
mPaint.getTextBounds(textStr,0,textStr.length(),rect1);
float textwidth =  mPaint.measureText(textStr);

Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.RED);
canvas.drawLine(100,200,100+textwidth,200,paint);
canvas.drawLine(100,500,100+textwidth,500,paint);

 System.out.println("=============getTextBounds111=================="+rect1.left+" "+rect1.right+"  "+rect1.top+"  "+rect1.bottom);
 System.out.println("=============getTextBounds222=================="+rect1.width()+"  "+rect1.height());
System.out.println("=============measureText=================="+textwidth);

获取的结果:
利用getTextBounds获取到的值包括:
rect1.left:6
rect1.right: 1212
rect1.top: -107
rect1.bottom: 27
rect1.width():1206
rect1.height(): 134

利用measureText函数获取文本宽度textwidth 的值为1212.
利用measureText获取的宽度比getTextBounds获取的宽度稍大。
思考:
通常我们获取文字的宽高是利用rect1.width()和rect1.height(),利用measureText获取到的值又是什么,为何不一样。利用getTextBounds获取到的rect的上下左右坐标分别代表什么,为什么有负值。(先公布答案,位于基准线之上的位置都为负)

两种测量方式为什么不同(参考网上):
measureText() 会在文本的左右两侧加上一些额外的宽度,这部分额外的宽度叫做 Glyph's AdvanceX (具体应该是属于字型方面的范畴,我猜测这部分宽度是类似字间距之类的东西)
getTextBounds() 返回的则是当前文本所需要的最小宽度,也就是整个文本外切矩形的宽度,而且两个函数的精度也是不同的。

4 FontMetrics

什么是基准线?
看上文我们绘制的文本例子:

基准线应该就是除了g,j两个字符其余字符所在的底部,仔细 观察这些英文字母可以知道,英文字母分成三类,一类是aceim,一类是gj,一类是bdfhl,回想初中时刚开始学习英语时写英文时的四线三格本,基准线应该就是四线中的第三根线。


FontMetrics 类说明:
Class that describes the various metrics for a font at a given text size. Remember, Y values increase going down, so those values will be positive, and values that measure distances going up will be negative. This class is returned by getFontMetrics().

FontMetrics用于描述指定了字体大小的文本字体的变量指标。


FontMetrics中的每个字段都跟baseline有关,上面已经对baseLine进行了解释。

  • ascent是baseline之上至字符最高处的距离
  • descent是baseline之下至字符最低处的距离
  • leading是上一行字符的descent到下一行的ascent之间的距离,如果只有一行这个值为0,计算字体高度有时也需要加上这个数据。
  • top是最高字符到baseline的值,即ascent的最大值
  • bottom最下字符到baseline的值,即descent的最大值

获取FontMetrics :

mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(120);
mPaint.setTextAlign(Paint.Align.LEFT);

mPaint2 = new Paint();
mPaint2.setColor(Color.RED);
mPaint2.setAntiAlias(true);
mPaint2.setStrokeWidth(10);
mPaint2.setStyle(Paint.Style.FILL);
canvas.drawText(textStr,100,200,mPaint);
canvas.drawText(textStr,100,500,mPaint);
canvas.drawPoint(100,200,mPaint2);
canvas.drawPoint(100,500,mPaint2);
Rect rect1 = new Rect();
mPaint.getTextBounds(textStr,0,textStr.length(),rect1);
float textwidth =  mPaint.measureText(textStr);

Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
System.out.println("=========="+fontMetrics.top);
System.out.println("=========="+fontMetrics.bottom);
System.out.println("=========="+fontMetrics.ascent);
System.out.println("=========="+fontMetrics.descent);
System.out.println("=========="+fontMetrics.leading);

fontMetrics.top:-126.738
fontMetrics.bottom:32.51953
fontMetrics.ascent:-111.328
fontMetrics.descent:29.29
fontMetrics.leading:0.0

fontMetrics.top 和fontMetrics.ascent为负都是相对于baseline来说的,所以利用getTextBounds获取到的rect的top也是相对于baseline来说为负。

借用网上示意图,感谢作者:


top ,bottom是descent,ascent的特殊值,所以两个值是类似的。

getTextBounds获取的高度是不准确的,想要最大化获取字体的高度,应该使用descent的绝对值,加上ascent的绝对值。

mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(120);
mPaint.setTextAlign(Paint.Align.LEFT);

mPaint2 = new Paint();
mPaint2.setColor(Color.RED);
mPaint2.setAntiAlias(true);
mPaint2.setStrokeWidth(10);
mPaint2.setStyle(Paint.Style.FILL);
        canvas.drawText(textStr,100,200,mPaint);
        canvas.drawText(textStr,100,500,mPaint);
        canvas.drawPoint(100,200,mPaint2);
        canvas.drawPoint(100,500,mPaint2);
        Rect rect1 = new Rect();
        mPaint.getTextBounds(textStr,0,textStr.length(),rect1);
        float textwidth =  mPaint.measureText(textStr);

        Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(2);
        paint.setColor(Color.RED);
        canvas.drawLine(100,200,100+textwidth,200,paint);
        canvas.drawLine(100,500,100+textwidth,500,paint);

        paint.setColor(Color.BLUE);

        canvas.drawLine(100,(200 + fontMetrics.top),100+textwidth,(200 + fontMetrics.top),paint);
        canvas.drawLine(100,(500 + fontMetrics.top),100+textwidth,(500 + fontMetrics.top),paint);

        paint.setColor(Color.YELLOW);
        canvas.drawLine(100,(200 + fontMetrics.ascent),100+textwidth,(200 + fontMetrics.ascent),paint);
        canvas.drawLine(100,(500 + fontMetrics.ascent),100+textwidth,(500 + fontMetrics.ascent),paint);

        paint.setColor(Color.GREEN);
        canvas.drawLine(100,200+(fontMetrics.bottom),100+textwidth,200+(fontMetrics.bottom),paint);
        canvas.drawLine(100,500+(fontMetrics.bottom),100+textwidth,500+(fontMetrics.bottom),paint);

        paint.setColor(Color.BLACK);
        canvas.drawLine(100,200+(fontMetrics.descent),100+textwidth,200+(fontMetrics.descent),paint);
        canvas.drawLine(100,500+(fontMetrics.descent),100+textwidth,500+(fontMetrics.descent),paint);

        paint.setColor(Color.RED);

可以看到baseline加上ascent之后,没有紧挨着字母的顶部,这时因为好多字在顶部还有很多符号,类似:

利用红线框把getTextBounds的rect绘制出来:

 mPaint = new Paint();
    mPaint.setColor(Color.BLUE);
    mPaint.setAntiAlias(true);
    mPaint.setStrokeWidth(5);
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setTextSize(120);
    mPaint.setTextAlign(Paint.Align.LEFT);

    mPaint2 = new Paint();
    mPaint2.setColor(Color.RED);
    mPaint2.setAntiAlias(true);
    mPaint2.setStrokeWidth(10);
    mPaint2.setStyle(Paint.Style.FILL);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawText(textStr,100,200,mPaint);
    canvas.drawText(textStr,100,500,mPaint);
    canvas.drawPoint(100,200,mPaint2);
    canvas.drawPoint(100,500,mPaint2);
    Rect rect1 = new Rect();
    mPaint.getTextBounds(textStr,0,textStr.length(),rect1);
    float textwidth =  mPaint.measureText(textStr);

    Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(2);
    paint.setColor(Color.RED);
    canvas.drawLine(100,200,100+textwidth,200,paint);
    canvas.drawLine(100,500,100+textwidth,500,paint);

    paint.setColor(Color.BLUE);

    canvas.drawLine(100,(200 + fontMetrics.top),100+textwidth,(200 + fontMetrics.top),paint);
    canvas.drawLine(100,(500 + fontMetrics.top),100+textwidth,(500 + fontMetrics.top),paint);

    paint.setColor(Color.YELLOW);
    canvas.drawLine(100,(200 + fontMetrics.ascent),100+textwidth,(200 + fontMetrics.ascent),paint);
    canvas.drawLine(100,(500 + fontMetrics.ascent),100+textwidth,(500 + fontMetrics.ascent),paint);

    paint.setColor(Color.GREEN);
    canvas.drawLine(100,200+(fontMetrics.bottom),100+textwidth,200+(fontMetrics.bottom),paint);
    canvas.drawLine(100,500+(fontMetrics.bottom),100+textwidth,500+(fontMetrics.bottom),paint);

    paint.setColor(Color.BLACK);
    canvas.drawLine(100,200+(fontMetrics.descent),100+textwidth,200+(fontMetrics.descent),paint);
    canvas.drawLine(100,500+(fontMetrics.descent),100+textwidth,500+(fontMetrics.descent),paint);

    paint.setColor(Color.RED);

    canvas.drawRect(100+rect1.left,200+(rect1.top),100+rect1.right,200+(rect1.bottom),paint);
    canvas.drawRect(100+rect1.left,500+(rect1.top),100+rect1.right,500+(rect1.bottom),paint);

红色矩形就是getTextBounds的rect

5获取text宽高

获取宽度:

public static float getTextWidth(String text, float textSize){
   Paint paint = new Paint();
   paint.setTextSize(textSize);
   return paint.measureText(text);
}

获取高度:

public static float getTextHeight(float textSize){
   Paint paint = new Paint();
   paint.setTextSize(textSize);
   FontMetrics fm = paint.getFontMetrics();   
       return fm.descent - fm.ascent;
}

上面只是示例代码,如果精度要求不高,getTextBounds依然是可以使用的。

如果是多行文本,还需要考虑leading。

android绘图之Paint(1)
android绘图之Canvas基础(2)
Android绘图之Path(3)
Android绘图之drawText绘制文本相关(4)
Android绘图之Canvas概念理解(5)
Android绘图之Canvas变换(6)
Android绘图之Canvas状态保存和恢复(7)
Android绘图之PathEffect (8)
Android绘图之LinearGradient线性渐变(9)
Android绘图之SweepGradient(10)
Android绘图之RadialGradient 放射渐变(11)
Android绘制之BitmapShader(12)
Android绘图之ComposeShader,PorterDuff.mode及Xfermode(13)
Android绘图之drawText,getTextBounds,measureText,FontMetrics,基线(14)
Android绘图之贝塞尔曲线简介(15)
Android绘图之PathMeasure(16)
Android 动态修改渐变 GradientDrawable

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容