1.写在前面
在实现自定义控件的过程中,常常会有绘制居中文字的需求,于是在网上搜了一些相关的博客,总是看的一脸懵逼,就想着自己分析一下,在此记录下来,希望对大家能够有所帮助。
2.绘制一段文本
首先把坐标原点移动到控件中心(默认坐标原点在屏幕左上角),这样看起来比较直观一些,然后绘制x、y轴,此时原点向上y为负,向下y为正,向左x为负,向右x为正,以(0,0)坐标开始绘制一段文本:
@Override public void draw(Canvas canvas) { super.draw(canvas); // 将坐标原点移到控件中心 canvas.translate(getWidth() / 2, getHeight() / 2); // x轴 canvas.drawLine(-getWidth() / 2, 0, getWidth() / 2, 0, paint); // y轴 canvas.drawLine(0, -getHeight() / 2, 0, getHeight() / 2, paint); // 绘制文字 paint.setTextSize(sp2px(50)); canvas.drawText("YangLe", 0, 0, paint); }
看下绘制的文本:
绘制文本
咦,为什么绘制的文本在第一象限,y坐标不是指定的0吗,为什么文本没有在x轴的上面或下面,而是穿过了x轴,带着这些疑问继续往下看:
首先看一个重要的类:
public static class FontMetrics { /** * The maximum distance above the baseline for the tallest glyph in * the font at a given text size. */ public float top; /** * The recommended distance above the baseline for singled spaced text. */ public float ascent; /** * The recommended distance below the baseline for singled spaced text. */ public float descent; /** * The maximum distance below the baseline for the lowest glyph in * the font at a given text size. */ public float bottom; /** * The recommended additional space to add between lines of text. */ public float leading; }
FontMetrics类是Paint的一个内部类,主要定义了绘制文本时的一些关键坐标位置,看下这些值都代表什么:
关键坐标
看图说话:
下面再来看看drawText这个方法:
/** * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted * based on the Align setting in the paint. * * @param text The text to be drawn * @param x The x-coordinate of the origin of the text being drawn * @param y The y-coordinate of the baseline of the text being drawn * @param paint The paint used for the text (e.g. color, size, style) */ public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) { super.drawText(text, x, y, paint); }
重点看下x、y参数的含义:
有点难理解,举个栗子,上文中的x、y参数传的是(0,0),此时的baseline正好是坐标系中x轴,就相当于从y轴开始向右绘制,以x轴作为文本的baseline进行绘制。
如果参数传(0,10),此时绘制文本的baseline从x轴开始向下移动10px,也就是以y10作为文本的baseline进行绘制,y10就是绘制文本的baseline在y轴方向的位置。
注意:baseline是绘制文本的基线,相对于绘制文本区域来说,相当于x轴,向上为负(top、ascent),向下为正(descent、bottom),但是这个x轴并不是控件的x轴,切记切记!!!
还记得我们在上文中提出的疑问吗,这下可以解释了:
为什么绘制的文本在第一象限?
因为我们把坐标原点移到了控件中心,文本的baseline正好为x轴,top、ascent值为负,所以绘制的文本在第一象限。
y坐标不是指定的0吗,为什么文本没有在x轴的上面或下面,而是穿过了x轴?
drawText方法默认x轴方向是从左到右绘制的,y轴方向是从baseline为基准绘制的,文中的baseline正好为x轴,以baseline为基准绘制文本向下还有一段距离,所以文本穿过了x轴。
3.绘制居中的文本
在上文中,我们学习了如何绘制一段文本,以及其中参数和坐标的含义,接下来进入正题,看下如何才能绘制居中的文本。
首先看一张图,此时文本的baseline正好为x轴,如果想要文本居中显示的话,就需要先计算文本的宽度和高度:
现在有了宽度,把绘制文本的x坐标向左移动(宽度 / 2)就可以水平居中,但是垂直方向就不能这么干了,我们要将文本向下移动baseline到文本中心的距离,也就是(高度 / 2 - fontMetrics.descent),如下图所示:
计算baseLineY
现在的公式为:
float baseLineY = (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent; = -fontMetrics.ascent / 2 - fontMetrics.descent / 2; = -(fontMetrics.ascent + fontMetrics.descent) / 2; = Math.abs(fontMetrics.ascent + fontMetrics.descent) / 2;
Paint中也有获取ascent和descent值的方法,所以公式最终为:
float baseLineY = Math.abs(paint.ascent() + paint.descent()) / 2;
注意:此公式是相对于坐标原点在控件中心来计算的,如果坐标原点在左上角,baseLineY需要加上控件高度的一半。
float baseLineY = height / 2 + Math.abs(paint.ascent() + paint.descent()) / 2;
看下代码:
@Override public void draw(Canvas canvas) { super.draw(canvas); // 将坐标原点移到控件中心 canvas.translate(getWidth() / 2, getHeight() / 2); // x轴 canvas.drawLine(-getWidth() / 2, 0, getWidth() / 2, 0, paint); // y轴 canvas.drawLine(0, -getHeight() / 2, 0, getHeight() / 2, paint); // 绘制居中文字 paint.setTextSize(sp2px(50)); paint.setColor(Color.GRAY); // 文字宽 float textWidth = paint.measureText("YangLe'Blog"); // 文字baseline在y轴方向的位置 float baseLineY = Math.abs(paint.ascent() + paint.descent()) / 2; canvas.drawText("YangLe'Blog", -textWidth / 2, baseLineY, paint); }
看下居中了吗:
绘制居中文本
大功告成!
4.绘制多行居中的文本
注意:drawText方法不支持绘制多行文本
4.1 方式一
使用支持自动换行的StaticLayout:
/** * 绘制多行居中文本(方式1) * * @param canvas 画布 */ private void drawCenterMultiText1(Canvas canvas) { String text = "ABC"; // 画笔 TextPaint textPaint = new TextPaint(); textPaint.setAntiAlias(true); textPaint.setColor(Color.GRAY); // 设置宽度超过50dp时换行 StaticLayout staticLayout = new StaticLayout(text, textPaint, dp2px(50), Layout.Alignment.ALIGN_CENTER, 1f, 0f, false); canvas.save(); // StaticLayout默认从(0,0)点开始绘制 // 如果需要调整位置,只能在绘制之前移动Canvas的起始坐标 canvas.translate(-staticLayout.getWidth() / 2, -staticLayout.getHeight() / 2); staticLayout.draw(canvas); canvas.restore(); }
看下StaticLayout的构造方法参数含义:
public StaticLayout(CharSequence source, TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad) { this(source, 0, source.length(), paint, width, align, spacingmult, spacingadd, includepad); }
看下效果:
StaticLayout
使用StaticLayout,每行设置的宽度是相同的,当需求为每行显示不同长度的文本时,这种方式就不能使用了,别担心,接着来看下第二种方式。
4.2 方式二
使用循环drawText的方式进行绘制,看图说话:
计算baseLineY
现在需要绘制A、B、C三行文本,红色A代表每行文本默认的绘制位置,绿色的线代表每行文本的baseline,x轴为红色A的baseline,现在分为三种情况:
看下代码:
/** * 绘制多行居中文本(方式2) * * @param canvas 画布 */ private void drawCenterMultiText2(Canvas canvas) { String[] texts = {"A", "B", "C"}; Paint.FontMetrics fontMetrics = paint.getFontMetrics(); // top绝对值 float top = Math.abs(fontMetrics.top); // ascent绝对值 float ascent = Math.abs(fontMetrics.ascent); // descent,正值 float descent = fontMetrics.descent; // bottom,正值 float bottom = fontMetrics.bottom; // 行数 int textLines = texts.length; // 文本高度 float textHeight = top + bottom; // 文本总高度 float textTotalHeight = textHeight * textLines; // 基数 float basePosition = (textLines - 1) / 2f; for (int i = 0; i < textLines; i++) { // 文本宽度 float textWidth = paint.measureText(texts[i]); // 文本baseline在y轴方向的位置 float baselineY; if (i < basePosition) { // x轴上,值为负 // 总高度的/2 - 已绘制的文本高度 - 文本的top值(绝对值) baselineY = -(textTotalHeight / 2 - textHeight * i - top); } else if (i > basePosition) { // x轴下,值为正 // 总高度的/2 - 未绘制的文本高度 - 文本的bottom值(绝对值) baselineY = textTotalHeight / 2 - textHeight * (textLines - i - 1) - bottom; } else { // x轴中,值为正 // 计算公式请参考单行文本居中公式 baselineY = (ascent - descent) / 2; } canvas.drawText(texts[i], -textWidth / 2, baselineY, paint); } }
对照上图再看代码就很好理解了,觉得代码中的公式还有可以优化的地方,如果你有好的方法,可以留言告诉我哈。
再看下中文版的多行文本:
多行居中文本
5.TextAlign
Paint的TextAlign属性决定了绘制文本相对于drawText方法中x参数的相对位置。
举个栗子:
看图理解下:
Paint.Align.LEFT
Paint.Align.CENTER
Paint.Align.RIGHT
6.文本居中的公式
坐标原点在控件中心:
float baseLineY = Math.abs(paint.ascent() + paint.descent()) / 2;
坐标原点在控件左上角:
float baseLineY = height / 2 + Math.abs(paint.ascent() + paint.descent()) / 2;
7.写在最后
源码已经上传到GitHub上了,欢迎Fork,觉得还不错就Start一下吧!
GitHub传送门
点我下载本文Demo的Apk
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对小牛知识库的支持。
我有一个容器div,其中高度和宽度设置为100%,位置是相对的。在div I内部,使用display:block和margin:auto将图像居中(图像小于div)。然后我尝试使用position:absolute,left:45%,top 82px将图像中的文本居中。垂直对齐看起来不错,但是随着文本中字符数量的增长,文本不再在中间对齐。因此,在我下面的图像中,如果文本为4个字符,文本将不再居中。
问题内容: 该属性是使用CSS居中图像的好方法吗? 问题答案: 这将无效,因为该属性适用于块容器,而不适用于内联元素,而是内联元素。请参阅W3C规范。 使用此代替:
假设我有一个bootstrap按钮,它有一个字体很棒的图标和一些文本: 如何使文本显示为垂直居中?文本现在与图标的底部边缘对齐:http://jsfidle.net/v7dlm/1/ 编辑:我有一个可能的解决方案:http://jsfidle.net/cldeg/1/但感觉有点怪--引入额外的p标记,使用拉左和display:table。也许有人可以建议一个更简单的方法。
本文向大家介绍怎么让div中的图片和文字同时上下居中?相关面试题,主要包含被问及怎么让div中的图片和文字同时上下居中?时的应答技巧和注意事项,需要的朋友参考一下 给 img 设置 并且 只对行内元素,还有表格内的单元格有效,对块级元素的垂直居中是没有任何效果的 MDN 文档说明
本文向大家介绍JavaScript中关于base64的一些事,包括了JavaScript中关于base64的一些事的使用技巧和注意事项,需要的朋友参考一下 base64 其实是一种编码转换方式, 将 ASCII 字符转换成普通文本, 是网络上最常见的用于传输8Bit字节代码的编码方式之一。 base64 由字母 a-z 、 A-Z 、 0-9 以及 + 和 / , 再加上作为垫字的 = , 一共6
我有一个很简单的情况,我似乎无法解决。我想将文本垂直居中到FontAwesome图标。但我一辈子都想不出为什么它不居中。 我尝试过其他答案,但没有一个使用:before CSS定义,这是一种非常普遍的实现图标的方法。