Android自定义View

Android绘图之drawText,getTextBounds

2019-08-22  本文已影响13人  sliencexiu

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进行了解释。

获取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

上一篇下一篇

猜你喜欢

热点阅读