Los intervalos son objetos de marcado eficaces que puedes usar para darle estilo al texto en el
carácter o párrafo. Al adjuntar intervalos a los objetos de texto, puedes cambiar
texto de varias maneras, como agregar color, hacer que se pueda hacer clic en el texto,
ajustar el tamaño del texto y dibujarlo de forma personalizada. Los intervalos también pueden
cambiar las propiedades TextPaint
, dibujar en un
Canvas
y cambia el diseño del texto.
Android proporciona varios tipos de intervalos que abarcan una variedad de patrones de estilo de texto comunes. También puedes crear tus propios intervalos para aplicar un estilo personalizado.
Cómo crear y aplicar un intervalo
Para crear un intervalo, puedes usar una de las clases que se indican en la siguiente tabla. Las clases difieren en función de si el texto en sí es mutable, de si este el lenguaje de marcado es mutable y qué estructura de datos subyacente contiene los datos del intervalo.
Clase | Texto mutable | Lenguaje de marcado mutable | Estructura de datos |
---|---|---|---|
SpannedString |
No | No | Arreglo lineal |
SpannableString |
No | Sí | Arreglo lineal |
SpannableStringBuilder |
Sí | Sí | Árbol de intervalos |
Las tres clases extienden la Spanned
interfaz de usuario. SpannableString
y SpannableStringBuilder
también extienden la interfaz Spannable
.
Sigue estos pasos para decidir cuál usar:
- Si no tienes pensado modificar el texto o el lenguaje de marcado después de crearlo, usa
SpannedString
. - Si necesitas adjuntar una pequeña cantidad de intervalos a un solo objeto de texto y
el texto en sí es de solo lectura, usa
SpannableString
. - Si necesitas modificar el texto después de crearlo y debes adjuntar intervalos a
el texto, usa
SpannableStringBuilder
. - Si necesitas adjuntar una gran cantidad de intervalos a un objeto de texto, independientemente
de si el texto en sí es de solo lectura, usa
SpannableStringBuilder
.
Para aplicar un intervalo, llama a setSpan(Object _what_, int _start_, int _end_, int
_flags_)
en un objeto Spannable
. ¿El parámetro qué se refiere al intervalo que
al texto, y los parámetros start y end indican la parte
del texto al que le estás aplicando el intervalo.
Si insertas texto dentro de los límites de un intervalo, este se expandirá automáticamente para
incluir el texto insertado. Cuando insertas texto en el intervalo
límites, es decir, en los índices start o end, las marcas
determina si el intervalo se expande para incluir el texto insertado. Usa
el
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
para incluir el texto insertado y usar
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
para excluir el texto insertado.
En el siguiente ejemplo, se muestra cómo adjuntar un
ForegroundColorSpan
a una
cadena:
Kotlin
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE )
Java
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE );
![Una imagen que muestra un texto gris, parcialmente rojo.](https://developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-fg-color.png?authuser=9&hl=es)
ForegroundColorSpan
Como el intervalo se establece con Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, el intervalo
se expande para incluir el texto insertado dentro de los límites del intervalo, como se muestra en el
siguiente ejemplo:
Kotlin
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) spannable.insert(12, "(& fon)")
Java
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE ); spannable.insert(12, "(& fon)");
![Imagen que muestra cómo el intervalo incluye más texto cuando se usa SPAN_EXCLUSIVE_INCLUSIVE.](https://developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-fg-color-2.png?authuser=9&hl=es)
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
Puedes adjuntar varios intervalos al mismo texto. En el siguiente ejemplo, se muestra cómo para crear texto en negrita y rojo:
Kotlin
val spannable = SpannableString("Text is spantastic!") spannable.setSpan(ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan( StyleSpan(Typeface.BOLD), 8, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )
Java
SpannableString spannable = new SpannableString("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ); spannable.setSpan( new StyleSpan(Typeface.BOLD), 8, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
![Imagen que muestra un texto con varios intervalos: "ForegroundColorSpan(Color.RED)" y "StyleSpan(BOLD)"](https://developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-bold-red.png?authuser=9&hl=es)
ForegroundColorSpan(Color.RED)
y
StyleSpan(BOLD)
Tipos de intervalo de Android
Android ofrece más de 20 tipos de intervalo en el paquete android.text.style. Android categoriza los intervalos de dos formas principales:
- Cómo afecta el intervalo al texto: Un intervalo puede afectar la apariencia o el texto métricas.
- Alcance del intervalo: Algunos intervalos pueden aplicarse a caracteres individuales y otros debe aplicarse a un párrafo completo.
![Una imagen que muestra diferentes categorías de intervalos](https://developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-span-categories.png?authuser=9&hl=es)
En las siguientes secciones, se describen estas categorías con más detalle.
Intervalos que afectan la apariencia del texto
Algunos intervalos que se aplican a nivel de carácter afectan la apariencia del texto, como
cambiar el color del texto o del fondo y agregar subrayados o tachados. Estos
de estos intervalos extienden el
CharacterStyle
.
En el siguiente ejemplo de código, se muestra cómo aplicar un UnderlineSpan
al subrayado
el texto:
Kotlin
val string = SpannableString("Text with underline span") string.setSpan(UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
SpannableString string = new SpannableString("Text with underline span"); string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
![Imagen que muestra cómo subrayar texto con un `UnderlineSpan`](https://developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-underlinespan.png?authuser=9&hl=es)
UnderlineSpan
Los intervalos que afectan solo la apariencia del texto activan un rediseño del texto sin activar un nuevo cálculo del diseño. Estos intervalos implementan UpdateAppearance
y extienden CharacterStyle
.
Las subclases de CharacterStyle
definen cómo dibujar texto proporcionando acceso a
actualiza TextPaint
.
Intervalos que afectan las métricas del texto
Otros intervalos que se aplican a nivel de caracteres afectan las métricas de texto, como las líneas
la altura y el tamaño del texto. Estos intervalos extienden el
MetricAffectingSpan
.
En el siguiente ejemplo de código, se crea un
RelativeSizeSpan
que
aumenta el tamaño del texto en un 50%:
Kotlin
val string = SpannableString("Text with relative size span") string.setSpan(RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
SpannableString string = new SpannableString("Text with relative size span"); string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
![Una imagen que muestra el uso de RelativeSizeSpan](https://developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-relativesizespan.png?authuser=9&hl=es)
RelativeSizeSpan
Aplicar un intervalo que afecta las métricas del texto hace que un objeto de observación volver a medir el texto para lograr un diseño y una renderización correctos; por ejemplo, cambiar el tamaño del texto puede hacer que las palabras aparezcan en diferentes líneas. Si aplicas lo anterior, span activa una nueva medición, un recálculo del diseño del texto y el rediseño del el texto.
Los intervalos que afectan las métricas del texto extienden la clase MetricAffectingSpan
, un
Es la clase abstracta que permite que las subclases definan cómo el intervalo afecta la medición del texto.
proporcionando acceso a TextPaint
. Dado que MetricAffectingSpan
extiende
CharacterSpan
, las subclases afectan la apariencia del texto en el carácter
a nivel de organización.
Intervalos que afectan párrafos
Un intervalo también puede afectar el texto a nivel de párrafo, como cambiar el
alineación o el margen de un bloque de texto. Los intervalos que afectan los párrafos completos implementan ParagraphStyle
. Para
estos intervalos, los adjuntas a todo el párrafo, sin incluir la terminación
carácter de línea nueva. Si intentas aplicar un intervalo de párrafo a otro elemento que no sea
un párrafo completo, Android no aplica el intervalo.
En la Figura 8, se muestra cómo Android separa los párrafos en el texto.
![](https://developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-paragraphs.png?authuser=9&hl=es)
\n
).
En el siguiente ejemplo de código se aplica un
QuoteSpan
a un párrafo. Ten en cuenta que
si fijas el intervalo en una posición que no sea el principio o el final de una
Android no aplica el estilo en absoluto.
Kotlin
spannable.setSpan(QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
![Una imagen que muestra un ejemplo de QuoteSpan](https://developer.android.com/static/develop/ui/views/text-and-emoji/images/spans-quotespan.png?authuser=9&hl=es)
QuoteSpan
se aplica a un párrafo.
Cómo crear intervalos personalizados
Si necesitas más funciones que las que se ofrecen en los intervalos existentes de Android, puedes implementar un intervalo personalizado. Cuando implementes tu propio intervalo, si el intervalo afecta el texto a nivel de caracteres o de párrafo. también si afecta el diseño o la apariencia del texto. Esto te ayudará determinar qué clases básicas puedes extender y qué interfaces podrías necesitar para implementarlo. Usa la siguiente tabla como referencia:
Situación | Clase o interfaz |
---|---|
El intervalo afecta el texto a nivel de carácter. | CharacterStyle |
El intervalo afecta la apariencia del texto. | UpdateAppearance |
El intervalo afecta las métricas del texto. | UpdateLayout |
El intervalo afecta el texto a nivel de párrafo. | ParagraphStyle |
Por ejemplo, si necesitas implementar un intervalo personalizado que modifique el tamaño del texto y
color y extender RelativeSizeSpan
. A través de la herencia, RelativeSizeSpan
extiende CharacterStyle
e implementa las dos interfaces Update
. Desde este
ya proporciona devoluciones de llamada para updateDrawState
y updateMeasureState
,
puedes anular esas devoluciones de llamada para implementar tu comportamiento personalizado. El
El siguiente código crea un intervalo personalizado que extiende RelativeSizeSpan
y
anula la devolución de llamada updateDrawState
para establecer el color de TextPaint
:
Kotlin
class RelativeSizeColorSpan( size: Float, @ColorInt private val color: Int ) : RelativeSizeSpan(size) { override fun updateDrawState(textPaint: TextPaint) { super.updateDrawState(textPaint) textPaint.color = color } }
Java
public class RelativeSizeColorSpan extends RelativeSizeSpan { private int color; public RelativeSizeColorSpan(float spanSize, int spanColor) { super(spanSize); color = spanColor; } @Override public void updateDrawState(TextPaint textPaint) { super.updateDrawState(textPaint); textPaint.setColor(color); } }
En este ejemplo, se muestra cómo crear un intervalo personalizado. Puedes lograr lo mismo
mediante la aplicación de RelativeSizeSpan
y ForegroundColorSpan
al texto.
Uso del intervalo de prueba
La interfaz Spanned
te permite establecer y recuperar intervalos de
texto. Cuando realices pruebas, implementa una Android JUnit
test para verificar que se agreguen los intervalos correctos
en las ubicaciones correctas. El ejemplo de estilo de texto
app
contiene un intervalo que aplica lenguaje de marcado a las viñetas adjuntando
BulletPointSpan
al texto. En el siguiente ejemplo de código, se muestra cómo probar
si las viñetas se muestran según lo esperado:
Kotlin
@Test fun textWithBulletPoints() { val result = builder.markdownToSpans("Points\n* one\n+ two") // Check whether the markup tags are removed. assertEquals("Points\none\ntwo", result.toString()) // Get all the spans attached to the SpannedString. val spans = result.getSpans<Any>(0, result.length, Any::class.java) // Check whether the correct number of spans are created. assertEquals(2, spans.size.toLong()) // Check whether the spans are instances of BulletPointSpan. val bulletSpan1 = spans[0] as BulletPointSpan val bulletSpan2 = spans[1] as BulletPointSpan // Check whether the start and end indices are the expected ones. assertEquals(7, result.getSpanStart(bulletSpan1).toLong()) assertEquals(11, result.getSpanEnd(bulletSpan1).toLong()) assertEquals(11, result.getSpanStart(bulletSpan2).toLong()) assertEquals(14, result.getSpanEnd(bulletSpan2).toLong()) }
Java
@Test public void textWithBulletPoints() { SpannedString result = builder.markdownToSpans("Points\n* one\n+ two"); // Check whether the markup tags are removed. assertEquals("Points\none\ntwo", result.toString()); // Get all the spans attached to the SpannedString. Object[] spans = result.getSpans(0, result.length(), Object.class); // Check whether the correct number of spans are created. assertEquals(2, spans.length); // Check whether the spans are instances of BulletPointSpan. BulletPointSpan bulletSpan1 = (BulletPointSpan) spans[0]; BulletPointSpan bulletSpan2 = (BulletPointSpan) spans[1]; // Check whether the start and end indices are the expected ones. assertEquals(7, result.getSpanStart(bulletSpan1)); assertEquals(11, result.getSpanEnd(bulletSpan1)); assertEquals(11, result.getSpanStart(bulletSpan2)); assertEquals(14, result.getSpanEnd(bulletSpan2)); }
Para ver más ejemplos de prueba, consulta MarkdownBuilderTest en GitHub.
Cómo probar intervalos personalizados
Cuando pruebes los intervalos, verifica que TextPaint
contenga el valor esperado.
modificaciones y que los elementos correctos aparezcan en tu Canvas
. Por ejemplo, piensa en la implementación de un intervalo personalizado que anexa una viñeta a alguna parte del texto. La viñeta tiene un color y un tamaño especificados, y hay un espacio.
entre el margen izquierdo del área del elemento de diseño y la viñeta.
Puedes probar el comportamiento de esta clase mediante la implementación de una prueba AndroidJUnit y verificar lo siguiente:
- Si aplicas correctamente el intervalo, se agrega una viñeta del tamaño especificado y el color aparece en el lienzo y existe el espacio adecuado entre el lado izquierdo el margen y la viñeta.
- Si no aplicas el intervalo, no aparecerá ninguno de los comportamientos personalizados.
Puedes ver la implementación de estas pruebas en TextStyling ejemplo en GitHub.
Puedes probar las interacciones del lienzo simulando el lienzo, pasando el
el objeto de escucha
drawLeadingMargin()
y verifica que se llame a los métodos correctos con las
parámetros.
Puedes encontrar más muestras de intervalos en BulletPointSpanTest.
Prácticas recomendadas para usar intervalos
Hay varias maneras eficientes en términos de memoria de configurar texto en un TextView
, según
según tus necesidades.
Cómo adjuntar o separar un intervalo sin cambiar el texto subyacente
TextView.setText()
contiene varias sobrecargas que controlan los intervalos de manera diferente. Por ejemplo, puedes
configura un objeto de texto Spannable
con el siguiente código:
Kotlin
textView.setText(spannableObject)
Java
textView.setText(spannableObject);
Cuando llamas a esta sobrecarga de setText()
, TextView
crea una copia de tu Spannable
como SpannedString
y la guarda en la memoria como CharSequence
.
Esto significa que el texto y los intervalos son inmutables, por lo que
actualiza el texto o los intervalos, crea un nuevo objeto Spannable
y llama
setText()
de nuevo, lo que también activa una nueva medición y un nuevo dibujo del
.
Para indicar que los intervalos deben ser mutables, puedes usar
setText(CharSequence text, TextView.BufferType
type)
:
como se muestra en el siguiente ejemplo:
Kotlin
textView.setText(spannable, BufferType.SPANNABLE) val spannableText = textView.text as Spannable spannableText.setSpan( ForegroundColorSpan(color), 8, spannableText.length, SPAN_INCLUSIVE_INCLUSIVE )
Java
textView.setText(spannable, BufferType.SPANNABLE); Spannable spannableText = (Spannable) textView.getText(); spannableText.setSpan( new ForegroundColorSpan(color), 8, spannableText.getLength(), SPAN_INCLUSIVE_INCLUSIVE);
En este ejemplo,
BufferType.SPANNABLE
parámetro hace que el TextView
cree un SpannableString
, y el
El objeto CharSequence
que mantiene TextView
ahora tiene lenguaje de marcado mutable y
texto inmutable. Para actualizar el intervalo, recupera el texto como Spannable
y, luego,
y actualizar los intervalos según sea necesario.
Cuando adjuntas, separas o cambias de posición los intervalos, TextView
se actualiza automáticamente para reflejar el cambio en el texto. Si cambias un atributo interno
de un intervalo existente, llama a invalidate()
para hacer cambios relacionados con la apariencia.
requestLayout()
para realizar cambios relacionados con las métricas.
Cómo configurar texto en TextView varias veces
En algunos casos, como cuando se usa un RecyclerView.ViewHolder
, es posible que quieras reutilizar un TextView
y configurar el texto varias veces. De
de forma predeterminada, independientemente de si estableces BufferType
, el TextView
crea
una copia del objeto CharSequence
y la retiene en la memoria. Esto hace que todo
TextView
se actualiza de forma intencional; no se puede actualizar la original
CharSequence
para actualizar el texto. Esto significa que cada vez que establezcas
texto, el TextView
crea un objeto nuevo.
Si quieres tener más control sobre este proceso y evitar el uso del objeto
de creación, puedes implementar tu propio
Spannable.Factory
y anular
newSpannable()
En lugar de crear un nuevo objeto de texto, puedes transmitir y mostrar el
CharSequence
como Spannable
, como se muestra en el siguiente ejemplo:
Kotlin
val spannableFactory = object : Spannable.Factory() { override fun newSpannable(source: CharSequence?): Spannable { return source as Spannable } }
Java
Spannable.Factory spannableFactory = new Spannable.Factory(){ @Override public Spannable newSpannable(CharSequence source) { return (Spannable) source; } };
Debes usar textView.setText(spannableObject, BufferType.SPANNABLE)
cuando
estableciendo el texto. De lo contrario, el CharSequence
de origen se crea como Spanned
.
y no se puede convertir a Spannable
, lo que provoca que newSpannable()
arroje una
ClassCastException
Después de anular newSpannable()
, indícale a TextView
que use el nuevo Factory
:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
Configura el objeto Spannable.Factory
una vez, inmediatamente después de obtener una referencia a tu
TextView
Si usas un objeto RecyclerView
, configura el objeto Factory
cuando
aumentar tus vistas. Esto evita la creación de objetos adicionales cuando tu
RecyclerView
vincula un elemento nuevo a tu ViewHolder
.
Cómo cambiar los atributos de intervalos internos
Si necesitas cambiar solo un atributo interno de un intervalo mutable, como el
color de la viñeta en un intervalo personalizado, puedes evitar la sobrecarga de llamar
setText()
varias veces manteniendo una referencia al intervalo a medida que se crea.
Cuando necesites modificar el intervalo, puedes modificar la referencia y, luego, llamar
invalidate()
o requestLayout()
en el TextView
, según el tipo de
que cambiaste.
En el siguiente ejemplo de código, una implementación personalizada de viñetas tiene un color predeterminado de rojo que cambia a gris cuando se presiona un botón:
Kotlin
class MainActivity : AppCompatActivity() { // Keeping the span as a field. val bulletSpan = BulletPointSpan(color = Color.RED) override fun onCreate(savedInstanceState: Bundle?) { ... val spannable = SpannableString("Text is spantastic") // Setting the span to the bulletSpan field. spannable.setSpan( bulletSpan, 0, 4, Spanned.SPAN_INCLUSIVE_INCLUSIVE ) styledText.setText(spannable) button.setOnClickListener { // Change the color of the mutable span. bulletSpan.color = Color.GRAY // Color doesn't change until invalidate is called. styledText.invalidate() } } }
Java
public class MainActivity extends AppCompatActivity { private BulletPointSpan bulletSpan = new BulletPointSpan(Color.RED); @Override protected void onCreate(Bundle savedInstanceState) { ... SpannableString spannable = new SpannableString("Text is spantastic"); // Setting the span to the bulletSpan field. spannable.setSpan(bulletSpan, 0, 4, Spanned.SPAN_INCLUSIVE_INCLUSIVE); styledText.setText(spannable); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Change the color of the mutable span. bulletSpan.setColor(Color.GRAY); // Color doesn't change until invalidate is called. styledText.invalidate(); } }); } }
Cómo usar las funciones de extensión de Android KTX
Android KTX también contiene funciones de extensión que facilitan el trabajo con intervalos y fácil de usar. Para obtener más información, consulta la documentación del paquete androidx.core.text.