Jak mogę zmienić kolor części TextView?

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

Chcę zmienić kolor CepVizyon.getPhoneCode()sznurka. W jaki sposób mogę to zrobić?

atasoyh
źródło
Możliwy duplikat ustawionego koloru zakresu
TextView
To pytanie zostało zadane 19 lipca 2010 o 16:27, około 3 miesiące przed twoim. Jednak nie zawsze najstarszy post musi być zduplikowanym celem. Należy wziąć pod uwagę liczbę odsłon, liczbę głosów, liczbę odpowiedzi oraz jasność pytania. Oznaczając to jako duplikat, może to pomóc innym znaleźć odpowiedzi, które również odpowiadają na Twoje pytanie.
Suragch
Sprawdź ten stackoverflow.com/a/57089362/6667442
Ketan Ramani,
Aby naprawdę zrozumieć, co kryje się za kulisami, zawsze sugeruję przeczytanie dogłębnego artykułu, takiego jak ten: medium.com/androiddevelopers/ ...
Michał Vician

Odpowiedzi:

171

Spannable jest bardziej elastyczny:

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);
andy boot
źródło
3
Dziękuję za tę odpowiedź! Jest to bardziej podobne do NSAttributedString w iOS :) Aby być jeszcze bardziej elastycznym, zamień text.lenght na text2.indexOf (CepVizyon.getPhoneCode ()), co pozwoli ci nie znać pierwszej części łańcucha.
iGranDav
1
Należy umieścić ()po text.lengthco lengthto nie metoda polu. Zrobiłbym to sam, ale zmiany muszą mieć co najmniej 6 znaków :)
MSX
To zdecydowanie najlepsza odpowiedź.
Pau Arlandis Martinez
1
Problem ze Spannable polega na tym, że ellipsize = end już nie działa. Co w niektórych przypadkach jest dość poważnym problemem.
Juan Carlos Ospina Gonzalez
1
Działa dobrze. Chociaż tworzenie ciągu HTML jest zalecane. A następnie parsowanie za pomocą klasy HTML. Html.fromHtml(R.id.your_html_string);
sud007
73
myTextView.setText(Html.fromHtml(text + "<font color=white>" + CepVizyon.getPhoneCode() + "</font><br><br>"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText()));
Maneesh
źródło
63

Jeśli masz tekst statyczny, który wymaga koloru, możesz dodać go bez żadnego kodu za pośrednictwem pliku ciągów:

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

następnie

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

wynik

wprowadź opis obrazu tutaj

nie jestem pewien, w których wersjach API to działa, ale nie działa dla API 19, które do tej pory testowałem, więc prawdopodobnie tylko niektóre z najnowszych wersji API obsługują to

edycja: jak @hairraisin wspomniano w komentarzach, spróbuj użyć fgcolorzamiast colorkoloru czcionki, wtedy powinno działać na niższych poziomach API, ale dla pewności potrzeba więcej testów

Fonix
źródło
3
Z powodzeniem przetestowałem przy użyciu <font fgcolor=...API 15 i API 25 (nie testowałem jednak specjalnie 19)
rodzynki do włosów
Nie działa, gdy ustawiam tekst programowo. :(
Rohit Singh
Nie jest to idealne rozwiązanie, ponieważ łączy tłumaczenia z kolorami tekstu.
Miloš Černilovský
16

Jeśli chodzi o odpowiedź Maneesha, to zadziała, ale musisz dodać cudzysłowy do atrybutu koloru i uciec od nich.

myTextView.setText(Html.fromHtml(text + "<font color=\"#FFFFFF\">" + CepVizyon.getPhoneCode() + "</font><br><br>"
            + getText(R.string.currentversion) + CepVizyon.getLicenseText()));
JoeLallouz
źródło
8

To jest dla mnie dobre!

            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);
Anh Duy
źródło
6

Oto colorizefunkcja oparta na odpowiedzi 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;
}
JohnnyLambada
źródło
6

Tutaj rozwiązanie w Kotlinie, które używa SpannableStringdo zmiany koloru części sznurka.

    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
Dmitrii Leonov
źródło
1
Dziękuję Ci. Po prostu eleganckie rozwiązanie dla Kotlin.
Nhon Nguyen
4

Nie podobał mi się pomysł robienia tego za pomocą kodu za każdym razem, gdy chcę pokolorować części tekstu, które często robiłem we wszystkich moich aplikacjach (a ponieważ w niektórych przypadkach tekst jest ustawiany w czasie wykonywania za pomocą różnych elementów w wierszu) zdefiniowane kolory), więc stworzyłem własne MarkableTextView.

Pomysł polegał na:

  • Wykryj tagi XML z ciągu
  • Zidentyfikuj i dopasuj nazwę tagu
  • Wyodrębnij i zapisz atrybuty i położenie tekstu
  • Usuń tag i zachowaj zawartość
  • Przejrzyj atrybuty i zastosuj style

Oto proces krok po kroku:

Najpierw potrzebowałem sposobu na znalezienie tagów XML w podanym ciągu i Regexzałatwiłem sprawę.

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

Aby powyższe było zgodne ze znacznikiem XML, musi mieć następujące kryteria:

  • Prawidłowa nazwa tagu, taka jak, <a> <a > <a-a> <a ..attrs..>ale nie< a> <1>
  • Tag zamykający, który ma taką samą nazwę jak, <a></a>ale nie<a></b>
  • Dowolna treść, ponieważ nie ma potrzeby dodawania stylu „nic”

Teraz dla atrybutów, których użyjemy tego ...

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

Ma tę samą koncepcję i generalnie nie musiałem iść daleko w obu przypadkach, ponieważ kompilator zajmie się resztą, jeśli coś wyjdzie poza format.

Teraz potrzebujemy klasy, która może przechowywać wyodrębnione dane:

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;
    }
}

Przede wszystkim dodamy ten fajny iterator, którego używam od dawna do zapętlania dopasowań (nie pamiętam autora) :

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;
    }

Na koniec stylizacja, więc oto bardzo prosta stylizacja, którą stworzyłem dla tej odpowiedzi:

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);
            }
        }
    }

A tak wygląda Markableklasa zawierająca definicje:

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";
    }
}

Wszystko, czego teraz potrzebujemy, to odwołać się do ciągu i zasadniczo powinien wyglądać tak:

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

Pamiętaj, aby owinąć tagi znakiem CDATA Sectioni uciekać za "pomocą \.

Stworzyłem to jako rozwiązanie modułowe do przetwarzania części tekstu na różne sposoby bez potrzeby wrzucania niepotrzebnego kodu.

Explisam
źródło
4

Zrobiłem tak, jak powiedział Andy Boot, ale miałem również klikalny zakres i nie zadziałało, ponieważ kolejność setSpanszostała wywołana. Musisz więc najpierw wywołać, a spannable.setSpan(clickableSpanand...następnie the, spannable.setSpan(new ForegroundColorSpan...aby uzyskać kolor w TextView

Tincho825
źródło
4

Zrobiłem tę małą funkcję, po prostu przekaż swój tekst do koloru, indeksy początku i końca tego, co chcesz pokolorować tego tekstu i sam kolor

Kotlin

   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
        }

Stosowanie

txt_comment.text = colorMyText("Comentario: ${item.comentario}",0,13,Color.BLACK)
Gastón Saillén
źródło
2

Z funkcją rozszerzenia Kotlin ogólnego przeznaczenia wyglądałoby to tak:

/**
 * 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)
   }
}
Alejandro H. Cruz
źródło
1

Użyj znaków ucieczki + Html.fromHtml ()

wprowadź opis obrazu tutaj

Jak przechowywać ciąg w folderze zasobów ciągu

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

Jak pokazać w TextView?

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

Premia:

String w danych wyjściowych wygląda następująco

<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>
Rohit Singh
źródło
0

Zainspirowany powyższą odpowiedzią Alejandro H. Cruz .

Jego funkcja działa tylko dla pojedynczego dopasowania podłańcucha, zaktualizowałem jego metodę, aby używała Regex i powinienem zaktualizować kolory we wszystkich dopasowaniach:

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
źródło
-5

Jednym ze sposobów jest podzielenie myTextViewna kilka oddzielnych TextViews, z których jeden byłby przeznaczony tylko na kod telefonu. Wtedy kontrolowanie koloru tego konkretnego TextViewjest całkiem proste.

Ralkie
źródło
7
Nie, ból w dupie. Używanie spannable to właściwy sposób.
Marc DiMillo
Spannable klasa może to zrobić bez dzielenia się
Sz-Nika Janos