SpannableString与SpannableStringBuilder

场景

在实际开发中,经常会出现一些富文本的样式(如:局部文本可点击,超链接,一段文本字体大小不一,局部文本加粗等),这样常规的设置文本就无法实现目的了。

针对这种场景一些常规的实现:

  1. 利用多个View的排布来实现;
  2. 利用Html实现富文本格式,在通过Html.getText(html)来解析;
  3. 利用SpannableString;

方案分析:

  1. 使用方案一会在布局中新增出多个View,导致布局文件臃肿,并且View的inflate是一件比较耗时的操作。当然在一些特殊的场景,为了简单快速的实现目的,这种方案也有应用。另外就是采用此方案有时并不能完美的实现我们想要的功能;
  2. 使用方案二,优点是可以很好的实现我们想要的文本样式,但缺点也很明显,Html.getText()方法解析html标签是一个很消耗性能的操作,一般情非得已不推荐使用此方案;
  3. 方案三就是本篇要介绍的对象,SpannableString是Android提供专门用于处理富文本的类,根据设置不同的span来实现不同的文本样式。使用一个TextView通过span的处理,即可实现目的。

API

SpannaableString与SpannableStringBuilder的区别就在于builder可以使用insert、append等系列方法。

  1. public void setSpan():设置span,一切富文本的实现都是通过此方法的来设置不同效果的span来实现的。

    /**
     *
     * @param what  具体的实现效果的Span类对象
     * @param start span生效的起始下标
     * @param end span生效的终止下标+1
     * @param flags span效果针对临界位置的insert是否扩散生效
     */
    public void setSpan(Object what, int start, int end, int flags) {
    }
    
  2. public void removeSpan(Object what):移除指定的span;

  3. insert/delete/append等方法是SpannableStringBuilder所有的方法系列,其中setSpan()中的flags参数就是配合这些方法的使用而发挥效果的。

    flags的选项在Spanned接口中,分别为:

    • SPAN_INCLUSIVE_EXCLUSIVE:包含start,不包含end
    • SPAN_INCLUSIVE_INCLUSIVE:start,end都包含
    • SPAN_EXCLUSIVE_EXCLUSIVE:start,end都不包含
    • SPAN_EXCLUSIVE_INCLUSIVE:start不包含,end包含

    具体生效备注如下:

    Spanned中flags的标记,是在SpannableStringBuilder中使用的,在SpannableString中没有作用。当使用了SpannableStringBuilder.insert(int,CharSeque)系列方法后,对于insert后的字符串是否进行扩展特性的标记,并且此标记作用的场景也仅仅是insert的位置恰好处于start或者end两个端点的临界位置,即用flags标记这个临界点跟随哪个,是否使用span的特性。

各种Span

  1. ForegroundColorSpan

    用于设置前景色,即设置字体的颜色

    void forefroundColorSpan() {
        TextView tv1 = new TextView(activity);
        /**
         * Spanned中flags的标记,是在SpannableStringBuilder中使用的,在SpannableString中没有作用。并且其使用场景是当
         使用了SpannableStringBuilder.insert(int,CharSeque)方法后,,对于insert后的字符串是否进行扩展特性的标记,,
         并且此标记作用的场景也仅仅是insert的位置恰好处于start 或者 end两个端点的临界位置,,,即用flags标记这个临界点跟随哪个。
         * */
        SpannableStringBuilder ss = new SpannableStringBuilder("手续费84.00元");
        ForegroundColorSpan span1 = new ForegroundColorSpan(Color.RED);
        ss.setSpan(span1, 3, 8, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        tv1.setText(ss);
        rootSpannable.addView(tv1);
        //插入的9扩展了特性,而插入的5未扩展特性
        ss.insert(3, "9").insert(9, "5");
        TextView tv2 = new TextView(activity);
        tv2.setText(ss);
        rootSpannable.addView(tv2);
        //移除指定span
        ss.removeSpan(span1);
        TextView tv5 = new TextView(activity);
        tv5.setText(ss);
        rootSpannable.addView(tv5);
        //恢复ss
        ss = new SpannableStringBuilder("手续费84.00元");
        ss.setSpan(span1, 3, 8, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        //验证同样的情况,只要执行insert系列方法,,即使flags改变效果也不变
        TextView tv3 = new TextView(activity);
        tv3.setText(ss);
        rootSpannable.addView(tv3);
        //插入的9未扩展特性,而插入的5扩展了特性
        ss.insert(3, "9").insert(9, "5");
        TextView tv4 = new TextView(activity);
        tv4.setText(ss);
        rootSpannable.addView(tv4);
    }
    

    效果图如下:

spannableString-forespan.png
  1. ClickableSpan

    设置局部文本可点击

    void clickableSpan() {
        ClickableSpan clickableSpan = new ClickableSpan() {
            @Override
            public void updateDrawState(TextPaint ds) {
                ds.setColor(Color.BLUE);//控制可点击文案的字体颜色
                ds.setUnderlineText(false);//可点击文案是否具有下划线
            }
    
            @Override
            public void onClick(View widget) {
                MyToast.showShort(getContext(), "clicked");
            }
        };
        SpannableString ss = getSS();
        ss.setSpan(clickableSpan, 0, 3, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
        TextView tv = getTv();
        tv.setText(ss);
        tv.setMovementMethod(LinkMovementMethod.getInstance());
        //一般由于页面的数据刷险等原因,为避免重复设置MovementMethod,在setMovementMethod之前做如下下判断
        /*MovementMethod m = tv.getMovementMethod();
        if (m == null || !(m instanceof LinkMovementMethod)) {
            if (tv.getLinksClickable()) {
                tv.setMovementMethod(LinkMovementMethod.getInstance());
            }
        }*/
        tv.setHighlightColor(Color.YELLOW);//更改可点击区域文本,触摸或者点击后的背景高亮的显示颜色
    }
    

    效果如下:

clickspan.gif
  1. BackgroundColorSpan

    背景色,设置局部的TextView具有背景

  2. MaskFilterSpan

    修饰效果,如模糊(BlurMaskFilter)浮雕(EmbossMaskFilter)

  3. RasterizerSpan

    光栅效果

  4. StrikethroughSpan

    删除线(中间线)

  5. SuggestionSpan

    相当于占位符

  6. UnderlineSpan

    下划线

  7. AbsoluteSizeSpan

    绝对大小(文本字体)

    // 设置字体绝对大小(绝对值,单位:像素)
    msp.setSpan(new AbsoluteSizeSpan(20), 4, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    // 第二个参数boolean dip,如果为true,表示前面的字体大小单位为dip,否则为像素,同上。
    msp.setSpan(new AbsoluteSizeSpan(20, true), 6, 8,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    
  8. DynamicDrawableSpan

    设置图片,基于文本基线或者底部对齐

  9. ImageSpan

    图片

  10. RelativeSizeSpan

    相对大小(文本字体)

    设置字体相对大小(相对值,单位:像素)参数表示为默认字体大小的多少倍

    msp.setSpan(new RelativeSizeSpan(0.5f), 8, 10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 0.5f表示默认字体大小的一半
    
  11. ReplacementSpan

    父类,一般不用

  12. MetricAffectingSpan

    父类,一般不用

  13. ScaleSpan

    基于X轴缩放

  14. StyleSpan

    new StyleSpan(Typeface.BOLD)
    

    字体样式:粗体、斜体等

  15. SubscriptSpan

    下标(数学公式会用到)

  16. SuperscriptSpan

    上标(数学公式会用到)

  17. TextAppearanceSpan

    文本外貌(包括字体、大小、样式、颜色)

  18. TypefaceSpan

    文本字体

  19. URLSpan

    文本超链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容