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.

This class is used by widgets to control text layout. You should not need to use this class directly unless you are implementing your own widget or custom display object, in which case you are encouraged to use a Layout instead of calling Canvas.drawText() directly.

上面是 BoringLayout 的官方解释。意思是说BoringLayout是一个非常简单的布局,是对单行且是LTR方向的文本进行适配的。不建议开发者自己使用,如果用的话,要确保通过isBoring判断文本是否符合要求。除非你自己开发自定义的widget或者自定义的显示对象,否则不建议直接使用。

我们在Android:TextView的Layout创建过程中提到过,TextView内部Layout创建的过程,也看到了BoringLayout的创建入口(TextViewmakeSingleLayout方法中)。这一篇文章就来具体讲讲BoringLayout的工作过程。

假如有一个TextView(宽度:200dp,高度:100dp),手机屏幕密度是4,所以转化成像素就是(宽度:800px,高度:400px)。

makeSingleLayout方法中,先判断如果不符合DynamicLayout的创建规则,那就会通过BoringLayoutisBoring来检测TextView中的文本是否符合单行显示的要求。isBoring其实返回的是一个度量对象Metrics。那isBoring测量的是什么呢?

isBoring测量的结果表示,如果TextView中所有的文字,都显示在一行中,需要多宽多高的显示区域,然后用这个结果和布局文件中设置的宽度进行比较,看看能否放得下(小于800px就能放得下),如果能,就会创建BoringLayout布局。

那我们就来看看isBoring

public static Metrics isBoring(CharSequence text, TextPaint paint,                               TextDirectionHeuristic textDir, Metrics metrics) {    final int textLength = text.length();//先检测是否存在一些有趣的字符,比如'','啥的    if (hasAnyInterestingChars(text, textLength)) {        return null;  // There are some interesting characters. Not boring.    }//检测文字方向,不允许从右到左,有的国家是这样的    if (textDir != null && textDir.isRtl(text, 0, textLength)) {        return null;  // The heuristic considers the whole text RTL. Not boring.    }//如果是Spanned类型的,判断是否设置了Span样式,有的话,不行    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 PargraphStyle 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);    fm.width = (int) Math.ceil(line.metrics(fm));    TextLine.recycle(line);     return fm;}

结果返回后,进行对比,满足下面的条件后,就会创建BoringLayout

if (boring == UNKNOWN_BORING) {    //检查是否可以用BoringLayout    boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);        ......}//如果boring不为空,那么就要把测量结果和实际的显示区域进行比较,来确定是否可以单行显示if (boring != null) {//如果测量宽度不大于需要的宽度,//并且设置了省略号位置或者测量宽度不大于带省略号时的宽度if (boring.width <= wantWidth        && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {        ......}//或者需要处理省略号,并且宽度满足//比如在布局中设置了singleLineelse if (shouldEllipsize && boring.width <= wantWidth) {        ......}}

第一个对比条件就是没有设置单行显示,并且所有文本一行就可以放的下。

第二个对比条件就是,比如你设置了ellipsize。

如果满足其中一个条件,表示可以用BoringLayout来处理文本显示,那么就会使用BoringLayoutreplaceOrMakemake来创建布局。makeSingleLayout中useSaved参数如果为true,那么就会把创建好的布局保存,以后如果再次使用时,可以用replaceOrMake来处理。

if (useSaved) {    mSavedLayout = (BoringLayout) result;}

replaceOrMake内部和make一样,都是调用的基类的replaceWith方法,所以我们来具体看看make的过程。

public static BoringLayout make(CharSequence source,                                TextPaint paint, int outerwidth,                                Alignment align,                                float spacingmult, float spacingadd,                                BoringLayout.Metrics metrics, boolean includepad,                                TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {    return new BoringLayout(source, paint, outerwidth, align,            spacingmult, spacingadd, metrics,            includepad, ellipsize, ellipsizedWidth);}

静态方法make中调用了BoringLayout的构造方法,创建实例。

public BoringLayout(CharSequence source,                    TextPaint paint, int outerwidth,                    Alignment align,                    float spacingmult, float spacingadd,                    BoringLayout.Metrics metrics, boolean includepad,                    TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {    /*     * 调用基类的初始化,保存一些变量     */    super(source, paint, outerwidth, align, spacingmult, spacingadd);     boolean trust;//如果没有设置省略号位置,或者不是跑马灯类型    if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {        mEllipsizedWidth = outerwidth;        mEllipsizedStart = 0;        mEllipsizedCount = 0;        trust = true;    } else {/*replace基类方法,替换掉一些属性*TextUtils.ellipsize把文字处理成带省略号的文字*/        replaceWith(TextUtils.ellipsize(source, paint, ellipsizedWidth,                ellipsize, true, this),                paint, outerwidth, align, spacingmult,                spacingadd);          mEllipsizedWidth = ellipsizedWidth;        trust = false;    }//初始化工作    init(getText(), paint, outerwidth, align, spacingmult, spacingadd,            metrics, includepad, trust);}

来看看TextUtilsellipsize方法。

public static CharSequence ellipsize(CharSequence text,                                     TextPaint paint,                                     float avail, TruncateAt where,                                     boolean preserveLength,                                     EllipsizeCallback callback) {    return ellipsize(text, paint, avail, where, preserveLength, callback,            TextDirectionHeuristics.FIRSTSTRONG_LTR,            (where == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS_STRING : ELLIPSIS_STRING);}

如果设定的是END_SMALL类型省略号,就用两个点(..),否则用三个点(...)。

public static CharSequence ellipsize(CharSequence text,                                     TextPaint paint,                                     float avail, TruncateAt where,                                     boolean preserveLength,                                     EllipsizeCallback callback,                                     TextDirectionHeuristic textDir, String ellipsis) {     int len = text.length();//MeasuredText中会保留文字的一些相关信息,比如字数,长度,字符数组等    MeasuredText mt = MeasuredText.obtain();    try {//和前面一样,测量需要多宽能够显示        float width = setPara(mt, paint, text, 0, text.length(), textDir);//和前面一样的比较,如果能放下,触发回调,直接返回        if (width <= avail) {            if (callback != null) {                callback.ellipsized(0, 0);            }             return text;        }//省略号宽度        float ellipsiswid = paint.measureText(ellipsis);//可用宽度减去省略号宽度,是实际用来显示文字的宽度        avail -= ellipsiswid;         int left = 0;        int right = len;        if (avail < 0) {            // it all goes        } else if (where == TruncateAt.START) {            right = len - mt.breakText(len, false, avail);        } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {//计算从第几个字符开始,后面的就被省略了            left = mt.breakText(len, true, avail);        } else {            right = len - mt.breakText(len, false, avail / 2);            avail -= mt.measure(right, len);            left = mt.breakText(right, true, avail);        }         if (callback != null) {            callback.ellipsized(left, right);        }         char[] buf = mt.mChars;        Spanned sp = text instanceof Spanned ? (Spanned) text : null;//计算剩下多少个字符用来显示        int remaining = len - (right - left);        if (preserveLength) {/* *这里填充buf数组,第left+1个填充成省略号, *后面的都填充成指定的相同内容, *表示都被省略了*/            if (remaining > 0) { // else eliminate the ellipsis too                buf[left++] = ellipsis.charAt(0);            }            for (int i = left; i < right; i++) {                buf[i] = ZWNBS_CHAR;            }            //生成带省略号的文字            String s = new String(buf, 0, len);            if (sp == null) {                return s;            }            SpannableString ss = new SpannableString(s);            copySpansFrom(sp, 0, len, Object.class, ss, 0);            return ss;        }         if (remaining == 0) {            return "";        }         ......        return ssb;    } finally {        MeasuredText.recycle(mt);    }}

下图是buf数组的截图。

99090f09729ab29168687c4da5cfb00e.png

这样就得到了带省略号的结果。

然后,通过replaceLayout内部的属性更新,比如mText属性更新为带省略号的。这样整个BoringLayout对象就创建完成了

提示:这时候你通过TextView的getText方法和TextView的getLayout().getText得到的结果就是不一样的了。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐