测量要在 Canvas 上绘制的文本高度(Android)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3654321/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 11:11:25  来源:igfitidea点击:

Measuring text height to be drawn on Canvas ( Android )

androidgraphicsviewandroid-canvas

提问by Danedo

Any straight forward way to measure the height of text? The way I am doing it now is by using Paint's measureText()to get the width, then by trial and error finding a value to get an approximate height. I've also been messing around with FontMetrics, but all these seem like approximate methods that suck.

任何直接的方法来测量文本的高度?我现在这样做的方法是使用 PaintmeasureText()来获取宽度,然后通过反复试验找到一个值来获得近似高度。我也一直在搞乱FontMetrics,但所有这些似乎都是很糟糕的近似方法。

I am trying to scale things for different resolutions. I can do it, but I end up with incredibly verbose code with lots of calculations to determine relative sizes. I hate it! There has to be a better way.

我正在尝试为不同的分辨率缩放东西。我可以做到,但我最终得到了令人难以置信的冗长代码,其中包含大量计算以确定相对大小。我恨它!一定有更好的方法。

回答by Suragch

There are different ways to measure the height depending on what you need.

根据您的需要,有多种测量高度的方法。

getTextBounds

获取文本边界

If you are doing something like precisely centering a small amount of fixed text, you probably want getTextBounds. You can get the bounding rectangle like this

如果您正在执行诸如将少量固定文本精确居中之类的操作,您可能需要getTextBounds. 您可以像这样获得边界矩形

Rect bounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), bounds);
int height = bounds.height();

As you can see for the following images, different strings will give different heights (shown in red).

正如您在以下图像中看到的,不同的字符串将给出不同的高度(以红色显示)。

enter image description here

在此处输入图片说明

These differing heights could be a disadvantage in some situations when you just need a constant height no matter what the text is. See the next section.

在某些情况下,无论文本是什么,当您只需要一个恒定的高度时,这些不同的高度可能是不利的。请参阅下一节。

Paint.FontMetrics

Paint.FontMetrics

You can calculate the hight of the font from the font metrics. The height is always the same because it is obtained from the font, not any particular text string.

您可以根据字体度量计算字体的高度。高度总是相同的,因为它是从字体中获得的,而不是任何特定的文本字符串。

Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float height = fm.descent - fm.ascent;

The baseline is the line that the text sits on. The descent is generally the furthest a character will go below the line and the ascent is generally the furthest a character will go above the line. To get the height you have to subtract ascent because it is a negative value. (The baseline is y=0and ydescreases up the screen.)

基线是文本所在的行。下降通常是角色在线下最远的距离,而上升通常是角色在线上方的最远距离。要获得高度,您必须减去上升,因为它是一个负值。(基线是y=0ydescreases了屏幕。)

Look at the following image. The heights for both of the strings are 234.375.

看下图。两根弦的高度都是234.375

enter image description here

在此处输入图片说明

If you want the line height rather than just the text height, you could do the following:

如果您想要行高而不仅仅是文本高度,您可以执行以下操作:

float height = fm.bottom - fm.top + fm.leading; // 265.4297

These are the bottomand topof the line. The leading (interline spacing) is usually zero, but you should add it anyway.

这些是线的bottomtop。前导(行间距)通常为零,但无论如何您都应该添加它。

The images above come from this project. You can play around with it to see how Font Metrics work.

上面的图片来自这个项目。您可以使用它来查看字体度量标准是如何工作的。

StaticLayout

静态布局

For measuring the height of multi-line text you should use a StaticLayout. I talked about it in some detail in this answer, but the basic way to get this height is like this:

要测量多行文本的高度,您应该使用StaticLayout. 我在这个答案中详细讨论了它,但是获得这个高度的基本方法是这样的:

String text = "This is some text. This is some text. This is some text. This is some text. This is some text. This is some text.";

TextPaint myTextPaint = new TextPaint();
myTextPaint.setAntiAlias(true);
myTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
myTextPaint.setColor(0xFF000000);

int width = 200;
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
float spacingMultiplier = 1;
float spacingAddition = 0;
boolean includePadding = false;

StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, width, alignment, spacingMultiplier, spacingAddition, includePadding);

float height = myStaticLayout.getHeight(); 

回答by bramp

What about paint.getTextBounds()(object method)

怎么样paint.getTextBounds()(对象方法)

回答by Nar Gar

@bramp's answer is correct - partially, in that it does not mention that the calculated boundaries will be the minimum rectangle that contains the text fully with implicit start coordinates of 0, 0.

@bramp 的答案是正确的 - 部分是因为它没有提到计算出的边界将是包含完全包含隐式起始坐标 0, 0 的文本的最小矩形。

This means, that the height of, for example "Py" will be different from the height of "py" or "hi" or "oi" or "aw" because pixel-wise they require different heights.

这意味着,例如“Py”的高度将与“py”或“hi”或“oi”或“aw”的高度不同,因为它们需要不同的高度。

This by no means is an equivalent to FontMetrics in classic java.

这绝不等同于经典 java 中的 FontMetrics。

While width of a text is not much of a pain, height is.

虽然文本的宽度不是很痛苦,但高度是。

In particular, if you need to vertically center-align the drawn text, try getting the boundaries of the text "a" (without quotes), instead of using the text you intend to draw. Works for me...

特别是,如果您需要垂直居中对齐绘制的文本,请尝试获取文本“a”的边界(不带引号),而不是使用您打算绘制的文本。对我有用...

Here's what I mean:

这就是我的意思:

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);

paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);

Rect bounds = new Rect();
paint.getTextBounds("a", 0, 1, bounds);

buffer.drawText(this.myText, canvasWidth >> 1, (canvasHeight + bounds.height()) >> 1, paint);
// remember x >> 1 is equivalent to x / 2, but works much much faster

Vertically center aligning the text means vertically center align the bounding rectangle - which is different for different texts (caps, long letters etc). But what we actually want to do is to also align the baselines of rendered texts, such that they did not appear elevated or grooved. So, as long as we know the center of the smallest letter ("a" for example) we then can reuse its alignment for the rest of the texts. This will center align all the texts as well as baseline-align them.

垂直居中对齐文本意味着垂直居中对齐边界矩形 - 这对于不同的文本(大写、长字母等)是不同的。但我们真正想要做的是对齐渲染文本的基线,这样它们就不会出现凸起或凹槽。因此,只要我们知道最小字母(例如“a”)的中心,我们就可以对其余文本重复使用它的对齐方式。这将居中对齐所有文本以及基线对齐它们。

回答by kimnod

The height is the text size you have set on the Paint variable.

高度是您在 Paint 变量上设置的文本大小。

Another way to find out the height is

找出高度的另一种方法是

mPaint.getTextSize();

回答by intrepidis

You could use the android.text.StaticLayoutclass to specify the bounds required and then call getHeight(). You can draw the text (contained in the layout) by calling its draw(Canvas)method.

您可以使用android.text.StaticLayout该类来指定所需的边界,然后调用getHeight(). 您可以通过调用其draw(Canvas)方法来绘制文本(包含在布局中)。

回答by moondroid

You can simply get the text size for a Paint object using getTextSize() method. For example:

您可以使用 getTextSize() 方法简单地获取 Paint 对象的文本大小。例如:

Paint mTextPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
//use densityMultiplier to take into account different pixel densities
final float densityMultiplier = getContext().getResources()
            .getDisplayMetrics().density;  
mTextPaint.setTextSize(24.0f*densityMultiplier);

//...

float size = mTextPaint.getTextSize();

回答by Putti

You must use Rect.width()and Rect.Height()which returned from getTextBounds()instead. That works for me.

您必须使用Rect.width()and Rect.Height()which 返回getTextBounds()。这对我行得通。

回答by Hesam

If anyone still has problem, this is my code.

如果有人仍然有问题,这是我的代码。

I have a custom view which is square (width = height) and I want to assign a character to it. onDraw()shows how to get height of character, although I'm not using it. Character will be displayed in the middle of view.

我有一个正方形的自定义视图(宽度 = 高度),我想为其分配一个字符。onDraw()展示了如何获得角色的高度,虽然我没有使用它。字符将显示在视图中间。

public class SideBarPointer extends View {

    private static final String TAG = "SideBarPointer";

    private Context context;
    private String label = "";
    private int width;
    private int height;

    public SideBarPointer(Context context) {
        super(context);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public SideBarPointer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        init();
    }

    private void init() {
//        setBackgroundColor(0x64FF0000);
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        height = this.getMeasuredHeight();
        width = this.getMeasuredWidth();

        setMeasuredDimension(width, width);
    }

    protected void onDraw(Canvas canvas) {
        float mDensity = context.getResources().getDisplayMetrics().density;
        float mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;

        Paint previewPaint = new Paint();
        previewPaint.setColor(0x0C2727);
        previewPaint.setAlpha(200);
        previewPaint.setAntiAlias(true);

        Paint previewTextPaint = new Paint();
        previewTextPaint.setColor(Color.WHITE);
        previewTextPaint.setAntiAlias(true);
        previewTextPaint.setTextSize(90 * mScaledDensity);
        previewTextPaint.setShadowLayer(5, 1, 2, Color.argb(255, 87, 87, 87));

        float previewTextWidth = previewTextPaint.measureText(label);
//        float previewTextHeight = previewTextPaint.descent() - previewTextPaint.ascent();
        RectF previewRect = new RectF(0, 0, width, width);

        canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
        canvas.drawText(label, (width - previewTextWidth)/2, previewRect.top - previewTextPaint.ascent(), previewTextPaint);

        super.onDraw(canvas);
    }

    public void setLabel(String label) {
        this.label = label;
        Log.e(TAG, "Label: " + label);

        this.invalidate();
    }
}