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
Paint类提供了测量宽高的方法:
getTextBounds(String text, int start, int end, Rect bounds)
返回一个包含所有字符,默认从(0,0)开始的最小矩形的矩形框。
measureText(String text, int start, int end):
返回text的宽度。
很多人会以为指定的坐标都是在左上角,但绘制文本时指定的坐标却不是左上角。
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(基准线)。
利用函数获取宽高:
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() 返回的则是当前文本所需要的最小宽度,也就是整个文本外切矩形的宽度,而且两个函数的精度也是不同的。
什么是基准线?
看上文我们绘制的文本例子:
基准线应该就是除了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);
获取宽度:
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。