初识StaticLayout
是在一个需要计算TextView
高度的时候,计算完高度后对TextView
进行分页显示。为此我仔细观看了TextView
中计算高度的部分,并从中找到了答案
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
// 篇幅太长,我们略过宽度部分,高度是由getDesiredHeight()来计算的
... ...
if (heightMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
height = heightSize;
mDesiredHeightAtMeasure = -1;
} else {
int desired = getDesiredHeight();
height = desired;
mDesiredHeightAtMeasure = desired;
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desired, heightSize);
}
}
... ...
setMeasuredDimension(width, height);
}
private int getDesiredHeight() {
return Math.max(
getDesiredHeight(mLayout, true),
getDesiredHeight(mHintLayout, mEllipsize != null));
}
private int getDesiredHeight(Layout layout, boolean cap) {
if (layout == null) {
return 0;
}
/*
* Don't cap the hint to a certain number of lines.
* (Do cap it, though, if we have a maximum pixel height.)
*/
int desired = layout.getHeight(cap);
final Drawables dr = mDrawables;
if (dr != null) {
desired = Math.max(desired, dr.mDrawableHeightLeft);
desired = Math.max(desired, dr.mDrawableHeightRight);
}
int linecount = layout.getLineCount();
final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom();
desired += padding;
if (mMaxMode != LINES) {
desired = Math.min(desired, mMaximum);
} else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout
|| layout instanceof BoringLayout)) {
desired = layout.getLineTop(mMaximum);
if (dr != null) {
desired = Math.max(desired, dr.mDrawableHeightLeft);
desired = Math.max(desired, dr.mDrawableHeightRight);
}
desired += padding;
linecount = mMaximum;
}
if (mMinMode == LINES) {
if (linecount < mMinimum) {
desired += getLineHeight() * (mMinimum - linecount);
}
} else {
desired = Math.max(desired, mMinimum);
}
// Check against our minimum height
desired = Math.max(desired, getSuggestedMinimumHeight());
return desired;
}
从上面的代码中我们可以看到TextView
中文本的高度是由Lyout.getHeight(boolean)
得到的,由此可见,文字的管理是通过Layout
实现的。TextView
内部会根据不同的设置,创建不同的Layout
,总共有三种。
DynamicLayout
:用在EditText
或者TextView
中设置的是Spannable
类型的文字。BoringLayout
:常用在处理单行文本。StaticLayout
:这个是默认的TextView
的Layout
,用在文字不会被改变的状态下。。
在这里我们主要研究了StaticLayout
,用它来计算文本高度。StaticLayout
是基于Builder
模式创建的
var layout = StaticLayout.Builder.obtain(source, start, end, paint, width)
.setAlignment(alignment)
.setTextDirection(textDir)
.setLineSpacing(spacingAdd, spacingMult)
.setIncludePad(includePad)
.setBreakStrategy(breakStrategy)
.setHyphenationFrequency(hyphenationFrequency)
.setMaxLines(maxLine)
.build()
主要参数
source
是文本,start
和end
分别是开始和结束位置。paint
是TextPaint
,用来绘制文本。width
是文本宽度,文字到达这个宽度后就会自动换行。alignment
是文本对齐方向,主要有ALIGN_NORMAL
/ALIGN_CENTER
/ALIGN_OPPOSITE
。spacingAdd
是行间距的额外增加值,默认为0。spacingmult
是行间距的倍数,默认是1。includePad
是指是否在文字上下添加额外的空间,超出ascent
和descent
部分。breakStrategy
是换行策略,主要有BREAK_STRATEGY_SIMPLE
/BREAK_STRATEGY_HIGH_QUALITY
/BREAK_STRATEGY_BALANCED
。maxLine
是最大行数。
找到了TextView
计算高度的方法后,我们自定义了一个文本显示控件,
class StaticLayoutView (context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
View(context, attrs, defStyleAttr) {
private var mStaticLayout: StaticLayout? = null
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
fun setLayout(layout: StaticLayout) {
mStaticLayout = layout
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var height = mStaticLayout?.height ?: 0
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
mStaticLayout?.draw(canvas)
}
}