首页 > 解决方案 > 测量多行文本的紧密边界框

问题描述

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

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

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

(leftest_pixel - rightest_pixel, highest_pixel - lowest_pixels)

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

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

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

例如,绿色框是测量的结果,StaticLayout红色框是我想要接收的框:

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

我该怎么做?谢谢。

标签: androidandroid-canvasandroid-graphicsstaticlayout

解决方案


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

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

在此处输入图像描述

在此处输入图像描述

我的静态文本

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;
}

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


推荐阅读