Как изменить цвет части TextView?

text = text + CepVizyon.getPhoneCode() + "nn"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText();
    activationText.setText(text);   
myTextView.setText(text);

Я хочу изменить цвет для CepVizyon.getPhoneCode()'строки s. Как я могу это сделать?

9 ответов


Spannable более гибкая:

String text2 = text + CepVizyon.getPhoneCode() + "\n\n"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText();

Spannable spannable = new SpannableString(text2);

spannable.setSpan(new ForegroundColorSpan(Color.WHITE), text.length(), (text + CepVizyon.getPhoneCode()).length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

myTextView.setText(spannable, TextView.BufferType.SPANNABLE);

myTextView.setText(Html.fromHtml(text + "<font color=white>" + CepVizyon.getPhoneCode() + "</font><br><br>"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText()));

если у вас есть статический текст, который нуждается в цвет, можно добавить его без какого-либо кода через файл строки:

<string name="already_have_an_account">Already have an account? <font color='#01C6DB'>Login</font></string>

затем

<TextView
    android:layout_width="wrap_content"
    android:layout_height="64dp"
    android:text="@string/already_have_an_account"/>

результат

enter image description here

не уверен, какие версии api это работает, но не работает для api 19, который Ive тестировал до сих пор, поэтому, вероятно, только некоторые из последних версий api поддерживают это

edit: как @hairraisin упоминается в комментариях, попробуйте использовать fgcolor вместо color для цвета шрифта, то он должен работать для более низких уровней api, но нужно больше тестирования, чтобы быть уверенным


что касается ответа Maneesh, это будет работать, но вам нужно добавить и избежать кавычек для атрибута цвета.

myTextView.setText(Html.fromHtml(text + "<font color=\"#FFFFFF\">" + CepVizyon.getPhoneCode() + "</font><br><br>"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText()));

Это хорошо для меня!

            Spannable spannable = new SpannableString("ABC In-Network DEF");
            String str = spannable.toString();
            iStart = str.indexOf("In-Network");
            iEnd = iStart + 10;/*10 characters = in-network. */

            SpannableString ssText = new SpannableString(spannable);
            ClickableSpan clickableSpan = new ClickableSpan() {
                @Override
                public void onClick(View widget) {
                    //your code at here.
                }

                @Override
                public void updateDrawState(TextPaint ds) {
                    super.updateDrawState(ds);
                    ds.setUnderlineText(true);
                    ds.setColor(getResources().getColor(R.color.green));
                }
            };
            ssText.setSpan(clickableSpan, iStart, iEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            mTextView.setText(ssText);
            mTextView.setMovementMethod(LinkMovementMethod.getInstance());
            mTextView.setHighlightColor(Color.TRANSPARENT);
            mTextView.setEnabled(true);

здесь colorize функция, основанная на ответе andyboot:

 /**
 * Colorize a specific substring in a string for TextView. Use it like this: <pre>
 * textView.setText(
 *     Strings.colorized("The some words are black some are the default.","black", Color.BLACK),
 *     TextView.BufferType.SPANNABLE
 * );
 * </pre>
 * @param text Text that contains a substring to colorize
 * @param word The substring to colorize
 * @param argb The color
 * @return the Spannable for TextView's consumption
 */
public static Spannable colorized(final String text, final String word, final int argb) {
    final Spannable spannable = new SpannableString(text);
    int substringStart=0;
    int start;
    while((start=text.indexOf(word,substringStart))>=0){
        spannable.setSpan(
                new ForegroundColorSpan(argb),start,start+word.length(),
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        );
        substringStart = start+word.length();
    }
    return spannable;
}

мне не понравилась идея делать это с помощью кода каждый раз, когда я хочу раскрасить части текста, которые я много делал во всех моих приложениях (и так как в некоторых случаях текст устанавливается во время выполнения с различными встроенными цветами), поэтому я создал свой собственный MarkableTextView.

идея:

  • обнаружение XML-тегов из строки
  • определить и сопоставить имя тега
  • извлеките и сохраните атрибуты и положение текст
  • удалить тег и сохранить контент
  • перебирать атрибуты и применять стили

вот процесс шаг за шагом:

сначала мне нужен был способ найти теги XML в данной строке и Regex сделал свое дело..

<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\s+([^>]*))?>([^>][^<]*)</\s*>

для приведенного выше, чтобы соответствовать тегу XML, он должен иметь следующие критерии:

  • допустимое имя тега, как <a> <a > <a-a> <a ..attrs..> но не < a> <1>
  • закрывающий тег, который имеет совпадающее имя, например <a></a> а не <a></b>
  • любой контент, так как нет необходимости в стиле "ничего"

теперь для атрибутов мы будем использовать этот..

([a-zA-Z]+)\s*=\s*(['"])\s*([^'"]+?)\s*

он имеет ту же концепцию, и вообще мне не нужно было далеко ходить для обоих, так как компилятор позаботится об остальном, если что-то выйдет из формата.

теперь нам нужен класс, который может удерживайте извлеченные данные:

public class MarkableSheet {

    private String attributes;
    private String content;
    private int outset;
    private int ending;
    private int offset;
    private int contentLength;

    public MarkableSheet(String attributes, String content, int outset, int ending, int offset, int contentLength) {

        this.attributes = attributes;
        this.content = content;
        this.outset = outset;
        this.ending = ending;
        this.offset = offset;
        this.contentLength = contentLength;
    }

    public String getAttributes() {
        return attributes;
    }

    public String getContent() {
        return content;
    }

    public int getOutset() {
        return outset;
    }

    public int getContentLength() {
        return contentLength;
    }

    public int getEnding() {
        return ending;
    }

    public int getOffset() {
        return offset;
    }
}

прежде чем что-либо еще, мы собираемся добавить этот классный итератор, который я использую для длительного цикла через матчи (не помню автора):

public static Iterable<MatchResult> matches(final Pattern p, final CharSequence input) {

        return new Iterable<MatchResult>() {

            public Iterator<MatchResult> iterator() {

                return new Iterator<MatchResult>() {

                    // Use a matcher internally.
                    final Matcher matcher = p.matcher(input);

                    // Keep a match around that supports any interleaving of hasNext/next calls.
                    MatchResult pending;

                    public boolean hasNext() {

                        // Lazily fill pending, and avoid calling find() multiple times if the
                        // clients call hasNext() repeatedly before sampling via next().
                        if (pending == null && matcher.find()) {
                            pending = matcher.toMatchResult();
                        }
                        return pending != null;
                    }

                    public MatchResult next() {

                        // Fill pending if necessary (as when clients call next() without
                        // checking hasNext()), throw if not possible.
                        if (!hasNext()) { throw new NoSuchElementException(); }

                        // Consume pending so next call to hasNext() does a find().
                        MatchResult next = pending;
                        pending = null;

                        return next;
                    }

                    /** Required to satisfy the interface, but unsupported. */
                    public void remove() { throw new UnsupportedOperationException(); }
                };
            }
        };
    }

MarkableTextView:

public class MarkableTextView extends AppCompatTextView {

    public MarkableTextView(Context context) {
        super(context);
    }

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

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

    @Override
    public void setText(CharSequence text, BufferType type) {

        // Intercept and process text
        text = prepareText(text.toString());

        super.setText(text, type);
    }

    public Spannable Markable;

    private Spannable prepareText(String text) {

        String parcel = text;
        Multimap<String, MarkableSheet> markableSheets = ArrayListMultimap.create();

        // Used to correct content position after tossing tags
        int totalOffset = 0;

        // Iterate through text
        for (MatchResult match : matches(Markable.Patterns.XML, parcel)) {

            // Get tag name
            String tag = match.group(1);

            // Match with a defined tag name "case-sensitive"
            if (!tag.equals(Markable.Tags.MARKABLE)) {

                // Break if no match
                break;
            }

            // Extract data
            String attributes = match.group(2);
            String content = match.group(3);

            int outset = match.start(0);
            int ending = match.end(0);
            int offset = totalOffset; // offset=0 since no preceded changes happened
            int contentLength = match.group(3).length();

            // Calculate offset for the next element
            totalOffset = (ending - outset) - contentLength;

            // Add to markable sheets
            MarkableSheet sheet =
                    new MarkableSheet(attributes, content, outset, ending, offset, contentLength);
            markableSheets.put(tag, sheet);

            // Toss the tag and keep content
            Matcher reMatcher = Markable.Patterns.XML.matcher(parcel);
            parcel = reMatcher.replaceFirst(content);
        }

        // Initialize spannable with the modified text
        Markable = new SpannableString(parcel);

        // Iterate through markable sheets
        for (MarkableSheet sheet : markableSheets.values()) {

            // Iterate through attributes
            for (MatchResult match : matches(Markable.Patterns.ATTRIBUTES, sheet.getAttributes())) {

                String attribute = match.group(1);
                String value = match.group(3);

                // Apply styles
                stylate(attribute,
                        value,
                        sheet.getOutset(),
                        sheet.getOffset(),
                        sheet.getContentLength());
            }
        }

        return Markable;
    }

наконец, стиль, так что вот очень простой стайлер, который я сделал для этого ответа:

public void stylate(String attribute, String value, int outset, int offset, int length) {

        // Correct position
        outset -= offset;
        length += outset;

        if (attribute.equals(Markable.Tags.TEXT_STYLE)) {

            if (value.contains(Markable.Tags.BOLD) && value.contains(Markable.Tags.ITALIC)) {

                Markable.setSpan(
                        new StyleSpan(Typeface.BOLD_ITALIC),
                        outset,
                        length,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            else if (value.contains(Markable.Tags.BOLD)) {

                Markable.setSpan(
                        new StyleSpan(Typeface.BOLD),
                        outset,
                        length,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }

            else if (value.contains(Markable.Tags.ITALIC)) {

                Markable.setSpan(
                        new StyleSpan(Typeface.ITALIC),
                        outset,
                        length,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }

            if (value.contains(Markable.Tags.UNDERLINE)) {

                Markable.setSpan(
                        new UnderlineSpan(),
                        outset,
                        length,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }

        if (attribute.equals(Markable.Tags.TEXT_COLOR)) {

            if (value.equals(Markable.Tags.ATTENTION)) {

                Markable.setSpan(
                        new ForegroundColorSpan(ContextCompat.getColor(
                                getContext(),
                                R.color.colorAttention)),
                        outset,
                        length,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            else if (value.equals(Markable.Tags.INTERACTION)) {

                Markable.setSpan(
                        new ForegroundColorSpan(ContextCompat.getColor(
                                getContext(),
                                R.color.colorInteraction)),
                        outset,
                        length,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

и вот как Markable класс, содержащий определения выглядит так:

public class Markable {

    public static class Patterns {

        public static final Pattern XML =
                Pattern.compile("<([a-zA-Z]+(?:-[a-zA-Z0-9]+)*)(?:\s+([^>]*))?>([^>][^<]*)</\1\s*>");
        public static final Pattern ATTRIBUTES =
                Pattern.compile("(\S+)\s*=\s*(['\"])\s*(.+?)\s*\2");
    }

    public static class Tags {

        public static final String MARKABLE = "markable";

        public static final String TEXT_STYLE = "textStyle";
        public static final String BOLD = "bold";
        public static final String ITALIC = "italic";
        public static final String UNDERLINE = "underline";

        public static final String TEXT_COLOR = "textColor";
        public static final String ATTENTION = "attention";
        public static final String INTERACTION = "interaction";
    }
}

все, что нам нужно сейчас, это ссылаться на строку, и в основном она должна выглядеть так:

<string name="markable_string">
    <![CDATA[Hello <markable textStyle=\"underline\" textColor=\"interaction\">world</markable>!]]>
</string>

убедитесь, что обернуть теги с CDATA Section и вы " С \.

я сделал это как модульное решение для обработки частей текста всеми различными способами без необходимости заполнения ненужного кода.


Я сделал, как сказал Энди Бут, но у меня также был кликабельный диапазон, и он не работал, потому что заказ setSpans назывались. Поэтому вы должны сначала позвонить spannable.setSpan(clickableSpanand... тут spannable.setSpan(new ForegroundColorSpan... чтобы получить цвет в TextView


один из способов-разделить myTextView на несколько отдельных TextViews, один из которых будет только для телефонного кода. Тогда управление цветом этого конкретного TextView довольно прямолинейно.