【问题标题】:How is StaticLayout used in Android?Android中如何使用StaticLayout?
【发布时间】:2017-06-06 09:41:11
【问题描述】:

我需要构建自己的自定义 TextView,所以我一直在学习 StaticLayout 在画布上绘制文本。这比直接使用Canvas.drawText() 更好,或者documentation 这么说。但是,该文档没有给出如何做到这一点的任何示例。只是模糊地提到 StaticLayout.Builder 是更新的方法。

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

我终于知道怎么做,所以我在下面添加我的解释。

【问题讨论】:

    标签: android canvas text android-custom-view staticlayout


    【解决方案1】:

    StaticLayout (similar to DynamicLayout and BoringLayout) 用于在画布上布局和绘制文本。它通常用于以下任务:

    • 测量多行文本布局后的大小。
    • 在位图图像上绘制文本。
    • 制作处理其自己的文本布局的自定义视图(与使用嵌入的TextView 制作复合视图相反)。 TextView 本身使用 StaticLayout internally

    测量文字大小

    单行

    如果你只有一行文字,你可以用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。当文本太长而无法在屏幕上显示时,它会自动换行并增加其高度。

    代码

    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>
    

    备注

    • Thisthisthis 在学习如何制作自定义文本处理视图时非常有用。

    • 如果您想添加可以从代码或 xml 设置的自定义属性,请参阅 Creating a View Class

    【讨论】:

    • 你必须在 StaticLayout 的构造函数/获取方法中传递 text.length() - 1 否则你会得到 IndexOutOfBoundException
    • @Suragch 在位图上写入文本比预期的要复杂。当位图尺寸较小时,文本尺寸会放大,而当位图尺寸较大时,文本尺寸会缩小。有关详细信息,请查看stackoverflow.com/questions/69578026/…
    【解决方案2】:

    这是我在画布上绘制多行文本的解释。

    声明 Paint 对象。使用 TextPaint,它是 Paint 的扩展。

    TextPaint textPaint;
    

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

    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's post

    【讨论】:

      猜你喜欢
      • 2011-02-23
      • 2011-08-28
      • 1970-01-01
      • 2020-08-14
      • 2022-11-02
      • 2018-07-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多