Android:解读TextView的DynamicLayout
Posted bdmh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:解读TextView的DynamicLayout相关的知识,希望对你有一定的参考价值。
在《Android:TextView的Layout创建过程》中我们提到过,TextView中有三种Layout,我们已经讲了两种,今天我们就来说说第三种,动态布局DynamicLayout。我们平时使用的带链接的、ClickSpan等带样式的TextView,都会默认由这种布局进行处理。下面我们定义一个带链接的TextView。
<TextView
android:layout_width="300dp"
android:layout_height="100dp"
android:autoLink="all"
android:text="http://www.baidu.com"/>
当我们设置了autoLink属性时,在TextView初始化时,会赋值mAutoLinkMask变量。
case com.android.internal.R.styleable.TextView_autoLink:
mAutoLinkMask = a.getInt(attr, 0);
break;
然后在setText方法的时候,就会把mText转换为SpannableString类型。
setText方法中,会通过Linkify中的静态方法addLinks来扫描文字中的URL类型文字。再通过applyLink方法,执行SpannableStringInternal中的setSpan,这里会组织Span对象的各种数据。
if (mAutoLinkMask != 0)
......
s2 = mSpannableFactory.newSpannable(text);
然后我们再次回到创建布局的makeSingleLayout方法。
if (mText instanceof Spannable)
result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
mBreakStrategy, mHyphenationFrequency, mJustificationMode,
getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
第二个参数mTransformed,是转换后的文字,用来显示的,mText是基础文字。比如,你设置了密码规则,那么mTransformed就会把文字替换成圆点,而不是明文。其它的参数在前面的文章中,基本都提到过,也基本一样。
随后进入DynamicLayout的初始化过程。
super((ellipsize == null)
? display
: (display instanceof Spanned)
? new SpannedEllipsizer(display)
: new Ellipsizer(display),
paint, width, align, textDir, spacingmult, spacingadd);
先判断是不是设置了省略,如果没有,那么就通过Layout内部的SpannedEllipsizer初始化文字。
接着初始化两个Vector对象,它们的作用和StaticLayout中的mLines类似,内部会用数组保存一些Span 的信息。
//没有设置ellipsize,COLUMNS_NORMAL=4
//行号作为一行,每行有4个数据
mInts = new PackedIntVector(COLUMNS_NORMAL);
......
mInts.insertAt(0, start);
......
mInts.insertAt(1, start);
//默认初始化一个
mObjects = new PackedObjectVector<Directions>(1);
然后通过内部调用reflow方法,从文本开始,找到真实有效文字的信息,保存在上面两个对象中。主要是从后往前及从前往后根据换行符'\\n'来确定位置。
TextUtils.lastIndexOf(text, '\\n', where - 1);
......
TextUtils.indexOf(text, '\\n', where + after);
reflow方法中,还会用到StaticLayout,用它来获取这些文字的布局信息,比如需要多少行,入股发现有空行的话,就会从mInts和mObjects中删除对应位置的数据。
拿到具体需要几行显示后,就会遍历每一行,获取每一行的信息,存储在mInts和mObjects中。
之后会通过updateBlocks,将文本分块,每个Block的长度是400字符,多余的会被放到新的Block中。
updateBlocks判断数组是否已经创建,如果没有,就创建新的Block,否则就对之前的进行调整。
if (mBlockEndLines == null)
createBlocks();
return;
private void createBlocks()
int offset = BLOCK_MINIMUM_CHARACTER_LENGTH;
mNumberOfBlocks = 0;
final CharSequence text = mDisplay;
//如果没有找到换行符,就整个添加
//如果找到换行符,就添加换行符之前的
while (true)
offset = TextUtils.indexOf(text, '\\n', offset);
if (offset < 0)
addBlockAtOffset(text.length());
break;
else
addBlockAtOffset(offset);
offset += BLOCK_MINIMUM_CHARACTER_LENGTH;
//再创建一个索引数组
// mBlockIndices and mBlockEndLines should have the same length
mBlockIndices = new int[mBlockEndLines.length];
for (int i = 0; i < mBlockEndLines.length; i++)
mBlockIndices[i] = INVALID_BLOCK_INDEX;
当这些数据组织完成后,最终还会通过Layout的onDraw方法,绘制到界面上。onDraw方法中会有各种获取行数据信息的方法,它们就会访问的前面组织好的数据。像下面这些。
@Override
public int getLineTop(int line)
return mInts.getValue(line, TOP);
@Override
public int getLineDescent(int line)
return mInts.getValue(line, DESCENT);
Layout的onDraw方法会调用TextLine对象,进行行的绘制。
TextLine tl = TextLine.obtain();
......
if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTab && !justify)
// 不符合条件的,直接就在canvas上绘制
canvas.drawText(buf, start, end, x, lbaseline, paint);
else
tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops);
if (justify)
tl.justify(right - left - indentWidth);
tl.draw(canvas, x, ltop, lbaseline, lbottom);
TextLine中继续调用内部的drawRun方法 。
void draw(Canvas c, float x, int top, int y, int bottom)
if (!mHasTabs)
if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT)
drawRun(c, 0, mLen, false, x, top, y, bottom, false);
return;
if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT)
drawRun(c, 0, mLen, true, x, top, y, bottom, false);
return;
drawRun方法会调用handleRun,这里面会获取你设置的Span的样式,比如是否显示下划线、链接文字颜色等。
CharacterStyle span = mCharacterStyleSpanSet.spans[k];
span.updateDrawState(wp);
/*
*URLSpan 继承自 ClickSpan
*所以updateDrawState默认是ClickSpan中的
*/
public void updateDrawState(TextPaint ds)
ds.setColor(ds.linkColor);
//默认使用下划线
ds.setUnderlineText(true);
最后会通过TextLine的drawTextRun方法绘制,最终的效果。
以上是关于Android:解读TextView的DynamicLayout的主要内容,如果未能解决你的问题,请参考以下文章
Android从零单排系列六《Android视图控件——TextView》
Android从零单排系列六《Android视图控件——TextView》