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


132

有没有直接测量文字高度的方法?我现在的操作方式是使用Paint measureText()来获取宽度,然后通过反复试验找到一个值来获取近似高度。我也一直在摸索FontMetrics,但是所有这些似乎都是很糟糕的近似方法。

我正在尝试按不同的分辨率缩放内容。我可以做到,但是最终我得到了冗长的冗长代码,需要大量计算才能确定相对大小。我讨厌它!一定有更好的方法。

Answers:


136

关于paint.getTextBounds()(对象方法)


1
当我评估文本的高度时,这会产生非常奇怪的结果。短文本的高度为12,而真正长的文本的高度为16(字体大小为16)。对我毫无意义(android 2.3.3)
AgentKnopf 2012年

35
高度的变化是您在文本中出现
下移

208

根据您的需要,可以使用不同的方法来测量高度。

getTextBounds

如果您要进行诸如将少量固定文本精确居中的操作,则可能需要getTextBounds。您可以这样获得边界矩形

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

如下面的图像所示,不同的字符串将给出不同的高度(以红色显示)。

在此处输入图片说明

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

Paint.FontMetrics

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

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

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

看下图。两根琴弦的高度均为234.375

在此处输入图片说明

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

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

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

上面的图片来自这个项目。您可以使用它来查看字体指标的工作方式。

静态布局

要测量多行文字的高度,应使用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(); 

很好的解释。截图来自哪个应用程序?
米歇尔·约翰逊

1
@MichealJohnson,我在此处将应用程序添加为GitHub项目
Suragch'3

1
那么,“ getTextSize”会给您什么?
Android开发人员

1
Paint的getTextSize()字体大小以像素为单位(相对于sp单位)。@androiddeveloper
Suragch

2
以像素为单位的字体大小与所测量的高度以及FontMetrics尺寸之间是什么关系?我想进一步探讨这个问题。
苏拉奇

85

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

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

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

文字的宽度虽然不怎么麻烦,但高度却很。

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

这就是我的意思:

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

垂直居中对齐文本表示垂直居中对齐边界矩形-不同的文本(大写字母,长字母等)不同。但是,我们实际上要做的是还要对齐渲染文本的基线,以使它们不会显得过高或粗糙。因此,只要我们知道最小字母的中心(例如“ a”),我们就可以将其对齐方式重新用于其余文本。这将使所有文本居中对齐并使其基线对齐。


25
x >> 1好久没见了。仅为此
投票

61
一个好的现代编译器将看到x / 2并对其进行优化,以x >> 1
intrepidis 2013年

37
x / 2考虑到克里斯的评论,@ keaukraine 在阅读代码时更加友好。
d3dave

是什么buffer在这个例子吗?它canvas传递给draw(Canvas)方法了吗?
AutonomousApps

@AutonomousApps是的,这是画布
Chisko

16

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

找出高度的另一种方法是

mPaint.getTextSize();

3

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


2

您可以使用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();

哪里24.0f来的?
折纸2015年

24.0f仅仅是文本大小的一个示例
moondroid

1

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


2
如果要处理多个文本段,则不会。原因是我上面的答案。
Nar Gar 2012年

0

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

我有一个自定义视图,该视图为正方形(宽度=高度),我想为其分配一个字符。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();
    }
}

3
-1用于onDraw()中的分配:如果要在类中声明字段(在构造函数中初始化)并在onDraw()中重用它们,将产生大量的性能优势。
zoltish
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.