Czy można mieć wiele stylów w TextView?


547

Czy można ustawić wiele stylów dla różnych fragmentów tekstu w TextView?

Na przykład ustawiam tekst w następujący sposób:

tv.setText(line1 + "\n" + line2 + "\n" + word1 + "\t" + word2 + "\t" + word3);

Czy można mieć inny styl dla każdego elementu tekstowego? Np. Wiersz 1 pogrubiony, słowo 1 kursywą itp.

Podręczniku programisty za Typowe zadania i jak je zrobić w Android zawiera wybieranie Podświetlanie lub Styling fragmentów tekstu :

// Get our EditText object.
EditText vw = (EditText)findViewById(R.id.text);

// Set the EditText's text.
vw.setText("Italic, highlighted, bold.");

// If this were just a TextView, we could do:
// vw.setText("Italic, highlighted, bold.", TextView.BufferType.SPANNABLE);
// to force it to use Spannable storage so styles can be attached.
// Or we could specify that in the XML.

// Get the EditText's internal text storage
Spannable str = vw.getText();

// Create our span sections, and assign a format to each.
str.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
str.setSpan(new BackgroundColorSpan(0xFFFFFF00), 8, 19, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
str.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 21, str.length() - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

Ale to wykorzystuje wyraźne numery pozycji w tekście. Czy jest na to czystszy sposób?


6
Jeśli ciąg TextView jest statyczny, możesz po prostu dodać tagi html <b>, <i> i <u> do pliku zasobów ciągów i zostaną one automatycznie zastosowane. Np. <TextView android: text = "@ string / test" /> gdzie @ string / test jest ustawiony na <ciąg> <b> pogrubienie </b>, <i>italic</i> </string>
greg7gkb

2
+1 @ greg7gkb! Kluczowym słowem jest „statyczny”. Wyciągałam włosy, zastanawiając się, dlaczego niektóre moje sznurki pracowały z <b>, a niektóre nie. Te, które nie miały w sobie zmiennych.
Scott Biggs,

Odpowiedzi:


694

W przypadku, gdy ktoś zastanawia się, jak to zrobić, oto jeden ze sposobów: (Jeszcze raz dziękuję Markowi!)

mBox = new TextView(context);
mBox.setText(Html.fromHtml("<b>" + title + "</b>" +  "<br />" + 
            "<small>" + description + "</small>" + "<br />" + 
            "<small>" + DateAdded + "</small>"));

Aby uzyskać nieoficjalną listę tagów obsługiwanych przez tę metodę, zapoznaj się z tym linkiem lub pytaniem: Które znaczniki HTML są obsługiwane przez Android TextView?


1
@ JānisGruzis: Być może jednym ze sposobów na to jest za pomocą metody skrótowej, powiedzmy, formatTextWhite(string text)że po prostu wstawia tekst w następującym formacie ciąg: "<font size="..." color="..." face="...">%s</font>".
Legenda,

@ Legend użyłem <font fgcolor = '# ffff5400'> <b> <big> "+" Title "+" </big> </b> </font>, ale fgcolor / color (oba wypróbowane) nie jest działa ... czy wiesz, jak zrobić kolor za pomocą HTML
Muhammad Babar,

@MuhammadBabar: można spróbować tak: Html.fromHtml("<![CDATA[<font color='#ffff5400'>the html content you already have</font>]]>");? Pamiętam, że kiedyś to zadziałało. Nie jestem pewien, czy nadal działa.
Legenda,

1
@Legend dzięki, ale udało się obejść to Html.fromHtml("<font color=\"#999999\">sekwencje ucieczki zadziałały dla mnie :) ...
Muhammad Babar

2
dlaczego to nie działa mBox..setText(Html.fromHtml("<b>" + name + "</b>") + doNotApplyHTML);żaden pomysł dzięki
Shan Xeeshi

216

Spróbuj Html.fromHtml()i oznacz swój tekst pogrubionymi i kursywnymi znacznikami HTML, np .:

Spanned text = Html.fromHtml("This mixes <b>bold</b> and <i>italic</i> stuff");
textView.setText(text);

1
Właściwie to prowadzi mnie do kolejnego pytania: czy lepiej jest mieć jeden widok tekstu z tekstem HTML lub trzy widoki tekstu z różnymi ustawieniami znaczników bez użycia klasy HTML? Zakładam, że jest to oczywiście pierwszy, ale chciałem tylko to potwierdzić ...
Legend z

2
Nie mam pojęcia, co obsługuje pełna lista tagów obsługiwana przez Html.fromHtml () - trzeba by było zajrzeć do kodu źródłowego. Wbudowane znaczniki i wiele widżetów TextView powinny być decyzjami ortogonalnymi. Użyj wielu widżetów, jeśli potrzebujesz precyzyjnego umieszczenia dyskretnych fragmentów tekstu. Użyj wbudowanego znacznika, jeśli potrzebujesz wbudowanego znacznika w widżecie. Pamiętaj: w systemie Android nie ma FlowLayout, więc łączenie wielu TextView w celu utworzenia akapitu nie jest naprawdę praktycznym AFAIK.
CommonsWare

1
Dzięki za to ... Właściwie tag <small> działał ... Więc postaram się, żeby był prosty i po prostu go używał ...
Legend

1
@TimBoland: Wyrzucacie swoje rozpiętości przez monthText + " " + month.getYearLabel(). Zobaczyć StringUtils.concat().
CommonsWare

2
@ TimBoland: Możesz zrobić to wszystko w jednym: monthYearLabel.setText(Html.fromHtml("<b>"+month.getMonthLabel().toUpperCase()+"</b>&nbsp;"+month.getYearLabel()))jeśli nie potrzebujesz poszczególnych elementów.
CommonsWare

191

Nieco nie na temat, ale uznałem, że jest to zbyt przydatne, aby nie być tutaj wymienione.

Co jeśli chcielibyśmy przeczytać tekst HTML z zasobu string.xml, a tym samym ułatwić lokalizację. CDATA umożliwia to:

<string name="my_text">
  <![CDATA[
    <b>Autor:</b> Mr Nice Guy<br/>
    <b>Contact:</b> myemail@grail.com<br/>
    <i>Copyright © 2011-2012 Intergalactic Spacebar Confederation </i>
  ]]>
</string> 

Z naszego kodu Java możemy teraz go wykorzystać w następujący sposób:

TextView tv = (TextView) findViewById(R.id.myTextView);
tv.setText(Html.fromHtml(getString(R.string.my_text))); 

Nie spodziewałem się, że to zadziała. Ale tak się stało.

Mam nadzieję, że jest to przydatne dla niektórych z was!


2
Zwykle „fałsz” oznacza, że ​​uzyskałeś dostęp do dziwnej wartości w tabeli odnośników. Dostałem to, gdy miałem R.id.ok i R.string.ok, i przypadkowo użyłem getString (R.id.ok) zamiast prawidłowego getString (R.string.ok)
Joe Plante

Czy to działa również w układzie XML? Kiedy odnoszę się do zasobu ciągów za pomocą "@string/someText"(gdzie „someText” jest zasobem zdefiniowanym w strings.xml), po prostu otrzymuję ciąg ze wszystkimi znacznikami HTML jako „tekst”.
Gee.E,

3
To była najlepsza odpowiedź. Zainspirowała mnie. Nie mogłem nic poradzić, ale postawiłem też moje dwa centy, na które warto odpowiedzieć. Rozwiązanie tutaj gist.github.com/aegis1980/b138dcb2fd1b2e98aa30 umożliwia przypisanie pliku układu, dzięki czemu nie trzeba przypisywać zasobu tekstowego programowo (i można wyświetlić podgląd w Android Studio!)
Jon

126

Jeśli nie masz ochoty używać HTML, możesz po prostu utworzyć styles.xml i użyć go w następujący sposób:

TextView tv = (TextView) findViewById(R.id.textview);
SpannableString text = new SpannableString(myString);

text.setSpan(new TextAppearanceSpan(getContext(), R.style.myStyle), 0, 5, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(new TextAppearanceSpan(getContext(), R.style.myNextStyle), 6, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

tv.setText(text, TextView.BufferType.SPANNABLE);

skopiuj / wklej go do prostej aplikacji na Androida, nad którą prawdopodobnie pracujesz. Zobaczysz, że w jednym widoku tekstu zastosowane zostaną dwa style.
Kent Andersen,

przepraszam, nie było jasne. jeśli musisz określić podciąg (za pomocą dwóch indeksów) miejsca, w którym chcesz zastosować styl, nie działa to dla zlokalizowanych ciągów, ponieważ indeksy będą oczywiście różne dla każdego ustawienia narodowego.
Jeffrey Blattman

Masz rację. Jeśli obsługujesz wiele języków, nie byłaby to droga, którą chciałbyś wybrać. Chyba że masz pewność, że łańcuch nie zmieni rozmiarów ... takich jak na przykład nazwa aplikacji.
Kent Andersen

9
Nie rozumiem, dlaczego l10n nie może współpracować z tym rozwiązaniem. Po prostu pobierz osobne ciągi zamiast jednego: String firstPart = getString (R.string.line1); Ciąg secondPart = getString (R.string.line2); Spannable text = new SpannableString (firstPart + secondPart); text.setSpan (nowy ForegroundColorSpan (Color.parseColor (TEXT_COLOR_FOR_FIRST_PART)), 0, firstPart.length (), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); myTextView.setText (tekst, TextView.BufferType.SPANNABLE);
Rui

5
@Rui, ponieważ l10n również jest pozycyjny. nie możesz zakodować na stałe pozycji słów w wyrażeniu i nadal być 1010n. dlatego widzisz takie rzeczy jak $1%s is a happy $2%sw wiązkach strun. musisz zezwolić na zmianę kolejności tokenów.
Jeffrey Blattman

60

Bardziej lekkie jest stosowanie SpannableStringzamiast znaczników HTML. Pomaga mi to zobaczyć przykłady wizualne, więc oto dodatkowa odpowiedź.

wprowadź opis zdjęcia tutaj

To jest singiel TextView.

// set the text
SpannableString s1 = new SpannableString("bold\n");
SpannableString s2 = new SpannableString("italic\n");
SpannableString s3 = new SpannableString("foreground color\n");
SpannableString s4 = new SpannableString("background color\n");
SpannableString s5 = new SpannableString("underline\n");
SpannableString s6 = new SpannableString("strikethrough\n");
SpannableString s7 = new SpannableString("bigger\n");
SpannableString s8 = new SpannableString("smaller\n");
SpannableString s9 = new SpannableString("font\n");
SpannableString s10 = new SpannableString("URL span\n");
SpannableString s11 = new SpannableString("clickable span\n");
SpannableString s12 = new SpannableString("overlapping spans\n");

// set the style
int flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
s1.setSpan(new StyleSpan(Typeface.BOLD), 0, s1.length(), flag);
s2.setSpan(new StyleSpan(Typeface.ITALIC), 0, s2.length(), flag);
s3.setSpan(new ForegroundColorSpan(Color.RED), 0, s3.length(), flag);
s4.setSpan(new BackgroundColorSpan(Color.YELLOW), 0, s4.length(), flag);
s5.setSpan(new UnderlineSpan(), 0, s5.length(), flag);
s6.setSpan(new StrikethroughSpan(), 0, s6.length(), flag);
s7.setSpan(new RelativeSizeSpan(2), 0, s7.length(), flag);
s8.setSpan(new RelativeSizeSpan(0.5f), 0, s8.length(), flag);
s9.setSpan(new TypefaceSpan("monospace"), 0, s9.length(), flag);
s10.setSpan(new URLSpan("https://developer.android.com"), 0, s10.length(), flag);
s11.setSpan(new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Toast.makeText(getApplicationContext(), "Span clicked", Toast.LENGTH_SHORT).show();
    }
}, 0, s11.length(), flag);
s12.setSpan(new ForegroundColorSpan(Color.RED), 0, 11, flag);
s12.setSpan(new BackgroundColorSpan(Color.YELLOW), 4, s12.length(), flag);
s12.setSpan(new UnderlineSpan(), 4, 11, flag);

// build the string
SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(s1);
builder.append(s2);
builder.append(s3);
builder.append(s4);
builder.append(s5);
builder.append(s6);
builder.append(s7);
builder.append(s8);
builder.append(s9);
builder.append(s10);
builder.append(s11);
builder.append(s12);

// set the text view with the styled text
textView.setText(builder);
// enables clicking on spans for clickable span and url span
textView.setMovementMethod(LinkMovementMethod.getInstance());

Dalsze badanie

Ten przykład został pierwotnie zainspirowany stąd .


5
Twój post jest lepszy niż Dokumentacja Google na temat SpannableString. Dziękuję
MBH

Czy to obsługuje obrazy, które z serwisu internetowego. Takie moje wymagania.
MohanRaj S

@MohanRajS SpannableStrings obsługuje emoji Unicode, ale nie inne obrazy.
Suragch

@ Suragch dziękuję za szybką odpowiedź, ale moim wymaganiem dodatkowym jest jpg, png. Jedyny wybór do obsługi to przeglądanie stron internetowych? :(
MohanRaj S

43

Lista obsługiwanych tagów to:

Jeśli korzystasz z zasobu ciągów, możesz dodać prostą stylizację, taką jak pogrubienie lub kursywa, za pomocą notacji HTML. Aktualnie obsługiwane znaczniki są: B(pogrubiony), I(kursywa), U(podkreślony) TT(o stałej szerokości) BIG, SMALL, SUP(górny) SUB(dolny) i STRIKE(Przekreślenia). Na przykład res/values/strings.xmlmożesz w tym zadeklarować:

<resource>
    <string id="@+id/styled_welcome_message">We are <b><i>so</i></b> glad to see you.</string>
</resources>

(Od http://developer.android.com/guide/faq/commontasks.html#selectingtext - link do archiwum internetowego, <resource>literówka jest w oryginale!)

Pokazuje również, że Html.fromHtmltak naprawdę nie jest to potrzebne w prostych przypadkach.


Html.fromHtml jest często łatwiejszym sposobem stylizowania tekstu. Link ten znajduje się już w pierwotnym pytaniu.
Zawiłości

17

Miałem taki sam problem. Mógłbym używać fromHtml, ale teraz jestem androidem, nie internetem, więc postanowiłem to wypróbować. Muszę to jednak zlokalizować, więc spróbowałem, używając koncepcji zamiany łańcucha. Ustawiłem styl na TextView jako główny styl, a następnie po prostu sformatowałem pozostałe wycinki.

Mam nadzieję, że to pomoże innym, którzy chcą zrobić to samo - nie wiem, dlaczego nie jest to łatwiejsze w ramach.

Moje ciągi wyglądają tak:


<string name="my_text">{0} You will need a {1} to complete this assembly</string>
<string name="text_sub0">1:</string>
<string name="text_sub1">screwdriver, hammer, and measuring tape</string>

Oto style:


<style name="MainStyle">
    <item name="android:textSize">@dimen/regular_text</item>
    <item name="android:textColor">@color/regular_text</item>
</style>
<style name="style0">
    <item name="android:textSize">@dimen/paragraph_bullet</item>
    <item name="android:textColor">@color/standout_text</item>
    <item name="android:textStyle">bold</item>
</style>
<style name="style1">
    <item name="android:textColor">@color/standout_light_text</item>
    <item name="android:textStyle">italic</item>
</style>

Oto mój kod, który wywołuje moją metodę formatStyles:


SpannableString formattedSpan = formatStyles(getString(R.string.my_text), getString(R.string.text_sub0), R.style.style0, getString(R.string.main_text_sub1), R.style.style1);
textView.setText(formattedSpan, TextView.BufferType.SPANNABLE);

Metoda formatu:


private SpannableString formatStyles(String value, String sub0, int style0, String sub1, int style1)
{
    String tag0 = "{0}";
    int startLocation0 = value.indexOf(tag0);
    value = value.replace(tag0, sub0);

    String tag1 = "{1}";
    int startLocation1 = value.indexOf(tag1);
    if (sub1 != null && !sub1.equals(""))
    {
        value = value.replace(tag1, sub1);
    }

    SpannableString styledText = new SpannableString(value);
    styledText.setSpan(new TextAppearanceSpan(getActivity(), style0), startLocation0, startLocation0 + sub0.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    if (sub1 != null && !sub1.equals(""))
    {
        styledText.setSpan(new TextAppearanceSpan(getActivity(), style1), startLocation1, startLocation1 + sub1.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    return styledText;
}

Nie sądzę, aby to zadziałało, jeśli zlokalizowana wersja ciągu znaków zmieni kolejność {0} i {1}. Być może będziesz musiał zmienić startLocation0, jeśli startLocation1 to <startLocation0
JonathanC


8

Oto prosty sposób, aby to zrobić za pomocą HTMLBuilder

    myTextView.setText(new HtmlBuilder().
                    open(HtmlBuilder.Type.BOLD).
                    append("Some bold text ").
                    close(HtmlBuilder.Type.BOLD).
                    open(HtmlBuilder.Type.ITALIC).
                    append("Some italic text").
                    close(HtmlBuilder.Type.ITALIC).
                    build()
    );

Wynik:

Trochę pogrubionego tekstu Trochę kursywa


7

Jeśli chcesz mieć możliwość dodania tekstu w formacie xml, możesz utworzyć własny widok rozszerzający TextView i przesłonić setText ():

public class HTMLStyledTextView extends TextView
{
    public HTMLStyledTextView(Context context) {
        super(context);
    }

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

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

    @Override
    public void setText(CharSequence text, BufferType type)
    {
       super.setText(Html.fromHtml(text.toString()), type);
    }
}

Następnie możesz użyć go w następujący sposób (zastąp PACKAGE_NAMEnazwą pakietu):

<PACKAGE_NAME.HTMLStyledTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="<![CDATA[
        <b>Bolded Text:</b> Non-Bolded Text
    ]]>"
/>

Czy wyświetla się prawidłowo w Android Studio?
JPM


3

Ja też

Co powiesz na użycie pięknego znacznika z Kotlin i Anko -

import org.jetbrains.anko.*
override fun onCreate(savedInstanceState: Bundle?) {
    title = "Created with Beautiful Markup"
    super.onCreate(savedInstanceState)

    verticalLayout {
        editText {
            hint = buildSpanned {
                append("Italic, ", Italic)
                append("highlighted", backgroundColor(0xFFFFFF00.toInt()))
                append(", Bold", Bold)
            }
        }
    }
}

Utworzono za pomocą Beautiful Markup


2

Tak, możliwe jest użycie SpannedString. Jeśli używasz Kotlina, staje się to jeszcze łatwiejsze core-ktx, ponieważ zapewnia on język specyficzny dla domeny (DSL) do tego:

    val string: SpannedString = buildSpannedString {
        bold {
            append("1111")
        }
        append("Devansh")     
    }

Więcej dostępnych opcji to:

append("Hello There")
bold {
    append("bold")
    italic {
        append("bold and italic")
        underline {
            append("then some text with underline")
        }
    }
}

W końcu możesz po prostu:

textView.text = string

0

W rzeczywistości, oprócz obiektu Html, można również użyć klas typu Spannable, np. TextAppearanceSpan lub TypefaceSpan i SpannableString. Klasa HTML również korzysta z tych mechanizmów. Ale dzięki klasom typu Spannable masz więcej swobody.


0

Może to być tak proste, jak wykorzystanie metody String's length ():

  1. Podziel ciąg tekstowy w pliku XML Ciągi na tyle ciągów podrzędnych (osobne ciągi z punktu widzenia Androida), ile potrzebujesz różnych stylów, więc może to być: str1, str2, str3 (jak w twoim przypadku), które połączone razem stanowią cały pojedynczy ciąg znaków, którego używasz.

  2. A następnie po prostu zastosuj metodę „Span”, tak jak przedstawiono to w kodzie - ale zamiast jednego łańcucha połącz wszystkie podciągi, łącząc je w jeden, każdy w innym niestandardowym stylu.

Nadal używasz liczb, ale nie bezpośrednio - nie są one teraz w formie zakodowanej (jak w kodzie), ale zastępuje się je metodami o długości () (zauważ dwie gwiazdki poprzedzające i sufiksem str. length () zamiast liczby bezwzględnej w celu wyjaśnienia zmiany):

str.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, **str.length()**, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

dla pierwszego rozmiaru łańcucha, a następnie str. długość () + 1, str. długość () + str2. długość () dla drugiego rozmiaru łańcucha i tak dalej ze wszystkimi podciągami, zamiast np. 0,7 lub 8,19 i tak dalej...


0

Używanie pomocniczej klasy Spannable jako zasobów ciągów Androida na dole strony. Możesz do tego podejść, tworząc CharSquencesi nadając im styl.

Ale w podanym przez nas przykładzie jest tylko pogrubienie, pochylenie, a nawet pokolorowanie tekstu. Musiałem owinąć kilka stylów CharSequencew, aby ustawić je w TextView. Więc do tej klasy (nazwałem ją CharSequenceStyles) właśnie dodałem tę funkcję.

public static CharSequence applyGroup(LinkedList<CharSequence> content){
    SpannableStringBuilder text = new SpannableStringBuilder();
    for (CharSequence item : content) {
        text.append(item);
    }
    return text;
}

I w widoku dodałem to.

            message.push(postMessageText);
            message.push(limitDebtAmount);
            message.push(pretMessageText);
            TextView.setText(CharSequenceStyles.applyGroup(message));

Mam nadzieję, że ci to pomoże!


0

Spanny sprawia, że ​​SpannableString jest łatwiejszy w użyciu.

Spanny spanny = new Spanny("Underline text", new UnderlineSpan())
                .append("\nRed text", new ForegroundColorSpan(Color.RED))
                .append("\nPlain text");
textView.setText(spanny)

0

Jak powiedział Jon , jest to dla mnie najlepsze rozwiązanie i nie musisz ustawiać żadnego tekstu w czasie wykonywania, użyj tylko tej niestandardowej klasy HtmlTextView

public class HtmlTextView extends TextView {

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

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

  public HtmlTextView(Context context, AttributeSet attrs, int defStyleAttr) 
  {
      super(context, attrs, defStyleAttr);
  }

  @TargetApi(21)
  public HtmlTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
      super(context, attrs, defStyleAttr, defStyleRes);
  }

  @Override
  public void setText(CharSequence s,BufferType b){
      super.setText(Html.fromHtml(s.toString()),b);
  }

}

i to wszystko, teraz umieść to tylko w swoim XML

<com.fitc.views.HtmlTextView
    android:id="@+id/html_TV"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/example_html" />

ze swoim ciągiem HTML

<string name="example_html">
<![CDATA[
<b>Author:</b> Mr Donuthead<br/>
<b>Contact:</b> me@donut.com<br/>
<i>Donuts for life </i>
]]>

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.