博客笔记:自定义View之绘图(1)--drawText
2017-05-22 18:39
274 查看
声明:
发现了一篇自定义View的博客索引,有比较系统的自定义View技术点。欣喜之下准备学习一下,把这些技术点学会,消化,变成自己的东西。做些笔记,或有copy,原文更好。看原文,请转到:启舰的博客—Android自定义控件三部曲文章索引。
记得初学字母时,要用到四线格作业本, 我们将字母按格式写到四线格内,见下图:
canvas在使用drawText绘制文字时是有标准的,它是以基线为基准的。
由图可以看出,基线就相当于四线格的第三条线。基线位置确定,文字位置也就确定了。
下面是canvas的drawText()方法:
传入一个字符串text,设置绘制原点的x、y坐标和一个画笔
但是这个原点坐标(x, y)的位置到底在哪里呢?之前以为是在左上角,但实际不是。其实原点(x, y)跟基线和对齐设置(paint.setTextAlign(align))有关。对齐设置后面再提。如下图,绘制文字“changes”时,起点就在基线上的绿点位置。
坐标(x, y)难搞的是y坐标,绘制图形时,原点(x, y)一般代表的是图形的左上角(left, top),但在绘制文字时,是个例外。y代表的是基线位置,x值确定以后,文字具体怎么显示还跟对齐设置有关。
- [b]2> 代码 [/b]
自定义一个
这里定义绘制文字的原点为(120, 200)。然后设置文字颜色和大小绘制文字,接着画基线和原点。
其中基线是一条从(0, 200)到(3000, 200)的水平线。
然后在布局文件中引用。
效果跟上面一样:
结论:
drawText(text, x, y, paint)t的y就是基线位置。
只有原点(x, y)、文字大小、对齐方式确定后文字位置才真正确定。
上面我们知道了我们确定了drawText(text, x, y, paint)的y就是基线位置。但是,文字绘制的原点(x, y)、文字大小确定后,并不能完全确定文字位置,还要看文字对齐。
我们在drawText(text, x, y, paint)中传入了原点(x, y),y代表基线位置,x就应该是文字绘制的起始了吧?但事实并不是想象的样子。x所代表的其实是水平方向上的一个参考点,文字可以以x为左边缘、右边缘以及中间位置。也就是文字相对于参考点x,有一个相对位置,这个相对位置就是文字对齐。下面是Paint类的设置文字对齐的方法:
- [b]2> 代码和效果 [/b]
下面,我们以(300, 200)为原点,绘制文字“Align”,但我们设置不同的文字对齐,看看有什么效果。代码:
在onDraw()方法中调用drawText_textAlign(canvas, 300, 200),效果如下:
mPaint.setTextAlign(Paint.Align.LEFT)
mPaint.setTextAlign(Paint.Align.CENTER)
mPaint.setTextAlign(Paint.Align.RIGHT)
从效果图看出,原点(x, y)的x坐标,表示文字的相对位置。其实设置文字的对齐,就是相对于原点的对齐:
LEFT,表示文字左边对齐于原点;
CENTER,表示文字中间对齐于原点;
RIGNT,表示文字右边对齐于原点。
这样文字的位置就确定了。同时我们也知道了,确定了原点坐标和对齐方式,文字的位置就确定了。
-图2.1.1-
由图,位置文字时除了基线外,还有四条线(topLine,ascentLine,descentLine,bottomLine),它们意义分别是:
-1> topLine–可绘制最高线
-2> ascentLine–建议绘制单行字符最高线
-3> descentLine–建议绘制单行字符最低线
-4> bottomLine–可绘制最低线
从图中我们还可以看到,ascentLine距离文字顶部的距离大于descentLine距离文字底部的距离,多出来的部分是做什么用呢?看下面的图片:
原来是不同国家文字不同,需要空出空间放置注音等符号。
FontMetric是Paint类的一个静态内部类,可由Paint的getFontMetrics()获得。下面是它的源码:
从上面我们知道了原点的y坐标,表示基线位置。那么其他4条线位置怎么确定,FontMetrics的属性top、ascent、descent、bottom与4条线之间的关系是什么?我们接着看。
除了leading,FontMetrics还有四个值:top,ascent,descent,bottom。
由源码可知,它们分别表示对应线距基线的距离。
由源码和图可知FontMetrics的四个值分别是:
fontMetrics.top = topLineY - baselineY;
fontMetrics.ascent = ascentLineY - baselineY;
fontMetrics.descent = descentLineY - baselineY;
fontMetrics.bottom = bottomLineY - baselineY;
注意:Y值向下为正。这四个值都是以baseline为基准的,那么top和ascent就会为负值。
由上面可得四条线的位置:
topLineY = fontMetrics.top + baselineY;
ascentLineY = fontMetrics.ascent + baselineY;
descentLineY - fontMetrics.descent + baselineY;
bottomLineY = fontMetrics.bottom + baselineY;
而FontMetrics的leading表示的是行距,见下图:
· 获得FontMetrics
- [b]2> 代码和效果 [/b]
上面我们计算通过基线和FontMetrics获得了四条线的位置,我们就可以绘制出这四条线。我们在文字对齐代码基础上绘制这四条线,代码如下:
原点坐标为(60, 300),文字对齐为左对齐,根据FontMetrics和baseLineY计算出四条线位置,然后画出四条线。
效果跟上面一样:
图中绿色矩形是文字所占区域,蓝色区域是仅包裹文字的最小矩形。
文字高度容易获取,直接用bottomLineY - topLineY即可。
- [b]2> 文字宽度 [/b]
文字的宽度要用到Paint类的一个方法:measureText(String text)
注意:使用前,一定要设置好文字的大小(如,mPaint.setTextSize(160f)),不然无法测量文字的宽度。
- [b]3> 最小矩形 [/b]
要得到绘制文字的最小矩形,同样要用到Paint类的一个方法:
使用起来很简单:
使用之前同样需要设置文字大小。
Log打印结果为:
可以看到,这个矩形左上角位置为(2, -144),右下角坐标为(570, 34)。
有点疑惑,左上角的Y坐标为什么是负数?我们在代码中并没有给getTextBounds()设置原点,那么它就是以点(0, 0)为原点(y = 0作为基线),来绘制的矩形。所以跟绘制文字一样,这个最小矩形是以(0, 0)为原点来绘制的(同样以y坐标为基线)。
而我们上面绘制文字时传入了原点,这个最小矩形就会与实际文字位置错开,所以这个矩形需要考虑加上绘制文字时的原点。
经过相加才是正确的最小矩形位置。因为文字以(baseX, baseLineY)为原点绘制,而最小矩形的测量基准为(0, 0)。相当于平移了最小矩形。
- [b]2> 代码和效果 [/b]
上面我们知道了怎样获得文字宽度、高度以及最小矩形,我们就可以绘制出文字所站区域和最小矩形。代码如下:
效果如下:
而我们绘制文字时就是根据原点和文字对齐来定位文字位置的。所以我们需要计算出原点位置(或基线位置)。
上面我们知道:
=>
我们就得到了baseline位置,也就是原点的y坐标;而left就是原点的x坐标。
这里的top就是topLine的y坐标topLineY。
那么原点坐标就是:
先看效果图:
再看代码:
给定了左上定点(60, 60),得到FontMetrics,计算得到基线位置,最后绘制文字。
下面是计算步骤,根据坐标运算:
①centerLine作为ascentLine和descentLine的中间线
centerLineY = (ascentLineY + descentLineY)/2
<=> centerLineY = (ascent + baselineY + descent + baselineY)/2
<=> centerLineY = baselineY + (ascent + descent)/2
<=>baselineY = centerLineY - (ascent + descent)/2
∵ ascent = fontMetrics.ascent, descent = fontMetrics.descent
∴ baseLineY = centerLineY - (fontMetrics.ascent + fontMetrics.descent)/2
②centerLine作为topLine和bottomLine的中间线
centerLineY = (bottomLineY + topLineY)/2
<=> centerLineY = (bottom + baselineY + top + baselineY)/2
<=> centerLineY = baselineY + (bottom + top)/2
<=>baselineY = centerLineY - (bottom + top)/2
∵ bottom = fontMetrics.bottom, top = fontMetrics.top
∴ baseLineY = centerLineY - (fontMetrics.bottom + fontMetrics.top)/2
因为topLine与ascentLine的距离大于bottomLine与descentLine距离。所以①方法更接近与文字中线。选择①的计算结果为基线位置(①和②差别不很大)。
代码如下:
效果:
①baseLineY = centerLineY - (fontMetrics.ascent + fontMetrics.descent)/2
②baseLineY = centerLineY - (fontMetrics.bottom + fontMetrics.top)/2
从效果可以看出,centerLine作为ascentLine和descentLine的中线更接近于文字真正的中线,而作为topLine和bottomLine中线则接近与文字上部。
笔记终于做完,经过自己的学习和验证,多有收获。看似有点耗时,但是确实值得。
发现了一篇自定义View的博客索引,有比较系统的自定义View技术点。欣喜之下准备学习一下,把这些技术点学会,消化,变成自己的东西。做些笔记,或有copy,原文更好。看原文,请转到:启舰的博客—Android自定义控件三部曲文章索引。
一、概述
1. 四线格与基线
在阅读启舰的博客: 自定义控件之绘图篇( 五):drawText()详解后,终于知道,原来canvas在绘制文字时,是有规则的,这个规则就是–基线。这也正是我之前疑惑之一, 为什么总画不好文字,感觉代码没错呀,文字位置为什么不对。看了此博客,终于解惑,感谢启舰的博客^_^。记得初学字母时,要用到四线格作业本, 我们将字母按格式写到四线格内,见下图:
canvas在使用drawText绘制文字时是有标准的,它是以基线为基准的。
由图可以看出,基线就相当于四线格的第三条线。基线位置确定,文字位置也就确定了。
2. canvas.drawText()
- [b]1> canvas.drawText()和基线 [/b]下面是canvas的drawText()方法:
/** * 使用指定的画笔从起点(x,y)开始绘制文字。 根据画笔对齐设置确定起点位置。 * @param text 要绘制的文字 * @param x 文字绘制起点x坐标 * @param y 文字绘制起点y坐标 * @param paint 用于绘制文字的画笔(e.g. color, size, style) */ public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint){ ... }
传入一个字符串text,设置绘制原点的x、y坐标和一个画笔
paint。就可以绘制文字了。
但是这个原点坐标(x, y)的位置到底在哪里呢?之前以为是在左上角,但实际不是。其实原点(x, y)跟基线和对齐设置(paint.setTextAlign(align))有关。对齐设置后面再提。如下图,绘制文字“changes”时,起点就在基线上的绿点位置。
坐标(x, y)难搞的是y坐标,绘制图形时,原点(x, y)一般代表的是图形的左上角(left, top),但在绘制文字时,是个例外。y代表的是基线位置,x值确定以后,文字具体怎么显示还跟对齐设置有关。
- [b]2> 代码 [/b]
自定义一个
DrawTextView,继承
View,重写onDraw()方法,绘制文字和基线。
public class DrawTextView extends View { private Paint mPaint; public DrawTextView(Context context) { super(context); initPaint(); } public DrawTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DrawTextView(Context context, AttributeSet attrs, int defSt 1341a yleAttr) { super(context, attrs, defStyleAttr); initPaint(); } private void initPaint() { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setColor(Color.MAGENTA); mPaint.setStrokeWidth(2); } @Override protected void onDraw(Canvas canvas) { drawTextBase(canvas); //drawText_textAlign(canvas, 300, 200); //drawText_4Lines(canvas); //drawText_textBounds(canvas); //drawText_leftTop(canvas); //drawText_centerLine(canvas); } private void drawTextBase(Canvas canvas) { int baseX = 120; int baseLineY = 200; //写文字 mPaint.setColor(Color.BLACK); mPaint.setTextSize(120f); canvas.drawText("changes", baseX, baseLineY, mPaint); //画基线 mPaint.setColor(Color.RED); canvas.drawLine(0, baseLineY, 3000, baseLineY, mPaint); //绘制原点 mPaint.setColor(Color.GREEN); mPaint.setStrokeWidth(8f); canvas.drawPoint(baseX, baseLineY, mPaint); } ... }
这里定义绘制文字的原点为(120, 200)。然后设置文字颜色和大小绘制文字,接着画基线和原点。
其中基线是一条从(0, 200)到(3000, 200)的水平线。
然后在布局文件中引用。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_draw_text" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.wzhy.customviewdemos.customviews.drawtext.DrawTextView android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
效果跟上面一样:
结论:
drawText(text, x, y, paint)t的y就是基线位置。
只有原点(x, y)、文字大小、对齐方式确定后文字位置才真正确定。
3. 设置文字对齐:paint.setTextAlign(align)
- [b]1> 文字对齐和原点 [/b]上面我们知道了我们确定了drawText(text, x, y, paint)的y就是基线位置。但是,文字绘制的原点(x, y)、文字大小确定后,并不能完全确定文字位置,还要看文字对齐。
我们在drawText(text, x, y, paint)中传入了原点(x, y),y代表基线位置,x就应该是文字绘制的起始了吧?但事实并不是想象的样子。x所代表的其实是水平方向上的一个参考点,文字可以以x为左边缘、右边缘以及中间位置。也就是文字相对于参考点x,有一个相对位置,这个相对位置就是文字对齐。下面是Paint类的设置文字对齐的方法:
/** * 设置Paint的文字对齐。这个方法控制文字相对于起点的位置。左对齐意味着所有的文字 * 将会被绘制在原点的右边(原点指定文字的左边缘),以此类推。 * * @param align 设置Paint绘制文字的对齐参数值。align可以是: * Paint.Align.LEFT 左对齐,文字以原点为左边缘,在原点右边 * Paint.Align.CENTER 居中对齐,文字以原点为中间位置 * Paint.Align.RIGHT 右对齐,文字以原点为右边缘,再远点左边 */ public void setTextAlign(Align align) { nSetTextAlign(mNativePaint, align.nativeInt); }
- [b]2> 代码和效果 [/b]
下面,我们以(300, 200)为原点,绘制文字“Align”,但我们设置不同的文字对齐,看看有什么效果。代码:
private void drawText_textAlign(Canvas canvas, int baseX, int baseY) { //文字大小和对齐 mPaint.setTextSize(120); mPaint.setTextAlign(Paint.Align.LEFT); //mPaint.setTextAlign(Paint.Align.CENTER); //mPaint.setTextAlign(Paint.Align.RIGHT); //绘制文字 mPaint.setColor(Color.BLACK); canvas.drawText("Align", baseX, baseY, mPaint); //画基线 mPaint.setColor(Color.RED); canvas.drawLine(0, baseY, 3000, baseY, mPaint); //画起始线 canvas.drawLine(baseX, 0, baseX, baseY + 60, mPaint); //绘制原点 mPaint.setColor(Color.GREEN); mPaint.setStrokeWidth(8f); canvas.drawPoint(baseX, baseY, mPaint); mPaint.setStrokeWidth(2f); }
在onDraw()方法中调用drawText_textAlign(canvas, 300, 200),效果如下:
mPaint.setTextAlign(Paint.Align.LEFT)
mPaint.setTextAlign(Paint.Align.CENTER)
mPaint.setTextAlign(Paint.Align.RIGHT)
从效果图看出,原点(x, y)的x坐标,表示文字的相对位置。其实设置文字的对齐,就是相对于原点的对齐:
LEFT,表示文字左边对齐于原点;
CENTER,表示文字中间对齐于原点;
RIGNT,表示文字右边对齐于原点。
这样文字的位置就确定了。同时我们也知道了,确定了原点坐标和对齐方式,文字的位置就确定了。
二、绘制文字的四线格和FontMetrics
1. 绘制文字的四线格
上面我们知道文字的基线就是绘制文字原点的y坐标。其实系统绘制文字时还有其他几条线,见下图:-图2.1.1-
由图,位置文字时除了基线外,还有四条线(topLine,ascentLine,descentLine,bottomLine),它们意义分别是:
-1> topLine–可绘制最高线
-2> ascentLine–建议绘制单行字符最高线
-3> descentLine–建议绘制单行字符最低线
-4> bottomLine–可绘制最低线
从图中我们还可以看到,ascentLine距离文字顶部的距离大于descentLine距离文字底部的距离,多出来的部分是做什么用呢?看下面的图片:
原来是不同国家文字不同,需要空出空间放置注音等符号。
2. FontMetrics
- [b]1> FontMetrics源码及概述 [/b]FontMetric是Paint类的一个静态内部类,可由Paint的getFontMetrics()获得。下面是它的源码:
/** * 此类描述了给定文字大小字体的尺寸变量。记住Y值向下为正,向上为负。 * 测量值相对于基线,在基线下的为正值,在基线上的为负值。此类是Paint类 * 的一个静态内部类。保存几个测量值。由Paint的getFontmetrics()返回。 */ public static class FontMetrics { /** * The maximum distance above the baseline for the tallest glyph in the font at a given text size. * 距离baseline之上最大的距离。 */ public float top; /** * The recommended distance above the baseline for singled spaced text. * 在单行文字里距离baseline之上推荐的距离。 */ public float ascent; /** * The recommended distance below the baseline for singled spaced text. * 在单行文字里距离baseline之下推荐的距离。 */ public float descent; /** * The maximum distance below the baseline for the lowest glyph in * the font at a given text size. * 距离baseline之下最大的距离。 */ public float bottom; /** * The recommended additional space to add between lines of text. * 行距:在两行文字之间推荐的额外空间。 */ public float leading; }
从上面我们知道了原点的y坐标,表示基线位置。那么其他4条线位置怎么确定,FontMetrics的属性top、ascent、descent、bottom与4条线之间的关系是什么?我们接着看。
除了leading,FontMetrics还有四个值:top,ascent,descent,bottom。
由源码可知,它们分别表示对应线距基线的距离。
由源码和图可知FontMetrics的四个值分别是:
fontMetrics.top = topLineY - baselineY;
fontMetrics.ascent = ascentLineY - baselineY;
fontMetrics.descent = descentLineY - baselineY;
fontMetrics.bottom = bottomLineY - baselineY;
注意:Y值向下为正。这四个值都是以baseline为基准的,那么top和ascent就会为负值。
由上面可得四条线的位置:
topLineY = fontMetrics.top + baselineY;
ascentLineY = fontMetrics.ascent + baselineY;
descentLineY - fontMetrics.descent + baselineY;
bottomLineY = fontMetrics.bottom + baselineY;
而FontMetrics的leading表示的是行距,见下图:
· 获得FontMetrics
//获得FontMetrics Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); //或 Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt();
- [b]2> 代码和效果 [/b]
上面我们计算通过基线和FontMetrics获得了四条线的位置,我们就可以绘制出这四条线。我们在文字对齐代码基础上绘制这四条线,代码如下:
private void drawText_4Lines(Canvas canvas) { int baseX = 60; int baseLineY = 300; drawText_textAlign(canvas, baseX, baseLineY); //计算四条线的位置 Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); float ascentY = fontMetrics.ascent + baseLineY; float descentY = fontMetrics.descent + baseLineY; float topY = fontMetrics.top + baseLineY; float bottomY = fontMetrics.bottom + baseLineY; //画top mPaint.setColor(Color.BLUE); canvas.drawLine(0, topY, 3000, topY, mPaint); //画ascent mPaint.setColor(Color.GREEN); canvas.drawLine(0, ascentY, 3000, ascentY, mPaint); //画descent mPaint.setColor(Color.MAGENTA); canvas.drawLine(0, descentY, 3000, descentY, mPaint); //画bottom mPaint.setColor(Color.CYAN); canvas.drawLine(0, bottomY, 3000, bottomY, mPaint); }
原点坐标为(60, 300),文字对齐为左对齐,根据FontMetrics和baseLineY计算出四条线位置,然后画出四条线。
效果跟上面一样:
三、所绘文字的宽度、高度和最小矩形的获取
这部分,将了解如何获取文字所占区域的高度、宽度和仅包裹文字的最小矩形。图中绿色矩形是文字所占区域,蓝色区域是仅包裹文字的最小矩形。
1. 文字的宽度和高度
- [b]1> 文字高度 [/b]文字高度容易获取,直接用bottomLineY - topLineY即可。
Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt(); int bottomY = fontMetricsInt.bottom + baseLineY; int topY = fontMetricsInt.top + baseLineY; //所占高度 int height = bottomY - topY;
- [b]2> 文字宽度 [/b]
文字的宽度要用到Paint类的一个方法:measureText(String text)
mPaint.setTextSize(120f); int width = (int) mPaint.measureText(text);
注意:使用前,一定要设置好文字的大小(如,mPaint.setTextSize(160f)),不然无法测量文字的宽度。
- [b]3> 最小矩形 [/b]
要得到绘制文字的最小矩形,同样要用到Paint类的一个方法:
/** * 获取指定字符串所对应的最小矩形,以(0,0)点所在位置为基线 * @param text 要测量最小矩形的字符串 * @param start 要测量起始字符在字符串中的索引 * @param end 所要测量的字符的长度 * @param bounds 接收测量结果 */ public void getTextBounds(String text, int start, int end, Rect bounds){...}
使用起来很简单:
mPaint.setTextSize(120f); /*最小矩形*/ Rect minRect = new Rect(); mPaint.getTextBounds(text, 0, text.length(), minRect); Log.i("Rect", "minRect: " + minRect.toShortString());
使用之前同样需要设置文字大小。
Log打印结果为:
I/Rect: minRect: [2,-144][570,34]
可以看到,这个矩形左上角位置为(2, -144),右下角坐标为(570, 34)。
有点疑惑,左上角的Y坐标为什么是负数?我们在代码中并没有给getTextBounds()设置原点,那么它就是以点(0, 0)为原点(y = 0作为基线),来绘制的矩形。所以跟绘制文字一样,这个最小矩形是以(0, 0)为原点来绘制的(同样以y坐标为基线)。
而我们上面绘制文字时传入了原点,这个最小矩形就会与实际文字位置错开,所以这个矩形需要考虑加上绘制文字时的原点。
minRect.left += baseX; minRect.top += baseLineY; minRect.bottom += baseLineY; minRect.right += baseX;
经过相加才是正确的最小矩形位置。因为文字以(baseX, baseLineY)为原点绘制,而最小矩形的测量基准为(0, 0)。相当于平移了最小矩形。
- [b]2> 代码和效果 [/b]
上面我们知道了怎样获得文字宽度、高度以及最小矩形,我们就可以绘制出文字所站区域和最小矩形。代码如下:
private void drawText_textBounds(Canvas canvas) {
//定义原点
int baseX = 300;
int baseLineY = 300;
//定义要绘制的文字
String text = "AgeÂǎЙ";
//设置文字的大小和对齐方式
mPaint.setTextSize(160f);
mPaint.setTextAlign(Paint.Align.LEFT);
/*字符串所占的高度和宽度*/
Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt();
int bottomY = fontMetricsInt.bottom + baseLineY;
int topY = fontMetricsInt.top + baseLineY;
//所占高度 int height = bottomY - topY;
//宽度
int width = (int) mPaint.measureText(text);
//绘制所占区域
Rect rect = new Rect(baseX, topY, baseX + width, bottomY);
mPaint.setColor(Color.GREEN);
canvas.drawRect(rect, mPaint);
/*最小矩形*/
Rect minRect = new Rect();
mPaint.getTextBounds(text, 0, text.length(), minRect);
Log.i("Rect", "minRect: " + minRect.toShortString());
minRect.left += baseX; minRect.top += baseLineY; minRect.bottom += baseLineY; minRect.right += baseX;
mPaint.setColor(Color.BLUE);
canvas.drawRect(minRect, mPaint);
//绘制文字
mPaint.setColor(Color.BLACK);
canvas.drawText(text, baseX, baseLineY, mPaint);
//画基线
mPaint.setColor(Color.RED);
canvas.drawLine(0, baseLineY, 3000, baseLineY, mPaint);
//画起始线
canvas.drawLine(baseX, 0, baseX, baseLineY + 60, mPaint);
//绘制原点
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(8f);
canvas.drawPoint(baseX, baseLineY, mPaint);
mPaint.setStrokeWidth(2f);
}
效果如下:
四、定点写字
我们在实际绘制文字时,一般不会直接得到原点来绘制文字。较为常见的是给出左上角或水平中线,或被约束在一个宽高中居中显示。而我们绘制文字时就是根据原点和文字对齐来定位文字位置的。所以我们需要计算出原点位置(或基线位置)。
1. 给定左上顶点绘图
如果给出左上顶点(left, top),我们需要计算出基线的位置。上面我们知道:
topLineY = fontMetrics.top + baselineY
=>
baselineY = topLineY - fontMetrics.top
我们就得到了baseline位置,也就是原点的y坐标;而left就是原点的x坐标。
这里的top就是topLine的y坐标topLineY。
那么原点坐标就是:
(left, top - fontMetrics.top)
先看效果图:
再看代码:
给定了左上定点(60, 60),得到FontMetrics,计算得到基线位置,最后绘制文字。
private void drawText_leftTop(Canvas canvas) { String text = "AngelÂ"; int topX = 60; int topY = 60; //设置paint mPaint.setTextSize(200);//单位:px mPaint.setTextAlign(Paint.Align.LEFT); //画左上顶点 mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(8f); canvas.drawPoint(topX, topY, mPaint); mPaint.setStrokeWidth(2f); //画top线 mPaint.setColor(Color.RED); canvas.drawLine(0, topY, 3000, topY, mPaint); //找到基线位置 Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt(); int baseLineY = topY - fontMetricsInt.top; //画基线 mPaint.setColor(Color.GREEN); canvas.drawLine(0, baseLineY, 3000, baseLineY, mPaint); /*写文字*/ mPaint.setColor(Color.BLACK); canvas.drawText(text, topX, baseLineY, mPaint); }
2. 给定中线位置绘制文字
给定中线位置centerLineY,我们同样需要通过它计算出基线位置baselineY才能绘制文字。下面是计算步骤,根据坐标运算:
①centerLine作为ascentLine和descentLine的中间线
centerLineY = (ascentLineY + descentLineY)/2
<=> centerLineY = (ascent + baselineY + descent + baselineY)/2
<=> centerLineY = baselineY + (ascent + descent)/2
<=>baselineY = centerLineY - (ascent + descent)/2
∵ ascent = fontMetrics.ascent, descent = fontMetrics.descent
∴ baseLineY = centerLineY - (fontMetrics.ascent + fontMetrics.descent)/2
②centerLine作为topLine和bottomLine的中间线
centerLineY = (bottomLineY + topLineY)/2
<=> centerLineY = (bottom + baselineY + top + baselineY)/2
<=> centerLineY = baselineY + (bottom + top)/2
<=>baselineY = centerLineY - (bottom + top)/2
∵ bottom = fontMetrics.bottom, top = fontMetrics.top
∴ baseLineY = centerLineY - (fontMetrics.bottom + fontMetrics.top)/2
因为topLine与ascentLine的距离大于bottomLine与descentLine距离。所以①方法更接近与文字中线。选择①的计算结果为基线位置(①和②差别不很大)。
代码如下:
private void drawText_centerLine(Canvas canvas) { String text = "AngelÂ"; int baseX = 120; int centerLineY = 200; //设置文字大小和文字排列 mPaint.setTextSize(200);//单位px mPaint.setTextAlign(Paint.Align.LEFT); //画中线 mPaint.setColor(Color.BLUE); canvas.drawLine(0, centerLineY, 3000, centerLineY, mPaint); //计算基线位置 /* * centerLineY = (ascentLineY + descentLineY)/2 * <=> centerLineY = (ascent + baselineY + descent + baselineY)/2 * <=> centerLineY = baselineY + (ascent + descent)/2 * <=>baselineY = centerLineY - (ascent + descent)/2 * ∵ ascent = fontMetrics.ascent, descent = fontMetrics.descent * ∴ baseLineY = centerLineY - (fontMetrics.ascent + fontMetrics.descent)/2 * */ Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); //float baselineY = centerLineY - (fontMetrics.top + fontMetrics.bottom) / 2; float baselineY = centerLineY - (fontMetrics.ascent + fontMetrics.descent) / 2; //画基线 mPaint.setColor(Color.GREEN); canvas.drawLine(0, baselineY, 3000, baselineY, mPaint); //画文字 mPaint.setColor(Color.BLACK); canvas.drawText(text, baseX, baselineY, mPaint); //画出其他几条线 mPaint.setColor(Color.MAGENTA); float topY = baselineY + fontMetrics.top; float bottomY = baselineY + fontMetrics.bottom; float ascentY = baselineY + fontMetrics.ascent; float decentY = baselineY + fontMetrics.descent; canvas.drawLine(0, topY, 3000, topY, mPaint); canvas.drawLine(0, bottomY, 3000, bottomY, mPaint); canvas.drawLine(0, ascentY, 3000, ascentY, mPaint); canvas.drawLine(0, decentY, 3000, decentY, mPaint); }
效果:
①baseLineY = centerLineY - (fontMetrics.ascent + fontMetrics.descent)/2
②baseLineY = centerLineY - (fontMetrics.bottom + fontMetrics.top)/2
从效果可以看出,centerLine作为ascentLine和descentLine的中线更接近于文字真正的中线,而作为topLine和bottomLine中线则接近与文字上部。
笔记终于做完,经过自己的学习和验证,多有收获。看似有点耗时,但是确实值得。
相关文章推荐
- 笔记—自定义View之DrawText文字绘制
- android 学习笔记 view和surfaceView的2D绘图
- android 自定义View1 笔记
- iOS开发笔记-ios7 UIAlertView自定义
- javascript之博客前端连缀,css自定义封装代码笔记
- 【iOS7开发笔记】tableview之通过代码自定义cell(cell的高度不一致)
- APIDemo学习笔记——在XML中使用自定义的View类
- 笔记83--自定义view--onMesure、MeasureSpec详解
- ios学习笔记----实现一个带滑动手势的tabBarViewController,并可自定义tabBar
- Android 自定义View(2) -- 绘图的基本知识
- Android开发学习笔记-自定义TextView属性模版
- android 自定义View2 笔记
- ((ios开发学习笔记 十一))自定义TableViewCell 的方式实现自定义TableView(带源码)
- Android自定义View研究(二) -- 绘图的基本知识
- 【安卓笔记】自定义view之组合控件
- Android笔记之 自定义程序view写法
- Android自定义View研究(二) -- 绘图的基本知识
- Android酱油笔记之自定义View的宽高
- 【安卓笔记】带自定义属性的view控件
- 安卓学习笔记:1:用自定义View