Android中如何使用StaticLayout?
How is StaticLayout used in Android?
我需要构建自己的自定义 TextView
,所以我一直在学习如何 StaticLayout
在 canvas 上绘制文本。这比直接使用 Canvas.drawText()
更可取,或者说 documentation says. However, the documentation doesn't give any examples for how do it. There is only a vague reference to StaticLayout.Builder
是更新的方法。
我找到了一个示例 here 但它似乎有点过时了。
我终于弄明白了怎么做,所以我在下面添加我的解释。
StaticLayout
(similar to DynamicLayout
and BoringLayout
) 用于在 canvas 上布局和绘制文本。它通常用于以下任务:
- 测量多行文本在布局后的大小。
- 在位图图像上绘制文本。
- 创建一个自定义视图来处理自己的文本布局(与创建具有嵌入式
TextView
的复合视图相反)。 TextView
本身使用了一个StaticLayout
internally。
正在测量文字大小
单行
如果只有一行文字,可以用Paint
或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();
多行
但是,如果有换行并且需要高度,那么最好使用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
和 returns 位图的方法示例。
制作自定义文本处理视图
这是使用 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>
备注
如果您想添加可以从代码或 xml.
设置的自定义属性,请参阅 Creating a View Class
这是我在 canvas 上绘制多行文本的解释。
声明画图对象。使用作为 Paint 扩展的 TextPaint。
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's post
我需要构建自己的自定义 TextView
,所以我一直在学习如何 StaticLayout
在 canvas 上绘制文本。这比直接使用 Canvas.drawText()
更可取,或者说 documentation says. However, the documentation doesn't give any examples for how do it. There is only a vague reference to StaticLayout.Builder
是更新的方法。
我找到了一个示例 here 但它似乎有点过时了。
我终于弄明白了怎么做,所以我在下面添加我的解释。
StaticLayout
(similar to DynamicLayout
and BoringLayout
) 用于在 canvas 上布局和绘制文本。它通常用于以下任务:
- 测量多行文本在布局后的大小。
- 在位图图像上绘制文本。
- 创建一个自定义视图来处理自己的文本布局(与创建具有嵌入式
TextView
的复合视图相反)。TextView
本身使用了一个StaticLayout
internally。
正在测量文字大小
单行
如果只有一行文字,可以用Paint
或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();
多行
但是,如果有换行并且需要高度,那么最好使用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
和 returns 位图的方法示例。
制作自定义文本处理视图
这是使用 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>
备注
如果您想添加可以从代码或 xml.
设置的自定义属性,请参阅 Creating a View Class
这是我在 canvas 上绘制多行文本的解释。
声明画图对象。使用作为 Paint 扩展的 TextPaint。
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's post