如图所示,textview限制行数之后会出现上面这个问题,看了网上很多办法,有说先计算宽度然后手动截取字符串的,有自定义textview的。。但没人觉得这些方法很难用,或者根本用不了吗?
1.首先手动截取字符串,增加了代码量,而且不好维护。
2.自定义控件就更不用说了,效率低不说,bug还一堆。
我不相信google没有考虑到上面的情况,毕竟本身就有emoji表情,不可能没碰到上面的情况!于是只能去看textview源码,于是找了半天,发现textview里面又把计算的逻辑分到其他类了,主要代码是下面这个方法:
protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
boolean useSaved) {
Layout result = null;
if (mText instanceof Spannable) {
result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
mBreakStrategy, mHyphenationFrequency,
getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
if (boring != null) {
mBoring = boring;
}
}
if (boring != null) {
if (boring.width <= wantWidth &&
(effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
if (useSaved && mSavedLayout != null) {
result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad);
} else {
result = BoringLayout.make(mTransformed, mTextPaint,
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad);
}
if (useSaved) {
mSavedLayout = (BoringLayout) result;
}
} else if (shouldEllipsize && boring.width <= wantWidth) {
if (useSaved && mSavedLayout != null) {
result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth);
} else {
result = BoringLayout.make(mTransformed, mTextPaint,
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth);
}
}
}
}
if (result == null) {
StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
0, mTransformed.length(), mTextPaint, wantWidth)
.setAlignment(alignment)
.setTextDirection(mTextDir)
.setLineSpacing(mSpacingAdd, mSpacingMult)
.setIncludePad(mIncludePad)
.setBreakStrategy(mBreakStrategy)
.setHyphenationFrequency(mHyphenationFrequency);
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth)
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
}
// TODO: explore always setting maxLines
result = builder.build();
}
return result;
}
其实这个方法就是区分什么情况下使用哪个类来进行计算,总共就三个类StaticLayout , DynamicLayout , BoringLayout
简单说下这三个类的意思.
1.Boringlayout 主要负责显示单行文本,并提供了isBoring方法来判断是否满足单行文本的条件。
2.DynamicLayout 当文本为Spannable的时候,TextView就会使用它来负责文本的显示,在内部设置了SpanWatcher,当检测到span改变的时候,会进行reflow,重新计算布局。
3.StaticLayout 当文本为非单行文本,且非Spannable的时候,就会使用StaticLayout,内部并不会监听span的变化,因此效率上会比DynamicLayout高,只需一次布局的创 建即可,但其实内部也能显示SpannableString,只是不能在span变化之后重新进行布局而已。
综上所述,使用Dynamiclayout来进行换行计算即可解决问题,再看下源码什么情况下会使用Dynamiclayout呢?没错就是当settext设置的文字是Spannable类型就会使用DynamicLayout,但很奇怪,我设置的明明就是spannable类型的文字了,为什么还是不行,是不是这个方法不行?再翻翻setText方法的源码,我们找到了原因:
@android.view.RemotableViewMethod
public final void setText(CharSequence text) {
setText(text, mBufferType);
}
一般来说我们都只会用上面的这个方法来设置内容,mBufferType默认是BufferType.NORMAL,这样设置的内容都会被包装成spanned类型,这样就会使用staticlayout来计算,所以无效。最后我们只要改用另一个settext方法即可
即textview.setText("content",BufferType.SPANNABLE) , 问题解决!