Android Open Source - MultiStyleTextView Multi Style Text View






From Project

Back to project page MultiStyleTextView.

License

The source code is released under:

Apache License

If you think the Android project MultiStyleTextView listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.linfaxin.multistyletextview;
//w  w  w.  j  av  a 2  s .  c  o  m
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.annotation.Nullable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;

/**
 * Created by linfaxin on 2014/8/13 013.
 * Email: linlinfaxin@163.com
 *
 * ???//#ffffff
 * ??????????????
 * android:text="//#ff5ec353??//?%d??? //#f99c2e??//?%d??? //#e75d5d??//?%d???"
 *
 * ??????//S16  //%80
 * ???????????????????????????DP/???
 * android:text="//s12??//?1??? //s13??//?1??? //s14??//?1???"
 *
 * ???? //u
 * ???? //l
 * ??? //b
 *
 * ?????//#!/?//S!/?//U!/...?????????????????//????????????
 *
 * ?????????
 */
public class MultiStyleTextView extends TextView {
    private static HashSet<Class<? extends StyleText>> stylesClasses;
    private static final String styleSeparator = "//";
    private static final String styleEndSeparator = "!/";
    private static final String refStart = "@";

    private ColorStateList[] colors = new ColorStateList[6];
    private int[] sizes = new int[3];
    String format;

    public MultiStyleTextView(Context context) {
        super(context);
        init(null);
    }

    public MultiStyleTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public MultiStyleTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    private void init(AttributeSet attrs){
        if(attrs!=null){
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MultiStyleTextView);
            colors[0]= a.getColorStateList(R.styleable.MultiStyleTextView_color1);
            colors[1]= a.getColorStateList(R.styleable.MultiStyleTextView_color2);
            colors[2]= a.getColorStateList(R.styleable.MultiStyleTextView_color3);
            colors[3]= a.getColorStateList(R.styleable.MultiStyleTextView_color4);
            colors[4]= a.getColorStateList(R.styleable.MultiStyleTextView_color5);
            colors[5]= a.getColorStateList(R.styleable.MultiStyleTextView_color6);
            for(int i=0,length=colors.length; i<length; i++){
                if(colors[i]==null) colors[i] = getTextColors();
            }

            sizes[0]= a.getDimensionPixelSize(R.styleable.MultiStyleTextView_size1, 14);
            sizes[1]= a.getDimensionPixelSize(R.styleable.MultiStyleTextView_size2, 14);
            sizes[2]= a.getDimensionPixelSize(R.styleable.MultiStyleTextView_size3, 14);

            format = a.getString(R.styleable.MultiStyleTextView_format);
        }
        CharSequence text = getText();
        if(text!=null && text.length() > 0){
            setTextMulti(text.toString());
        }else if(format != null ) setTextMulti(format);

        if(getHint()!=null && getHint().length()>0 ) setHint(convertToMulti(getHint().toString()));
        requestLayout();
    }
    public void setColors(int... colors) {
        if(colors==null || colors.length==0) return;
        for(int i=0,length=colors.length;i<length;i++){
            this.colors[i] = ColorStateList.valueOf(colors[i]);
        }
    }
    public void setColors(ColorStateList... colors) {
        if(colors==null || colors.length==0) return;
        this.colors = colors;
    }

    private String getFormattedText(Object... args){
        try {
            return String.format(format, args);
        } catch (Exception e) {
            //???????format
            try {
                if(format!=null){
                    int wantArgsLength = format.split("%").length -1;
                    Object[] fixArgs = Arrays.copyOf(args, wantArgsLength);

                    for(int i=0; i<wantArgsLength; i++){
                        Object arg = fixArgs[i];
                        if(arg instanceof Number){
                            fixArgs[i] = arg+"";
                        }
                    }
                    return String.format(format.replace("%d", "%s").replace("%f", "%s"), fixArgs);
                }
            } catch (Exception ignore) {
            }
            e.printStackTrace();
        }
        return format;
    }
    public void formatText(Object... args){
        if(format==null) format = getText().toString();
        setTextMulti(getFormattedText(args));
    }
    public void setTextFormat(String format, Object... args){
        this.format = format;
        if(args.length>0) setTextMulti(getFormattedText(args));
        else setTextMulti(format);
    }
    public void setTextMulti(String text){
        setText(convertToMulti(text));
    }

    public Spannable convertToMulti(String text){
        //????????
        String[] parts = text.split(styleSeparator);//?//??????????????????
        ArrayList<StyleText> styleTexts = new ArrayList<StyleText>();
        for(String part : parts){
            if(TextUtils.isEmpty(part)){
                continue;
            }

            styleTexts.add(parseStyleText(part));
        }

        //??????????????
        StringBuilder sb = new StringBuilder();
        for(StyleText colorText : styleTexts){
            colorText.start = sb.length();
            sb.append(colorText.text);
        }
        Spannable spannable = new SpannableString(sb);

        //??????
        HashMap<Class, StyleText> lastSetStyleMap = new HashMap<Class, StyleText>();
        for(StyleText styleText : styleTexts){
            int nowIndex = styleText.start;
            if(styleText instanceof NoStyleText){//????????style?????
                for(StyleText lastStyle : lastSetStyleMap.values()){
                    lastStyle.setSpan(spannable, nowIndex);
                }
                lastSetStyleMap.clear();

            }else{
                StyleText lastStyle = lastSetStyleMap.get(styleText.getClass());
                if(lastStyle!=null){
                    lastStyle.setSpan(spannable, nowIndex);
                }
                lastSetStyleMap.put(styleText.getClass(), styleText);
            }
        }
        //?????????
        int endIndex = spannable.length();
        for(StyleText lastStyle : lastSetStyleMap.values()){
            lastStyle.setSpan(spannable, endIndex);
        }

        return spannable;
    }

    /**??????style??? */
    private StyleText parseStyleText(String part){

        String styleFlag = part.substring(0,1);//??????styleFlag?#?????S...
        part = part.substring(1);//??????????styleFlag

        Integer refIndex = null;
        if(part.startsWith(refStart)){//??????@1,@3???????base-1
            try {
                refIndex = Integer.valueOf(part.substring(1, 2))-1;
                part = part.substring(2);
            } catch (Exception ignore) {
            }
        }
        if(stylesClasses == null){
            initStyleClasses(getContext());
        }
        for(Class<? extends StyleText> c : stylesClasses){
            Style style = c.getAnnotation(Style.class);
            if(style==null){
                Log.e(MultiStyleTextView.class.getSimpleName(), "class:" + c + " miss annotation:" + Style.class);
            }else{
                if(styleFlag.equalsIgnoreCase(style.flag())){
                    try {
                        StyleText styleText = c.newInstance();
                        styleText.doParse(MultiStyleTextView.this, part, styleFlag, refIndex);
                        return styleText;
                    } catch (Exception e) {
                        e.printStackTrace();
                        return new NoStyleText(part, styleFlag, refIndex);
                    }
                }
            }
        }
        return new NoStyleText(part, styleFlag, refIndex);
    }
    public void removeAllStyle(){
        if(getText()!=null) setText(getText().toString());
        if(getHint()!=null) setHint(getHint().toString());
    }

    public void removeAllStyle(Class<? extends StyleText> c){
        try {
            SpannableString spannable = new SpannableString(getText());
            for(Object whatSpan : getAllSpans(spannable, c)){
                if(whatSpan!=null) spannable.removeSpan(whatSpan);
            }
            setText(spannable);

            if(getHint()!=null){
                spannable = new SpannableString(getHint());
                for(Object whatSpan : getAllSpans(spannable, c)){
                    if(whatSpan!=null) spannable.removeSpan(whatSpan);
                }
                setHint(spannable);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static Object[] getAllSpans(Spannable spannable, Class<? extends StyleText> c){
        return spannable.getSpans(0, spannable.length(), getSpanClass(c));
    }

    private static Class getSpanClass(Class<? extends StyleText> c){
        Style style = c.getAnnotation(Style.class);
        if(style==null) return null;
        return style.spanClass();
    }
    private static synchronized void initStyleClasses(Context context){
        if(stylesClasses==null){
            stylesClasses = new HashSet<Class<? extends StyleText>>();
            stylesClasses.add(ColorText.class);
            stylesClasses.add(SizeText.class);
            stylesClasses.add(UnderlineStyle.class);
            stylesClasses.add(LineThroughStyle.class);
            stylesClasses.add(BoldStyle.class);
        }
    }

    private static abstract class StyleText{
        String text;
        int start;
        boolean isIgnoreStyle;
        public StyleText(){
        }

        public final void setSpan(Spannable span, int end){
            if(isIgnoreStyle) return;
            Object spanObject = getSpanObject();
            if(spanObject==null) return;
            span.setSpan(spanObject, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        public void doParse(MultiStyleTextView tv, String part, String styleFlag, Integer refIndex) throws Exception{
            if(refIndex==null && part!=null && part.startsWith(styleEndSeparator)){
                isIgnoreStyle = true;
                text = part.substring(styleEndSeparator.length());
            }else{
                parse(tv, part, styleFlag, refIndex);
            }
        }
        /**
         * @param part ???????,????????styleFlag??????refIndex
         * @param styleFlag ????????,???????{@link Style}.
         * @param refIndex ??????????,??????????????
         * @throws Exception
         */
        public abstract void parse(MultiStyleTextView tv, String part, String styleFlag,@Nullable Integer refIndex) throws Exception;
        /**???????setSpan??? */
        public abstract Object getSpanObject();
    }
    //??????????
    private static abstract class NoParamStyleText extends StyleText{
        @Override
        public void parse(MultiStyleTextView tv, String part, String styleFlag,@Nullable Integer refIndex){
            if(refIndex!=null){
                this.text = refStart + refIndex + part;
            }else{
                this.text = part;
            }
        }
    }
    //????
    private class NoStyleText extends StyleText{
        public NoStyleText(String part) {
            parse(MultiStyleTextView.this, part, null, null);
        }
        public NoStyleText(String part, String styleFlag, @Nullable Integer refIndex) {
            parse(MultiStyleTextView.this, part, styleFlag, refIndex);
        }

        @Override
        public void parse(MultiStyleTextView tv, String part, String styleFlag,@Nullable Integer refIndex){
            if(styleFlag==null) styleFlag="";
            if(refIndex!=null){
                this.text = styleFlag + refStart + refIndex + part;
            }else{
                this.text = styleFlag + part;
            }
        }

        @Override
        public Object getSpanObject() {
            return null;
        }
    }

    //????
    @Style(flag = "#", spanClass = ColorText.ForegroundColorListSpan.class)
    public static class ColorText extends StyleText{
        ColorStateList colorStateList;
        @Override
        public void parse(MultiStyleTextView tv, String part, String styleFlag, @Nullable Integer refIndex) throws Exception {
            ColorStateList textColor = null;
            ColorStateList[] colors = tv.colors;
            if(refIndex !=null && colors!=null && colors.length>0){//??
                textColor = colors[refIndex % colors.length];
                if(textColor==null) textColor = tv.getTextColors();

            }else{
                try {
                    textColor = ColorStateList.valueOf(Color.parseColor("#" + part.substring(0, 8).trim()));//??8????????????????
                    part = part.substring(8);
                } catch (Exception ignore) {
                }
                if(textColor==null){
                    try {
                        textColor = ColorStateList.valueOf(Color.parseColor("#" + part.substring(0, 6).trim()));//??6????????????????
                        part = part.substring(6);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            colorStateList = textColor;
            text = part;
        }

        @Override
        public Object getSpanObject() {
            if(colorStateList==null) return null;
            return new ForegroundColorListSpan(colorStateList);
        }

        class ForegroundColorListSpan extends ForegroundColorSpan {
            ColorStateList colorStateList;
            public ForegroundColorListSpan(ColorStateList colorStateList) {
                super(colorStateList.getDefaultColor());
                this.colorStateList = colorStateList;
            }
            @Override
            public void updateDrawState(TextPaint ds) {
                if(colorStateList==null) ds.setColor(getForegroundColor());
                else ds.setColor(colorStateList.getColorForState(ds.drawableState, colorStateList.getDefaultColor()));
            }
        }
    }
    //?????
    @Style(flag = "s", spanClass = AbsoluteSizeSpan.class)
    public static class SizeText extends StyleText{
        Integer size;
        boolean isSizeInDp = true;
        @Override
        public void parse(MultiStyleTextView tv, String part, String styleFlag, @Nullable Integer refIndex) throws Exception {
            Integer size = null;
            int[] sizes = tv.sizes;
            isSizeInDp = true;

            if(refIndex !=null && sizes!=null && sizes.length>0){//??
                size = sizes[refIndex % sizes.length];

            } else if(part.startsWith("%")){//?????????://S%50
                try {
                    StringBuilder sb = new StringBuilder();
                    char c;
                    for(int i=1;i<=4;i++){
                        if(i>=part.length()) break;
                        c = part.charAt(i);
                        if( c >='0' && c <='9'){
                            sb.append(c);
                        }else break;
                    }
                    size = (int)(tv.getTextSize() * Double.parseDouble(sb.toString()) / 100);
                    part = part.substring(sb.length()+1);
                    isSizeInDp = false;
                } catch (Exception e) {
                    e.printStackTrace();
                }

            } else {
                try {
                    size = Integer.valueOf(part.substring(0, 2).trim());//?????????????????
                    part = part.substring(2);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            this.size = size;
            text = part;
        }
        @Override
        public Object getSpanObject() {
            if(size ==null) return null;
            return new AbsoluteSizeSpan(size, isSizeInDp);
        }
    }
    //???
    @Style(flag = "u", spanClass = UnderlineSpan.class)
    public static class UnderlineStyle extends NoParamStyleText{
        @Override
        public Object getSpanObject() {
            return new UnderlineSpan();
        }

    }
    //???
    @Style(flag = "l", spanClass = StrikethroughSpan.class)
    public static class LineThroughStyle extends NoParamStyleText{
        @Override
        public Object getSpanObject() {
            return new StrikethroughSpan();
        }
    }
    //??
    @Style(flag = "b", spanClass = StyleSpan.class)
    public static class BoldStyle extends NoParamStyleText{
        @Override
        public Object getSpanObject() {
            return new StyleSpan(android.graphics.Typeface.BOLD);
        }
    }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Style {
        public String flag();
        public Class spanClass();
    }
}




Java Source Code List

com.linfaxin.multistyletextview.MultiStyleTextView.java
com.linfaxin.multistyletextview.demo.DemoActivity.java