Bootstrap

SpannableStringBuilder的setSpan方法使用需注意点!

 



以前偶然遇见一个问题,今天突然想起来了,机智的我赶紧贴过来以帮助遇见此问题尚未解决的小伙伴们微笑

 

 

这个问题是关于SpannableStringBuilder类的setSpan方法的。

 

大家都知道setSpan方法在使用时需要传入四个参数:

 

 

  
    public void setSpan(Object what, int start, int end, int flags) {
        throw new RuntimeException("Stub!");
    }    public void setSpan(Object what, int start, int end, int flags) {
        throw new RuntimeException("Stub!");
    }

 

 

第一个参数:Object what。我们使用的时候传入一个CharacterStyle对象。

第二、三个参数很好理解,intstart, int end。分别表示要把字符串中从start开始到end结束的位置设置成what样式的。当然,start是从0开始的,而修改样式的位置是[start,end),也就是说包含start位置但不包含end位置,这样就确保了实际修改的“子字符串”的长度是end - start。

第四个参数是intflags,表示要将字符串按照指定的flag进行修改。它定义在Spanner类中,我们常用到的有以下几种:

 

 

    
    int SPAN_EXCLUSIVE_EXCLUSIVE = 33; //在Span前后输入的字符都不应用Span效果
    int SPAN_EXCLUSIVE_INCLUSIVE = 34; //在Span前面输入的字符不应用Span效果,后面输入的字符应用Span效果
    int SPAN_INCLUSIVE_EXCLUSIVE = 17; //在Span前面输入的字符应用Span效果,后面输入的字符不应用Span效果
    int SPAN_INCLUSIVE_INCLUSIVE = 18; //在Span前后输入的字符都应用Span效果    int SPAN_EXCLUSIVE_EXCLUSIVE = 33; //在Span前后输入的字符都不应用Span效果
    int SPAN_EXCLUSIVE_INCLUSIVE = 34; //在Span前面输入的字符不应用Span效果,后面输入的字符应用Span效果
    int SPAN_INCLUSIVE_EXCLUSIVE = 17; //在Span前面输入的字符应用Span效果,后面输入的字符不应用Span效果
    int SPAN_INCLUSIVE_INCLUSIVE = 18; //在Span前后输入的字符都应用Span效果

 

 

 

 

下面着重说一下what参数。

 

我们可以把newForegroundColorSpan(int color)作为what传入setSpan方法中,它表示要设置的Span效果是设置前景色。还有很多其他的,比如newBackgroundColorSpan(int color),则对应背景色。

 

无论what使用哪种,一定要注意一个问题:每个CharacterStyle对象只能应用于一个Spanned,也就是说每个CharacterStyle对象只能使用一次。如果在给定的String中对多个字符应用,则只有最后一次应用会起作用。

 

要想多次使用,可以调用CharacterStyle类的静态方法wrap方法:

 

public static CharacterStyle wrap (CharacterStyle cs)

 

 

 

 

API是这样描述的:

 

意思总归一句话:使用wrap方法并传入一个CharacterStyle对象,跟直接new一个CharacterStyle对象然后传入setSpan方法中的效果是一样的。

 

下面给一个小小的例子来说明(这里代码很少,为了能看清每个变量的类型,我把所有变量的定义都写在了onCreate方法里面了):

 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = (TextView) findViewById(R.id.textColorTest_tv);
        String str = "你好,安卓-》Hello,Android!";
        SpannableStringBuilder builder = new SpannableStringBuilder(str);
        ForegroundColorSpan[] colorSpan = {new ForegroundColorSpan(Color.RED),
                new ForegroundColorSpan(Color.GREEN), new ForegroundColorSpan(Color.BLUE),
                new ForegroundColorSpan(Color.WHITE), new ForegroundColorSpan(Color.BLACK)};
        int length = "你好,安卓-》Hello,World".length();
        int rand;
        for (int i = 1; i < length; i++) {
            rand = (int) (Math.random() * 5);
            while (rand == 5) {
                rand = (int) (Math.random() * 5);
            }
            builder.setSpan(CharacterStyle.wrap(colorSpan[rand]), i - 1, i, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
//            builder.setSpan(colorSpan[rand], i - 1, i, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        }
        tv.setText(builder);
    }

 

 

 

代码中应用第18行的效果如图:

 

但是应用第19行,注释掉18行,效果是这样的:

两者的效果差异还是很明显的。第二个很明显前面很多字符都是黑的而除了黑色其他的颜色都只出现了一次,且位置比较靠后,已打印随机数查看log,并不是因为随机生成的是黑色的。

 

这个坑,大家注意!!

------------------------------------------------------------------------------------------------------------------------------------------------------------------------

近期有网友提出来这种设置Span的方法只是改变了样式而已,如果需要新添样式那么之前的样式就不见了。确实是这样,而且这样也是合理的。

原因?你想一下,如果我为textview设置了文本并设置了样式,如果这种样式是跟随textview不变的,那么会不会在我修改textview的text的时候出现问题呢?很显然会。

如果你想要在原来的样式基础上新添样式,你必须先拿到原来的样式,然后再此基础上新增Span,最后将样式设置给textview才行。

如果设置样式和重新设置样式都在同一个位置,可以直接访问到原先的位置的话,你可以把SpannableStringBuilder设置为全局变量来共享。如果没法共享怎么办呢?我来告诉你方法!!

SpannableStringBuilder类中有一个静态方法 valueOf()方法,来看声明:
public static SpannableStringBuilder valueOf (CharSequence source)

虽然官方并没有任何解释,但是从名字就可以看得出来这个方法是用来拿到source的样式的。

所以如果你需要在原先样式的基础上新增样式,这个方法可以帮你拿到原先设置的SpannableStringBuilder对象,在这个对象上设置想要的样式,最后一同设置给textview就行了。

 

举个栗子

        val builderTest = SpannableStringBuilder.valueOf(tvOriginal.text)
        index = 0
        while (index < tvTest.text.length) {
            val begin = content.indexOf("测试", index)
            if (begin == -1) break
            val end = begin + 2
            builderTest.setSpan(UnderlineSpan(), begin, end, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE)
            index = end
        }
        tvTest.text = builderTest

效果如下:

原文本样式:

修改后的样式:修改后样式.png

 

 

;