I need to build my own custom TextView
so I have been learning about StaticLayout
to draw text on a canvas. This is preferable to using Canvas.drawText()
directly, or so the documentation says. However, the documentation doesn't give any examples for how do it. There is only a vague reference to StaticLayout.Builder
being the newer way to do it.
I found an example here but it seems a little dated.
I finally worked though how to do it so I am adding my explanation below.
StaticLayout is a Layout for text that will not be edited after it is laid out. Use DynamicLayout for text that may change. This is used by widgets to control text layout.
TextView is the user interface which displays the text message on the screen to the user. It is based on the layout size, style, and color, etc. TextView is used to set and display the text according to our specifications.
StaticLayout
(similar to DynamicLayout
and BoringLayout
) is used to layout and draw text on a canvas. It is commonly used for the following tasks:
TextView
). TextView
itself uses a StaticLayout
internally. Single line
If you only have a single line of text, you can measure it with Paint
or TextPaint
.
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();
Multiline
However, if there is line wrapping and you need the height, then it is better to use a StaticLayout
. You provide the width and then you can get the height from the 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();
New API
If you want to use the newer StaticLayout.Builder
(available from API 23), you can get your layout like this:
StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(), myTextPaint, width); StaticLayout myStaticLayout = builder.build();
You can tack on addition settings using dot notation:
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();
I may expand this more in the future, but for now see this post for an example of a method that uses StaticLayout
and returns a bitmap.
Here is an example of a custom view using a StaticLayout
. It behaves like a simple TextView
. When the text is too long to fit on the screen, it automatically line wraps and increases its height.
Code
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>
Notes
This, this, and this were useful in learning how to make a custom text handling view.
See Creating a View Class if you would like to add custom attributes that can be set from code or xml.
Here is my explanation for drawing multiline text on canvas.
Declare Paint object. Use TextPaint which is an extension of Paint.
TextPaint textPaint;
Initialize Paint object. Set your own color, size etc.
textPaint = new TextPaint(); textPaint.setAntiAlias(true); textPaint.setTextSize(16 * getResources().getDisplayMetrics().density); textPaint.setColor(Color.YELLOW);
Add getTextHeight function
private float getTextHeight(String text, Paint paint) { Rect rect = new Rect(); paint.getTextBounds(text, 0, text.length(), rect); return rect.height(); }
in your onDraw function put following lines like this
@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(); }
Courtesy goes to KOC's post
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With