测量多行文本的紧边界框

Measuring tight bounds box of multiline text

我正在使用 StaticLayout 在 canvas 上绘制多行文本,并且我想在绘制之前测量文本周围最紧密的边界框,(文本可能有不同的大小、字体、样式等...),我想要这样的东西:

Size measureText(String text, float size, Font font, etc...)

我希望它 return 文本周围最紧密的边界框,即(如果我们谈论的是文本的像素):

(leftest_pixel - rightest_pixel, highest_pixel - lowest_pixels)

如果文本是单行的,我可以这样做:

Paint paint = new Paint();
...
paint.getTextBounds(text, 0, size, rect);

但是由于文本可能有多行,我必须考虑行距和字形下降以及所有其他字体参数...因此下一个选项是使用 StaticLayoutmaximalLineWidth (为了打破行),但 StaticLayout 不计算最紧密的框,它会在顶部和底部添加一些填充(因为它基本上是行数乘以最大行高):

比如绿框是用StaticLayout测量的结果,红框是我要收的框:

我该怎么做? 谢谢。

构建 StaticLayout,然后使用 TextPaint 中的方法确定每一行的边界。整个多行文本的边界是第一行的上边界、最后一行的下边界和所有行的最左边界和最右边界。

这是一个演示此概念的示例自定义视图。布局只是自定义视图,宽度为 200dp,高度为 wrap_content

MyStaticText

public class MyStaticText extends View {
    private final String mText = "This is some longer text to test multiline layout.";
    private TextPaint mTextPaint;
    private StaticLayout mStaticLayout;
    private final Paint mRectPaint = new Paint();

    public MyStaticText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(FONT_SIZE * getResources().getDisplayMetrics().density);
        mTextPaint.setColor(Color.BLACK);

        mRectPaint.setStyle(Paint.Style.STROKE);
        mRectPaint.setStrokeWidth(2f);
        mRectPaint.setColor(Color.RED);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthAskedFor = MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            mStaticLayout = new StaticLayout(mText, mTextPaint, widthAskedFor, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0, false);
        } else {
            throw new RuntimeException("View width must be exactly specified.");
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height;
        if (heightMode == MeasureSpec.AT_MOST) {
            height = mStaticLayout.getHeight() + getPaddingTop() + getPaddingBottom();
        } else {
            throw new RuntimeException("View height must be 'wrap_content'.");
        }
        setMeasuredDimension(widthAskedFor, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mStaticLayout.draw(canvas);

        // Start with bounds of first line.
        Rect textBounds = getLineBounds(0);

        // Check bounds of last line since it will be the bottom of the bounding rectangle.
        Rect lineBounds = getLineBounds(mStaticLayout.getLineCount() - 1);
        if (lineBounds.bottom > textBounds.bottom) {
            textBounds.bottom = lineBounds.bottom;
        }

        // Now check all lines for min left bound and max right bound.
        for (int line = 0; line < mStaticLayout.getLineCount(); line++) {
            lineBounds = getLineBounds(line);
            if (lineBounds.left < textBounds.left) {
                textBounds.left = lineBounds.left;
            }
            if (lineBounds.right > textBounds.right) {
                textBounds.right = lineBounds.right;
            }
        }
        canvas.drawRect(textBounds, mRectPaint);
    }

    private Rect getLineBounds(int line) {
        int firstCharOnLine = mStaticLayout.getLineStart(line);
        int lastCharOnLine = mStaticLayout.getLineVisibleEnd(line);
        String s = mText.substring(firstCharOnLine, lastCharOnLine);

        // bounds will store the rectangle that will circumscribe the text.
        Rect bounds = new Rect();

        // Get the bounds for the text. Top and bottom are measured from the baseline. Left
        // and right are measured from 0.
        mTextPaint.getTextBounds(s, 0, s.length(), bounds);
        int baseline = mStaticLayout.getLineBaseline(line);
        bounds.top = baseline + bounds.top;
        bounds.bottom = baseline + bounds.bottom;

        return bounds;
    }

    private static final int FONT_SIZE = 48;
}

Here 是一个具有更通用解决方案的演示应用程序。