Android Canvas drawText()文字居中

看过的东西,总是记不住。最好是拿一篇博客讲一讲,或许会理解得更清楚,也会记得更清楚。

1. 需求

在项目开发中,绘制文字并使文字在一定的区域内居中显示,这样的需求不少见。
居中显示包括水平居中和竖直居中两个方面。
先看代码:

/**
 * @author wzc
 * @date 2018/8/4
 */
public class TextCenterView extends View {

    private Paint mPaint = new Paint();

    public TextCenterView(Context context) {
        this(context, null);
    }

    public TextCenterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.CYAN);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(2);
        mPaint.setTextSize(80);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制文字需要居中显示的区域
        mPaint.setColor(Color.CYAN);
        Rect rect = new Rect(((int) (getWidth() * 0.05)), 50, ((int) (getWidth() * 0.95)), 200);
        canvas.drawRect(rect, mPaint);

        // 绘制文字
        mPaint.setColor(Color.RED);
        String string = "willwangwang6";
        canvas.drawText(string, rect.left, rect.bottom, mPaint);

    }
}

运行的效果是:

这显然不符合需求。下面,我们就开始解决这个问题。

2. 水平居中

Paint 类有一个 setTextAlign(Paint.Align align) 的方法。这个方法的作用:设置文字的对齐方式。一共有三个值:LEFT CETNERRIGHT。默认值为 LEFT
我们可以使用这个方法,解决水平不居中的问题。
修改绘制文字部分的代码为:

        // 绘制文字
        mPaint.setColor(Color.RED);
        // 设置文字的对齐方法为 Paint.Align.CENTER
        mPaint.setTextAlign(Paint.Align.CENTER);
        String string = "willwangwang6";
        // 把绘制文字起点的 x 坐标修改为矩形区域 x 方向上的中点
        canvas.drawText(string, rect.centerX(), rect.bottom, mPaint);

运行的效果是:

可以看到文字已经可以水平居中了。这个解决起来还是挺快的。

3. 竖直居中

首先,要理解 Paint.FontMetricsInt 类,它封装了几个文字排印方面的数值:ascent, descent, top, bottom, leading

  • baseline :作为文字显示的基准线。
  • ascent / descent :限制普通字符的顶部和底部范围。普通的字符,上不会高过 ascent ,下不会低过 descentascent 的值为负,descent 的值为正。
  • top / bottom:限制所有字形( glyph )的顶部和底部范围。除了普通字符,有些字形的显示范围是会超过 ascentdescent 的,而 topbottom 则限制的是所有字形的显示范围,包括这些特殊字形。
  • leading:行的额外间距,即对于上下相邻的两行,上行的 bottom 线和下行的 top 线的距离。

在程序中验证一下上述说法:

        // 理解 Paint.FontMetricsInt
        Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt();
        Log.d("TextCenterView", fontMetricsInt.toString());

查看日志:

D/TextCenterView: FontMetricsInt: top=-85 ascent=-74 descent=20 bottom=22 leading=0

为了更加直观,我们分别绘制出上面这些线所在的位置:

        // 画出 baseline 线
        int baseline = rect.bottom;
        mPaint.setColor(Color.LTGRAY);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawLine(rect.left, baseline,  rect.right, baseline, mPaint);
        // 绘制 ascent 线
        int ascentLine = baseline + fontMetricsInt.ascent;
        mPaint.setColor(Color.YELLOW);
        canvas.drawLine(rect.left, ascentLine, rect.right, ascentLine, mPaint);
        // 绘制 top 线
        int topLine = baseline + fontMetricsInt.top;
        mPaint.setColor(Color.DKGRAY);
        canvas.drawLine(rect.left, topLine, rect.right, topLine, mPaint);
        // 绘制 descent 线
        int descentLine = baseline + fontMetricsInt.descent;
        mPaint.setColor(Color.GREEN);
        canvas.drawLine(rect.left, descentLine, rect.right, descentLine, mPaint);
        // 绘制 bottom 线
        int bottomLine = baseline + fontMetricsInt.bottom;
        mPaint.setColor(Color.BLACK);
        canvas.drawLine(rect.left, bottomLine, rect.right, bottomLine, mPaint);

运行的效果是:

可以看到 :

  • top 线是在 baseline 的上边,所以 FontMetricsInt 的 top 值为负,它的绝对值是 top 线和 baseline 线之间的距离;
  • ascent 线是在 baseline 的上边,所以 FontMetricsInt 的 ascent 值为负,它的绝对值是 ascent 线和 baseline 线之间的距离;
  • descent 线是在 baseline 的下边,所以 FontMetricsInt 的 descent 值为正,它的绝对值是 descent 线和 baseline 线之间的距离;
  • bottom 线是在 baseline 的下边,所以 FontMetricsInt 的 bottom 值为正,它的绝对值是 bottom 线和 baseline 线之间的距离。

然后,解决竖直不居中的问题。怎么解决这个问题呢?
其实只要文本区域竖直方向的中点(标记为 textCenterY)和矩形区域竖直方向的中点(标记为 rect.centerY())重合,就可以了。
而 textCenterY
= (文本区域的顶部线 y 坐标值 + 文本区域的底部线 y 坐标值)/ 2
= (topLine + bottomLine) / 2
由上面的代码可以知道:

int topLine = baseline + fontMetricsInt.top;
int bottomLine = baseline + fontMetricsInt.bottom;

所以,textCenterY
= (baseline + fontMetricsInt.top + baseline + fontMetricsInt.bottom) / 2
= baseline + ( fontMetricsInt.top + fontMetricsInt.bottom) / 2
而 textCenterY = rect.centerY()
故 baseline + ( fontMetricsInt.top + fontMetricsInt.bottom) / 2 = rect.centerY()
最后,baseline = rect.centerY() - ( fontMetricsInt.top + fontMetricsInt.bottom) / 2。

把代码里的 baseline 的值替换为推导的结果,来验证一下推导是否正确。
运行的效果:

可以知道推导是正确的。

源码地址

参考

猜你喜欢

转载自blog.csdn.net/willway_wang/article/details/81414347