前言
在Android开发中,TextView是一个很基础的控件。但看过它的源码你就会发现他其实一点都不简单。毕竟他也是不少view的父类。其中包含了很多布局、逻辑和计算。之前一直想有空就研究一下来着的。那么现在就开始吧。
这篇先讲TextView中三大布局(BoringLayout、StaticLayout、DynamicLayout)中最简单的BoringLayout。
BoringLayout
在官网中,BoringLayout的描述如下:
A BoringLayout is a very simple Layout implementation for text that fits on a single line and is all left-to-right characters. You will probably never want to make one of these yourself; if you do, be sure to call
isBoring(CharSequence, TextPaint)
first to make sure the text meets the criteria.
简单点说,BoringLayout是适用单行,文本方向为LTR(从左到右)的简单布局。不建议开发者自己适用,如果要用,需要先isBoring方法判断是否符合要求。
在TextView中,BoringLayout的创建入口是makeSingleLayout
方法。
TextView#makeSingleLayout
protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,boolean useSaved) {
Layout result = null;
if (useDynamicLayout()) {
...
}else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
if (boring != null) {
mBoring = boring;
}
}
if (boring != null) {
//如果测量宽度小于小想要的宽度,并且设置了ellipsize或者测量宽度小于省略后的长度
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) {
//如果需要处理省略号(如设置了singleLine),且测量宽度小于想要的宽度
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);
}
}
}
}
经过isBoring检测后符合要求,且满足宽度和设置省略条件,会创建BoringLayout,若之前创建过,useSaved为true,则通过replaceOrMake
方法直接取用,否则通过make
创建新的。在创建过程中,若设置了ellipsize省略,会调用TextUtils.ellipsize进行文本省略处理。
BoringLayout#isBoring
public static Metrics isBoring(CharSequence text, TextPaint paint,
TextDirectionHeuristic textDir, Metrics metrics) {
final int textLength = text.length();
//检测是否存在某些有趣的字符,如'\n','\t',
//因为这些字符的存在会影响宽度计算和LTR方向
if (hasAnyInterestingChars(text, textLength)) {
return null; // There are some interesting characters. Not boring.
}
//检测文字方向,BoringLayout不支持从右到左。有些国家文字书写是从右到左
//关于字符分类和书写方向,有兴趣可以看https://www.sohu.com/a/348173901_298038
if (textDir != null && textDir.isRtl(text, 0, textLength)) {
return null; // The heuristic considers the whole text RTL. Not boring.
}
//如果是spanned类型,判断是否设置了样式,若设置了返回null
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
if (styles.length > 0) {
return null; // There are some ParagraphStyle spans. Not boring.
}
}
Metrics fm = metrics;
if (fm == null) {
fm = new Metrics();
} else {
fm.reset();
}
//TextLine对文字进行测量
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
0,0 );
fm.width = (int) Math.ceil(line.metrics(fm));
TextLine.recycle(line);
return fm;
}