SpannableStringBuilder 其实可以这么用

如下图所示,在App开发过程中很多时候需要对文字做类似如下处理:


给文字加颜色,加背景,加下划线,可点击,加中划线,字体大小调整,甚至还有中间插入一个小icon之类的。
好在Android SDK对这此功能是支持的,通过SpannableStringBuilderParcelableSpan的众多扩展子类完成,下面是Android默认实现方式:

String str = "红字, 绿背景,下划线|灰色字,可点击,中划线,大号字";
SpannableStringBuilder builder = new SpannableStringBuilder(str);
//设置颜色
builder.setSpan(new ForegroundColorSpan(Color.RED), 0, 2,
        // setSpan时需要指定的 flag,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE(前后都不包括).
        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
//超链接
builder.setSpan(new BackgroundColorSpan(Color.GREEN), 2, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

// 下划线|灰色字
builder.setSpan(new UnderlineSpan(), 8, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
builder.setSpan(new BackgroundColorSpan(Color.GRAY), 8, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

//删除线
builder.setSpan(new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
    }
}, 16, 20, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setMovementMethod(LinkMovementMethod.getInstance());

// 中划线
builder.setSpan(new StrikethroughSpan(), 20, 24, Spannable.SPAN_INCLUSIVE_INCLUSIVE);

// 大号字
builder.setSpan(new AbsoluteSizeSpan(80), 24, 27, Spannable.SPAN_INCLUSIVE_INCLUSIVE);

//将文字赋予TextView
tv.setText(builder);

我想第一次使用此API可能会有些犯晕,毕竟其中太多需要计算的span起始和结束的index,虽然算不上非常复杂,但是也挺烦人。其实,完全可以借鉴JDK中对StringBuilder API的设计进行对其改造。
首先,先看看改造后的使用,看如何实现上图里的效果:

TextView textView = (TextView) findViewById(R.id.textView);
textView.setMovementMethod(LinkMovementMethod.getInstance());

StyleStringBuilder builder = new StyleStringBuilder();
builder.append(new StyleString("红字, ").setTextColor(ContextCompat.getColor(this, R.color.red)));
builder.append(new StyleString("绿背景, ").setBackgroundColor(ContextCompat.getColor(this, R.color.green)));
builder.append(new StyleString("下划线|灰色字, ").setUnderline().setTextColor(ContextCompat.getColor(this, R.color.grey)));
builder.append(new StyleString("可点击").setClickable(new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Toast.makeText(MainActivity.this, "clicked", Toast.LENGTH_SHORT).show();
    }
}));
builder.append(new StyleString("中划线").setStrikeThrough());
builder.append(new StyleString("大号字").setTextSize(getResources().getDimensionPixelOffset(R.dimen.text_size_large)));

textView.setText(builder.build());

我相信大家对这种风格的api是相对很熟悉的,可读性也尚可,来看看如何屏蔽那些琐碎的index的:

  1. StyleString:
public class StyleString {
        private final String mText;
        private final SpannableStringBuilder mBuilder;

        public StyleString(String text) {
                if (text == null) {
                        text = "";
                }
                mBuilder = new SpannableStringBuilder();
                mBuilder.append(mText = text);
        }

        public StyleString setTextColor(@ColorInt int color) {
                mBuilder.setSpan(new ForegroundColorSpan(color),
                                0, mText.length(),
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                return this;
        }

        /**
         * @deprecated please use {@link #setTextColor(int)} instead
         */
        public StyleString setForegroundColor(int color) {
                return setTextColor(color);
        }

        public StyleString setBackgroundColor(@ColorInt int color) {
                mBuilder.setSpan(new BackgroundColorSpan(color), 0, mText.length(),
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                return this;
        }

        /**
         * @deprecated please use {@link #setTextSize(int)} instead
         */
        public StyleString setFontSizePX(int sizeInPx) {
                return setTextSize(sizeInPx);
        }

        public StyleString setTextSize(int sizeInPx) {
                mBuilder.setSpan(new AbsoluteSizeSpan(sizeInPx), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                return this;
        }

        /**
         * About typeFace see {@link Typeface}
         */
        public StyleString setFontStyle(int typeFace) {
                mBuilder.setSpan(new StyleSpan(typeFace), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                return this;
        }

        public StyleString setUnderline() {
                mBuilder.setSpan(new UnderlineSpan(), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                return this;
        }

        /**
         * If StyleString was used as content of TextView or EditText,
         * setMovementMethod should be called like below:
         * 
         * 
         * TextView textView = new TextView(context);
         * textView.setMovementMethod(LinkMovementMethod.getInstance());
         * 
*/ public StyleString setClickable(ClickableSpan clickable) { mBuilder.setSpan(clickable, 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return this; } public StyleString setUri(String uri) { mBuilder.setSpan(new URLSpan(uri), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return this; } public StyleString setStrikeThrough() { mBuilder.setSpan(new StrikethroughSpan(), 0, mText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return this; } public SpannableStringBuilder toStyleString() { return mBuilder; } }
  1. StyleStringBuilder:
public class StyleStringBuilder {
        private final SpannableStringBuilder builder = new SpannableStringBuilder();

        public StyleStringBuilder() {
        }

        public StyleStringBuilder(CharSequence text) {
                builder.append(text);
        }

        public StyleStringBuilder(StyleString styleString) {
                builder.append(styleString.toStyleString());
        }

        public StyleStringBuilder append(CharSequence text) {
                builder.append(text);
                return this;
        }

        public StyleStringBuilder append(StyleString styleString) {
                builder.append(styleString.toStyleString());
                return this;
        }

        public Spanned build() {
                return builder;
        }

        public static CharSequence formatString(Context context, String text, String regular, int colorResId) {

                try {
                        SpannableStringBuilder builder = new SpannableStringBuilder(text);

                        Pattern p = Pattern.compile(regular);
                        Matcher matcher = p.matcher(text);
                        int start = 0;
                        int end = 0;
                        while (matcher.find()) {
                                if (matcher.start() == end) {
                                        end = matcher.end();
                                } else {
                                        if (start != end) {
                                                ForegroundColorSpan span = new ForegroundColorSpan(ContextCompat.getColor(context, colorResId));
                                                builder.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                                        }
                                        start = matcher.start();
                                        end = matcher.end();
                                }
                        }
                        if (start != end) {
                                ForegroundColorSpan span = new ForegroundColorSpan(ContextCompat.getColor(context, colorResId));
                                builder.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                        }
                        return builder;
                } catch (Exception e) {
                        return text;
                }
        }
}

至于为什么没有对图片等其他特性支持是因为一时半会没有加那些span的支持,自己参考着扩展就好,因为这里已经没有什么秘密了。

你可能感兴趣的:(SpannableStringBuilder 其实可以这么用)