¿Es posible tener múltiples estilos dentro de un TextView?

547

¿Es posible establecer múltiples estilos para diferentes fragmentos de texto dentro de un TextView?

Por ejemplo, estoy configurando el texto de la siguiente manera:

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

¿Es posible tener un estilo diferente para cada elemento de texto? Por ejemplo, línea1 negrita, palabra1 cursiva, etc.

Las tareas comunes de la guía del desarrollador y cómo hacerlas en Android incluyen seleccionar, resaltar o diseñar partes de texto :

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

Pero eso usa números de posición explícitos dentro del texto. ¿Hay una forma más limpia de hacer esto?

Leyenda
fuente
66
Si la cadena TextView es estática, puede agregar etiquetas html <b>, <i> y <u> en el archivo de recursos de cadenas y se aplicarán automáticamente. Por ejemplo, <TextView android: text = "@ string / test" /> donde @ string / test se establece en <string> <b> negrita </b>, <i>italic</i> </string>
greg7gkb
2
+1 @ greg7gkb! La palabra clave es 'estática'. Me estaba arrancando el pelo preguntándome por qué algunas de mis cuerdas funcionaban con <b> y otras no. Los que no tenían variables en ellos.
Scott Biggs

Respuestas:

694

En caso de que alguien se pregunte cómo hacer esto, aquí hay una manera: (¡Gracias a Mark nuevamente!)

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

Para obtener una lista no oficial de etiquetas compatibles con este método, consulte este enlace o esta pregunta: ¿Qué etiquetas HTML son compatibles con Android TextView?

Leyenda
fuente
1
@ JānisGruzis: Tal vez una forma de hacer esto es a través de un método de rutas internas, por ejemplo, formatTextWhite(string text)que simplemente inserta el texto en la cadena siguiente formato: "<font size="..." color="..." face="...">%s</font>".
Leyenda
@Legend He usado <font fgcolor = '# ffff5400'> <b> <big> "+" Title "+" </big> </b> </font> pero el fgcolor / color (ambos intentados) no es trabajando ... ¿sabes cómo hacer el color usando html?
Muhammad Babar
@MuhammadBabar: ¿Puedes probar esto Html.fromHtml("<![CDATA[<font color='#ffff5400'>the html content you already have</font>]]>");? Recuerdo que esto me funcionó alguna vez. No estoy seguro si aún funciona.
Leyenda
1
@Legend gracias, pero una tiene una forma de evitar que Html.fromHtml("<font color=\"#999999\">las secuencias de escape trabajaron para mí:) ...
Muhammad Babar
2
por qué esto no funciona mBox..setText(Html.fromHtml("<b>" + name + "</b>") + doNotApplyHTML);ninguna idea gracias
Shan Xeeshi
216

Pruebe Html.fromHtml()y marque su texto con etiquetas HTML en negrita y cursiva, por ejemplo:

Spanned text = Html.fromHtml("This mixes <b>bold</b> and <i>italic</i> stuff");
textView.setText(text);
CommonsWare
fuente
1
En realidad, eso me lleva a otra pregunta: ¿es mejor tener una vista de texto con texto html dentro o tres vistas de texto con diferentes configuraciones de marcado sin usar la clase html? Estoy asumiendo que es obviamente el primero pero sólo quería confirmarlo ...
Leyenda
2
No tengo idea de cuál es la lista completa de etiquetas que admite Html.fromHtml (). Debería mirar el código fuente. El marcado en línea y múltiples widgets TextView deberían ser decisiones ortogonales. Utilice varios widgets si necesita una ubicación precisa de fragmentos de texto discretos. Utilice el marcado en línea si, um, necesita marcado en línea en un widget. Recuerde: no hay FlowLayout en Android, por lo que encadenar múltiples TextViews para crear un párrafo no es realmente práctico AFAIK.
CommonsWare
1
Gracias por eso ... En realidad, la etiqueta <small> funcionó ... Así que lo mantendré simple y solo lo usaré ...
Legend
1
@TimBoland: Estás tirando tus tramos a través de monthText + " " + month.getYearLabel(). Ver StringUtils.concat().
CommonsWare
2
@TimBoland: Podría hacerlo todo en uno: monthYearLabel.setText(Html.fromHtml("<b>"+month.getMonthLabel().toUpperCase()+"</b>&nbsp;"+month.getYearLabel()))si no necesita las piezas individuales.
CommonsWare
191

Ligeramente fuera de tema, pero encontré esto demasiado útil para no ser mencionado aquí.

¿Qué pasaría si quisiéramos leer el texto HTML del recurso string.xml y así facilitar la localización? CDATA hace esto posible:

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

Desde nuestro código Java ahora podríamos utilizarlo así:

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

No esperaba que esto funcionara. Pero lo hizo.

¡Espero que sea útil para algunos de ustedes!

Ben
fuente
2
Por lo general, "falso" implica que accedió a un valor extraño en la tabla de búsqueda. Obtuve esto cuando tuve R.id.ok y R.string.ok, y accidentalmente usé getString (R.id.ok) en lugar del getString correcto (R.string.ok)
Joe Plante
¿Funciona esto también en un diseño XML? Cuando me refiero al recurso de cadena allí con "@string/someText"(donde "someText" es un recurso definido en strings.xml), solo obtengo la cadena con todas las etiquetas HTML como 'texto'.
Gee.E
3
Encontré esta la mejor respuesta. Me inspiró. No pude evitar poner mis dos centavos por valor también. La solución aquí gist.github.com/aegis1980/b138dcb2fd1b2e98aa30 le permite asignar en el archivo de diseño para que no necesite asignar recursos de texto mediante programación (¡y puede previsualizar en Android Studio!)
Jon
127

Si no tiene ganas de usar html, puede crear un styles.xml y usarlo así:

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);
Kent Andersen
fuente
cópielo / péguelo en una aplicación simple de Android en la que podría estar trabajando. Verá que en una sola vista de texto se aplicarán dos estilos.
Kent Andersen
lo siento, no estaba claro. si tiene que especificar la subcadena (a través de los dos índices) de dónde aplicar el estilo, eso no funciona para las cadenas localizadas porque, por supuesto, los índices serán diferentes para cada configuración regional.
Jeffrey Blattman
Estás en lo correcto. Si admite varios idiomas, esta no sería la ruta que desea tomar. A menos que esté seguro de que la cadena no cambiará los tamaños ... como el nombre de la aplicación, por ejemplo.
Kent Andersen
99
No veo por qué l10n no puede funcionar con esta solución. Simplemente obtenga cadenas separadas en lugar de solo una: String firstPart = getString (R.string.line1); String secondPart = getString (R.string.line2); Texto de Spannable = new SpannableString (firstPart + secondPart); text.setSpan (new ForegroundColorSpan (Color.parseColor (TEXT_COLOR_FOR_FIRST_PART)), 0, firstPart.length (), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); myTextView.setText (text, TextView.BufferType.SPANNABLE);
Rui
55
@Rui porque l10n también es posicional. no puedes codificar la posición de las palabras en una frase y aún así ser l10n. Es por eso que ves cosas como $1%s is a happy $2%sen paquetes de cuerdas. tienes que permitir que las fichas se reorganicen.
Jeffrey Blattman
60

Es más ligero usar un SpannableStringmarcado en lugar de html. Me ayuda a ver ejemplos visuales, así que aquí hay una respuesta complementaria.

ingrese la descripción de la imagen aquí

Este es un sencillo 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());

Estudio adicional

Este ejemplo fue originalmente inspirado desde aquí .

Suragch
fuente
55
Su publicación es mejor que la documentación de Google sobre SpannableString .. Gracias
MBH
¿Esto es compatible con imágenes que provienen del servicio web? Mi requerimiento así.
MohanRaj S
@MohanRajS SpannableStrings admite emoji Unicode, pero no otras imágenes.
Suragch
@Suragch gracias por su pronta respuesta, pero mi requisito adicional es jpg, png. La única opción para manejar es webview? :(
MohanRaj S
43

La lista de etiquetas compatibles es:

Si usa un recurso de cadena, puede agregar un estilo simple, como negrita o cursiva usando notación HTML. Las etiquetas soportadas actualmente son: B(negrita), I(en cursiva), U(subrayado), TT(monovolumen), BIG, SMALL, SUP(superíndice), SUB(subíndice), y STRIKE(tachado). Entonces, por ejemplo, en res/values/strings.xmlusted podría declarar esto:

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

(De http://developer.android.com/guide/faq/commontasks.html#selectingtext - Enlace al archivo web, ¡el <resource>error tipográfico está en original!)

También muestra que Html.fromHtmlno es realmente necesario en casos simples.

Jon
fuente
Html.fromHtml es a menudo una forma más fácil de aplicar estilo al texto. Además, este enlace ya está en la pregunta original.
Intrincaciones
17

Me encontraba con el mismo problema. Podría usar fromHtml, pero ahora soy Android, no web, así que decidí probar esto. Sin embargo, tengo que localizar esto, así que lo probé usando el concepto de reemplazo de cadena. Establezco el estilo en TextView para que sea el estilo principal, luego simplemente formateo los otros pedazos.

Espero que esto ayude a otros que buscan hacer lo mismo. No sé por qué esto no es más fácil en el marco.

Mis cuerdas se ven así:


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

Aquí están los estilos:


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

Aquí está mi código que llama a mi método 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);

El método de formato:


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;
}
farcrats
fuente
No creo que esto funcione si una versión localizada de la cadena reordena {0} y {1}. Puede que tenga que cambiar startLocation0 si startLocation1 es <startLocation0
JonathanC
14

Ahora el <b>elemento está en desuso. <strong>se representa como <b>y se <em>representa como <i>.

tv.setText(Html.fromHtml("<strong>bold</strong> and <em>italic</em> "));

Esto funciona bien para mi

Sandeep P
fuente
8

Aquí hay una manera fácil de hacerlo usando 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()
    );

Resultado:

Algún texto en negrita Algún texto en cursiva

Ilya Gazman
fuente
7

Si desea poder agregar el texto con estilo en xml, puede crear una vista personalizada que extienda TextView y anule 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);
    }
}

Luego, puede usarlo así (reemplace PACKAGE_NAMEcon el nombre de su paquete):

<PACKAGE_NAME.HTMLStyledTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="<![CDATA[
        <b>Bolded Text:</b> Non-Bolded Text
    ]]>"
/>
bcorso
fuente
¿Vista previa en Android Studio entonces correctamente?
JPM
3

Yo también

¿Qué tal usar un marcado hermoso con Kotlin y 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)
            }
        }
    }
}

Creado con hermoso marcado

Himanshu
fuente
2

Sí, es posible usarlo SpannedString. Si está utilizando Kotlin, se vuelve aún más fácil hacerlo core-ktx, ya que proporciona un lenguaje de dominio específico (DSL) para hacer esto:

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

Más opciones proporcionadas por él son:

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

Por fin, solo puedes:

textView.text = string
Devansh Maurya
fuente
0

De hecho, excepto el objeto Html, también podría usar las clases de tipo Spannable, por ejemplo, TextAppearanceSpan o TypefaceSpan y SpannableString togather. La clase HTML también usa estos mecanismos. Pero con las clases de tipo Spannable, tienes más libertad.

Reloj ZHONG
fuente
0

Puede ser tan simple como aprovechar el método length () de String:

  1. Divida la cadena de texto en el archivo XML de cadenas en tantas subcadenas ( cadenas separadas desde el punto de vista de Android) como necesite diferentes estilos, por lo que podría ser como: str1, str2, str3 (como en su caso), que cuando se unen son toda la cadena que usas.

  2. Y luego simplemente siga el método "Span", tal como lo presentó con su código, pero en lugar de una sola cadena, combine todas las subcadenas fusionándolas en una sola, cada una con un estilo personalizado diferente.

Todavía usa los números, sin embargo, no directamente; ya no toman una forma codificada (como en su código) ahora, sino que se sustituyen con los métodos de longitud combinada () (observe dos estrellas que preceden y agregan el str). longitud () en lugar del número absoluto para eliminar el cambio):

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

para el primer tamaño de cadena, luego str.length () + 1, str.length () + str2.length () para el segundo tamaño de cadena, y así sucesivamente con todas las subcadenas, en lugar de, por ejemplo, 0,7 u 8,19 y así...

forsberg
fuente
0

El uso de una clase auxiliar de Spannable como recursos de cadena de Android se comparte en la parte inferior de la página web. Puede abordar esto creando CharSquencesy dándoles un estilo.

Pero en el ejemplo que nos dan, es solo para texto en negrita, cursiva e incluso para colorear. Necesitaba ajustar varios estilos en a CharSequencepara configurarlos en a TextView. Entonces, a esa clase (la llamé CharSequenceStyles), acabo de agregar esta función.

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

Y en la vista agregué esto.

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

¡Espero que esto te ayude!

MontDeska
fuente
0

Spanny hace que SpannableString sea más fácil de usar.

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

Como dijo Jon , para mí esta es la mejor solución y no necesita configurar ningún texto en tiempo de ejecución, solo use esta clase personalizada 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);
  }

}

y eso es todo, ahora solo ponlo en tu 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" />

con su cadena de HTML

<string name="example_html">
<![CDATA[
<b>Author:</b> Mr Donuthead<br/>
<b>Contact:</b> [email protected]<br/>
<i>Donuts for life </i>
]]>

AWolfsdorf
fuente