android - 测量多行文本的紧密边界框
问题描述
我正在使用 在画布上绘制多行文本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);
但由于文本可能有多行,我必须考虑行间距和字形下降以及所有其他字体参数......所以下一个选项将是使用StaticLayout
with maximalLineWidth
(为了换行),但StaticLayout
不计算最紧凑的盒子,它会在顶部和底部添加一些填充(因为它基本上是行数乘以最大行高):
例如,绿色框是测量的结果,StaticLayout
红色框是我想要接收的框:
我该怎么做?谢谢。
解决方案
构建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;
}
这是一个具有更通用解决方案的演示应用程序。
推荐阅读
- soap - 使用失眠症来打肥皂电话
- python - {Python} 如何将字典转换为坐标?
- python - 将绘图树状图应用于文本数据
- r - 确保使用 {mongolite} 读取 MongoDB 数据时数据帧变为 tibbles
- php - 在 Laravel Excel 中插入不存在的数据并拒绝存在的数据
- swift - 状态变量和 ForEach Swift
- javascript - 如何从 asyn/await 函数将值返回给全局变量?
- amazon-web-services - 重复的 SQS 消息可以持续多长时间?
- java - Java,DateTimeFormatter 不考虑 ZoneDateTime 的时区
- tmux - tmux - 为每个命令强制重复前缀键