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,用它来获取这些文字的布局信息,比如需要多少行,入股发现有空行的话,就会从mIntsmObjects中删除对应位置的数据。

拿到具体需要几行显示后,就会遍历每一行,获取每一行的信息,存储在mIntsmObjects中。

之后会通过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;
    

当这些数据组织完成后,最终还会通过LayoutonDraw方法,绘制到界面上。onDraw方法中会有各种获取行数据信息的方法,它们就会访问的前面组织好的数据。像下面这些。

@Override
public int getLineTop(int line) 
    return mInts.getValue(line, TOP);


@Override
public int getLineDescent(int line) 
    return mInts.getValue(line, DESCENT);

LayoutonDraw方法会调用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);

最后会通过TextLinedrawTextRun方法绘制,最终的效果。

以上是关于Android:解读TextView的DynamicLayout的主要内容,如果未能解决你的问题,请参考以下文章

Android中用TextView显示大量文字的方法

android textview啥意思

Android从零单排系列六《Android视图控件——TextView》

Android从零单排系列六《Android视图控件——TextView》

Android从零单排系列六《Android视图控件——TextView》

Android—— TextView文字链接4中方法