在安卓中,如何使用StaticLayout?

44

我需要建立自己的自定义TextView,所以一直在学习StaticLayout如何在画布上绘制文本。根据文档所说,这比直接使用Canvas.drawText()更好。然而,文档没有给出任何关于如何实现的示例,只提到使用StaticLayout.Builder是较新的方法。

我在这里找到了一个示例,但它似乎有点过时了。

最后,我成功地弄清了如何实现它,所以我会在下面添加我的说明。

2个回答

133

StaticLayout (类似于DynamicLayoutBoringLayout) 用于在画布上进行文本布局和绘制。它通常用于以下任务:

  • 测量多行文本在布局后的大小。
  • 在位图图像上绘制文本。
  • 创建一个自定义视图,处理自己的文本布局(而不是使用嵌入式TextView创建组合视图)。TextView本身使用StaticLayout内部实现

测量文本大小

单行

如果您只有一行文本,可以使用PaintTextPaint来进行测量。

String text = "This is some text."

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

float width = mTextPaint.measureText(text);
float height = -mTextPaint.ascent() + mTextPaint.descent();

多行文本

然而,如果存在换行并且需要高度,则最好使用StaticLayout。您提供宽度,然后可以从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(); 

新的 API

如果您想使用更新的 StaticLayout.Builder(从API 23可用),您可以按照以下方式获取您的布局:

StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(), myTextPaint, width);
StaticLayout myStaticLayout = builder.build();

您可以使用点符号添加其他设置:

StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(), myTextPaint, width)
        .setAlignment(Layout.Alignment.ALIGN_NORMAL)
        .setLineSpacing(spacingAddition, spacingMultiplier)
        .setIncludePad(includePadding)
        .setMaxLines(5);
StaticLayout myStaticLayout = builder.build();

在图片上书写文本

我将来可能会扩展更多内容,但目前请参见this post的示例,其中使用StaticLayout方法并返回位图。

创建自定义文本处理视图

这是一个使用StaticLayout的自定义视图示例。它的行为类似于简单的TextView。当文本过长无法适应屏幕时,它会自动换行并增加其高度。

enter image description here

代码

MyView.java

public class MyView extends View {

    String mText = "This is some text.";
    TextPaint mTextPaint;
    StaticLayout mStaticLayout;

    // use this constructor if creating MyView programmatically
    public MyView(Context context) {
        super(context);
        initLabelView();
    }

    // this constructor is used when created from xml
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLabelView();
    }

    private void initLabelView() {
        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
        mTextPaint.setColor(0xFF000000);

        // default to a single line of text
        int width = (int) mTextPaint.measureText(mText);
        mStaticLayout = new StaticLayout(mText, mTextPaint, (int) width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0, false);

        // New API alternate
        //
        // StaticLayout.Builder builder = StaticLayout.Builder.obtain(mText, 0, mText.length(), mTextPaint, width)
        //        .setAlignment(Layout.Alignment.ALIGN_NORMAL)
        //        .setLineSpacing(0, 1) // add, multiplier
        //        .setIncludePad(false);
        // mStaticLayout = builder.build();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Tell the parent layout how big this view would like to be
        // but still respect any requirements (measure specs) that are passed down.

        // determine the width
        int width;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthRequirement = MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthRequirement;
        } else {
            width = mStaticLayout.getWidth() + getPaddingLeft() + getPaddingRight();
            if (widthMode == MeasureSpec.AT_MOST) {
                if (width > widthRequirement) {
                    width = widthRequirement;
                    // too long for a single line so relayout as multiline
                    mStaticLayout = new StaticLayout(mText, mTextPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0, false);
                }
            }
        }

        // determine the height
        int height;
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightRequirement = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightRequirement;
        } else {
            height = mStaticLayout.getHeight() + getPaddingTop() + getPaddingBottom();
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(height, heightRequirement);
            }
        }

        // Required call: set width and height
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // do as little as possible inside onDraw to improve performance

        // draw the text on the canvas after adjusting for padding
        canvas.save();
        canvas.translate(getPaddingLeft(), getPaddingTop());
        mStaticLayout.draw(canvas);
        canvas.restore();
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/activity_vertical_margin"
    tools:context="com.example.layoutpractice.MainActivity">

    <com.example.layoutpractice.MyView
        android:layout_centerHorizontal="true"
        android:background="@color/colorAccent"
        android:padding="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>

注释

  • 这个, 这个这个 对学习如何制作自定义文本处理视图非常有用。

  • 如果您想添加可以从代码或xml中设置的自定义属性,请参见创建视图类


1
你必须在 StaticLayout 的构造函数/获取方法中传递 text.length() - 1,否则你将会得到 IndexOutOfBoundException。 - Rami Jemli
@Suragch 在位图上编写文本比预期更棘手。当位图尺寸较小时,文本大小会变大,而当位图尺寸较大时,文本大小会缩小。欲了解更多详细信息,请查看此链接:https://stackoverflow.com/questions/69578026/canvas-drawtext-changes-text-size-for-different-resolution-images - iCantC

4
这里是关于在画布上绘制多行文本的说明。
声明 Paint 对象。使用 TextPaint,它是 Paint 的扩展。
TextPaint textPaint;

初始化画笔对象。设置自己的颜色、大小等。

textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
textPaint.setColor(Color.YELLOW);

添加getTextHeight函数
private float getTextHeight(String text, Paint paint) {
    Rect rect = new Rect();
    paint.getTextBounds(text, 0, text.length(), rect);
    return rect.height();
}

在您的onDraw函数中,请按以下方式添加以下行:
@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    String text = "This is a lengthy text. We have to render this properly. If layout mess users review will mess. Is that so ? ";

    Rect bounds = canvas.getClipBounds();

    StaticLayout sl = new StaticLayout(text, textPaint, bounds.width(),
            Layout.Alignment.ALIGN_CENTER, 1, 1, true);

    canvas.save();

    //calculate X and Y coordinates - In this case we want to draw the text in the
    //center of canvas so we calculate
    //text height and number of lines to move Y coordinate to center.
    float textHeight = getTextHeight(text, textPaint);
    int numberOfTextLines = sl.getLineCount();
    float textYCoordinate = bounds.exactCenterY() -
            ((numberOfTextLines * textHeight) / 2);

    //text will be drawn from left
    float textXCoordinate = bounds.left;

    canvas.translate(textXCoordinate, textYCoordinate);

    //draws static layout on canvas
    sl.draw(canvas);
    canvas.restore();
}

感谢KOC的文章


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接