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

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

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

атасойх
источник
См. Это: stackoverflow.com/questions/4897349/…
live-love
Возможный дубликат параметра Установить цвет диапазона TextView в Android
Suragch
Этот вопрос был задан 19 июля 2010 года в 16:27, примерно на 3 месяца раньше вашего. Однако не всегда самый старый пост должен быть дублирующейся целью. Следует учитывать количество просмотров, количество голосов, количество ответов и ясность вопроса. Отмечая это как дубликат, это может помочь людям найти другие ответы, которые также отвечают на ваш вопрос.
Suragch
Проверьте этот stackoverflow.com/a/57089362/6667442
Кетан
Чтобы по-настоящему понять, что происходит за кулисами, я всегда предлагаю прочитать подробную статью вроде этой: medium.com/androiddevelopers/…
Михал Вичиан,

Ответы:

171

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);
Энди Бут
источник
3
Спасибо за такой ответ! Это больше похоже на NSAttributedString в iOS :) Чтобы быть еще более гибким, замените text.lenght на text2.indexOf (CepVizyon.getPhoneCode ()), что позволит вам не знать первую часть строки.
iGranDav 02
1
Вы должны поставить ()после , text.lengthкак lengthэто метод не является полем. Сделал бы это сам, но правка должна состоять не менее чем из 6 символов :)
MSX
На сегодняшний день это лучший ответ.
Пау Арландис Мартинес,
1
Проблема с Spannable в том, что ellipsize = end больше не работает. В некоторых случаях это довольно серьезная проблема.
Хуан Карлос Оспина Гонсалес
1
Работает нормально. Хотя создание строки HTML желательно. А затем разобрать его через класс HTML. Html.fromHtml(R.id.your_html_string);
sud007 07
73
myTextView.setText(Html.fromHtml(text + "<font color=white>" + CepVizyon.getPhoneCode() + "</font><br><br>"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText()));
Маниш
источник
63

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

<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"/>

результат

введите описание изображения здесь

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

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

Fonix
источник
3
Я успешно протестировал использование <font fgcolor=...API 15 и API 25 (хотя я специально не тестировал 19)
hair raisin
Не работает, когда я задаю текст программно. :(
Рохит Сингх
Это не идеальное решение, потому что переводы смешиваются с цветами текста.
Милош Черниловский,
16

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

myTextView.setText(Html.fromHtml(text + "<font color=\"#FFFFFF\">" + CepVizyon.getPhoneCode() + "</font><br><br>"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText()));
Джо Лаллуз
источник
8

Мне это хорошо!

            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);
Ань Дуй
источник
6

Вот 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;
}
Джонни Ламбада
источник
6

Вот решение в Kotlin, которое используется SpannableStringдля изменения цвета части строки.

    val phoneCodeColor = ContextCompat.getColor(this, R.color.myColor)
    val text = SpannableStringBuilder()
        .color(phoneCodeColor) { append("${ CepVizyon.getPhoneCode() }") }
        .append("\n\n")
        .append(getString(R.string.currentversion))
        .append(${ CepVizyon.getLicenseText() })

    activationText.text = text
    myTextView.text = text
Дмитрий Леонов
источник
1
Спасибо. Просто элегантное решение для Котлина.
Нхон Нгуен,
4

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

Идея заключалась в следующем:

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

Вот пошаговый процесс:

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

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

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

  • Допустимое имя тега, например, <a> <a > <a-a> <a ..attrs..>но не< a> <1>
  • Закрывающий тег с таким же именем, как, <a></a>но не<a></b>
  • Любой контент, так как не нужно стилизовать «ничего»

Теперь что касается атрибутов, которые мы собираемся использовать ...

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

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

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

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и бежать "с\ .

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

Explisam
источник
4

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

Тинчо825
источник
4

Я сделал эту небольшую функцию, просто передайте ваш текст в цвет, начальный и конечный индексы того, что вы хотите раскрасить для этого текста, и сам цвет

Котлин

   private fun colorMyText(inputText:String,startIndex:Int,endIndex:Int,textColor:Int):Spannable{
            val outPutColoredText: Spannable = SpannableString(inputText)
            outPutColoredText.setSpan(
                ForegroundColorSpan(textColor), startIndex, endIndex,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            return outPutColoredText
        }

использование

txt_comment.text = colorMyText("Comentario: ${item.comentario}",0,13,Color.BLACK)
Gastón Saillén
источник
2

С функцией расширения Kotlin общего назначения это выглядело бы так:

/**
 * Change the color of a part of the text contained in this textView
 *
 * @param subStringToColorize has to already be set in the textView's text
 * @param colorResId
 */
fun TextView.colorize(subStringToColorize: String, @ColorRes colorResId: Int) {

  val spannable: Spannable = SpannableString(text)

  val startIndex = text.indexOf(subStringToColorize, startIndex = 0, ignoreCase = false)
  val endIndex = startIndex + subStringToColorize.length

  val color: Int = ContextCompat.getColor(context, colorResId)

  if (startIndex != -1) {
      spannable.setSpan(ForegroundColorSpan(color),
          startIndex,
          endIndex,
          Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
      setText(spannable, TextView.BufferType.SPANNABLE)
   }
}
Алехандро Х. Крус
источник
1

Использовать escape-символы + Html.fromHtml ()

введите описание изображения здесь

Как сохранить строку в папке строковых ресурсов

<string name="textFromRes">
    &lt;font color="#FF0000">This is colored in red &lt;/font> This is not
</string>

Как показать в TextView?

String text = this.getResources().getString(R.string.textFromRes);
htmlText.setText(Html.fromHtml(text));

Бонус:

Строка на выходе выглядит так

<string name="textFromRes">
    &lt;font color="#FF0000">This is colored in red &lt;/font> This is not
    &lt;br /&gt;
    &lt;h1> This is h1 heading &lt;/h1>
    &lt;br /&gt;
    &lt;h3> This is h2 subheading&lt;/h3>
    &lt;br /&gt;
    &lt;b> This text is bold&lt;/b>
    &lt;br /&gt;
    &lt;i> This text is italic&lt;/i>
    &lt;br /&gt;
    Android users expect your app to look and behave in a way that is
    consistent with the platform. Not only should you follow material
    design guidelines for visual and navigation patterns,
    but you should also follow quality guidelines for compatibility,
    performance, security, and more.
    &lt;br /&gt;
    &lt;br /&gt;
    The following links provide everything you need to design a high quality Android app.
</string>
Рохит Сингх
источник
0

Вдохновленный ответом Алехандро Х. Круза выше .

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

fun TextView.colorizeAll(subStringToColorize: String, @ColorRes colorResId: Int) {

    val color: Int = ContextCompat.getColor(context, colorResId)

    val spannable: Spannable = SpannableString(text)

    val pattern = subStringToColorize.toRegex()

    val matches = pattern.findAll(text, 0)

    matches.forEach { match ->

        val startIndex = match.range.first

        val endIndex = match.range.last + match.range.step

        spannable.setSpan(ForegroundColorSpan(color),
                startIndex,
                endIndex,
                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
        setText(spannable, TextView.BufferType.SPANNABLE)

    }
}
eppe
источник
-5

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

Ралки
источник
7
Нет, заноза в заднице. Использование spannable - правильный путь.
Marc DiMillo
Spannable class может сделать это без разделения
Sz-Nika Janos