Os períodos são poderosos objetos de marcação que podem ser usados para definir o estilo do texto em nível de caractere ou parágrafo. Ao anexar períodos a objetos de texto, é possível mudar o texto de várias maneiras, incluindo adicionar cor, tornar o texto clicável, dimensionar o tamanho do texto e desenhar o texto de maneira personalizada. Os períodos também podem
mudar as propriedades de TextPaint
, desenhar em um
Canvas
e mudar o layout do texto.
O Android oferece vários tipos de períodos que abrangem uma variedade de padrões comuns de estilo de texto. Você também pode criar seus próprios períodos para aplicar estilos personalizados.
Criar e aplicar um período
Para criar um período, use uma das classes listadas na tabela a seguir. As classes são diferentes dependendo de se o texto é mutável, se a marcação de texto é mutável e qual estrutura de dados subjacente contém os dados do período.
Classe | Texto mutável | Marcação mutável | Estrutura de dados |
---|---|---|---|
SpannedString |
Não | Não | Matriz linear |
SpannableString |
Não | Sim | Matriz linear |
SpannableStringBuilder |
Sim | Sim | Árvore de intervalo |
Todas as três classes estendem a interface Spanned
. SpannableString
e SpannableStringBuilder
também estendem a
interface Spannable
.
Veja como decidir qual usar:
- Caso não pretenda modificar o texto ou a marcação após a criação, use
SpannedString
. - Se precisar anexar um pequeno número de períodos a um único objeto de texto e
o texto for somente leitura, use
SpannableString
. - Caso precise modificar o texto após a criação e anexar períodos a ele, use
SpannableStringBuilder
. - Se precisar anexar um grande número de períodos a um objeto de texto, independentemente
do texto ser somente leitura, use
SpannableStringBuilder
.
Para aplicar um período, chame setSpan(Object _what_, int _start_, int _end_, int
_flags_)
em um objeto Spannable
. O parâmetro what se refere ao período que você está
aplicando ao texto, e os parâmetros start e end indicam a parte
do texto em que você está aplicando o período.
Se você inserir texto dentro dos limites de um período, ele vai se expandir automaticamente para incluir esse texto. Ao inserir texto nos limites do período, ou seja, nos índices start ou end, o parâmetro flags determina se o período será expandido para incluir o texto inserido. Use a flag
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
para incluir e
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
para excluir o texto inserido.
O exemplo abaixo mostra como vincular um
ForegroundColorSpan
a uma
string:
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 );

ForegroundColorSpan
.
Como o período é definido usando Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, ele
se expande para incluir o texto inserido nos limites do período, conforme mostrado no
exemplo a seguir:
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)");

Spannable.SPAN_EXCLUSIVE_INCLUSIVE
.
Você pode anexar vários períodos ao mesmo texto. O exemplo abaixo mostra como criar um texto em negrito e em vermelho:
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 );

ForegroundColorSpan(Color.RED)
e
StyleSpan(BOLD)
.
Tipos de períodos do Android
O Android oferece mais de 20 tipos de períodos no pacote android.text.style. Ele categoriza os períodos de duas formas principais:
- Como o período afeta o texto: um período pode afetar a aparência ou as métricas de texto.
- Escopo do período: alguns períodos podem ser aplicados a caracteres individuais, enquanto outros precisam ser aplicados a um parágrafo inteiro.

As seções a seguir descrevem essas categorias em mais detalhes.
Períodos que afetam a aparência do texto
Alguns períodos que se aplicam no nível do caractere afetam a aparência do texto, como
mudar a cor do texto ou do plano de fundo e adicionar sublinhados ou tachados. Esses períodos estendem a classe CharacterStyle
.
O exemplo de código a seguir mostra como aplicar um UnderlineSpan
para sublinhar o 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);

UnderlineSpan
.
Períodos que afetam apenas a aparência do texto acionam um redesenho do texto sem
acionar um novo cálculo do layout. Esses períodos implementam
UpdateAppearance
e ampliam
CharacterStyle
.
As subclasses de CharacterStyle
definem como desenhar texto, fornecendo acesso para
atualizar o TextPaint
.
Períodos que afetam as métricas de texto
Outros períodos que se aplicam no nível do caractere afetam as métricas de texto, como altura da linha e tamanho do texto. Esses períodos estendem a classe
MetricAffectingSpan
.
O exemplo de código a seguir cria um
RelativeSizeSpan
que
aumenta o tamanho do texto em 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);

RelativeSizeSpan
.
Aplicar um período que afeta a métrica de texto faz com que um objeto de observação meça novamente o texto para conseguir o layout e a renderização corretos. Por exemplo, a alteração do tamanho do texto pode fazer com que as palavras apareçam em linhas diferentes. A aplicação do período anterior aciona uma nova medição, um novo cálculo do layout do texto e um novo desenho do texto.
Os períodos que afetam as métricas de texto estendem a classe MetricAffectingSpan
, uma classe abstrata que permite que subclasses definam como o período afeta a medição de texto fornecendo acesso ao TextPaint
. Como MetricAffectingSpan
estende CharacterStyle
, as subclasses afetam a aparência do texto no nível do caractere.
Períodos que afetam parágrafos
Um período também pode afetar os parágrafos de um texto, por exemplo, mudando o
alinhamento ou a margem de um bloco de texto. Períodos que afetam parágrafos inteiros
implementam ParagraphStyle
. Para
usar esses períodos, anexe-os ao parágrafo inteiro, excluindo o caractere final de
nova linha. Se você tentar aplicar um período de parágrafo a algo que não seja
um parágrafo inteiro, o Android não o aplicará.
A figura 8 mostra como o Android separa os parágrafos no texto.

\n
).
O exemplo de código a seguir aplica um
QuoteSpan
a um parágrafo. Se você anexar o período a qualquer posição que não seja o início ou fim de um parágrafo, o Android não vai aplicar o estilo.
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);

QuoteSpan
aplicado a um parágrafo.
Criar períodos personalizados
Caso você precise de mais funcionalidades do que as fornecidas nos períodos existentes do Android, é possível implementar um período personalizado. Ao implementar seu próprio período, decida se ele afeta o texto em nível de caracteres ou parágrafos e se afeta o layout ou a aparência do texto. Isso ajuda a determinar quais classes base podem ser estendidas e quais interfaces pode ser necessário implementar. Use a tabela a seguir como referência:
Cenário | Classe ou interface |
---|---|
Seu período afeta os caracteres do texto. | CharacterStyle |
Seu período afeta a aparência do texto. | UpdateAppearance |
Seu período afeta as métricas de texto. | UpdateLayout |
Seu período afeta os parágrafos do texto. | ParagraphStyle |
Por exemplo, se você precisar implementar um período personalizado que modifique o tamanho e a cor do texto, estenda RelativeSizeSpan
. Por herança, RelativeSizeSpan
estende CharacterStyle
e implementa as duas interfaces Update
. Como essa
classe já fornece callbacks para updateDrawState
e updateMeasureState
,
você pode substituir esses callbacks para implementar seu comportamento personalizado. O
código a seguir cria um período personalizado que estende RelativeSizeSpan
e
substitui o callback updateDrawState
para definir a cor do 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); } }
Este exemplo ilustra como criar um período personalizado. Você pode conseguir o mesmo efeito aplicando RelativeSizeSpan
e ForegroundColorSpan
ao texto.
Uso do período de teste
A interface Spanned
permite definir e recuperar períodos do texto. Ao testar, implemente um teste Android JUnit para verificar se os períodos corretos foram adicionados nos locais corretos. O app de exemplo Text Styling
contém um período que aplica a marcação a marcadores anexando
BulletPointSpan
ao texto. O exemplo de código a seguir mostra como testar
se os marcadores aparecem conforme o 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 mais exemplos de testes, consulte MarkdownBuilderTest no GitHub (link em inglês).
Testar períodos personalizados
Ao testar períodos, verifique se o TextPaint
contém as modificações
esperadas e se os elementos corretos aparecem no Canvas
. Por
exemplo, considere a implementação de um período personalizado que inclui marcadores
em um texto. O marcador tem tamanho e cor específicos e há uma lacuna
entre a margem esquerda da área do drawable e o marcador.
Você pode testar o comportamento dessa classe implementando um teste AndroidJUnit para verificar o seguinte:
- Se você aplicar o período corretamente, um marcador do tamanho e da cor especificados vai aparecer na tela, e haverá espaço adequado entre a margem esquerda e o marcador.
- Se você não aplicar o período, nenhum comportamento personalizado vai ser usado.
Consulte a implementação desses testes no exemplo TextStyling (link em inglês) no GitHub.
Você pode testar as interações de Canvas simulando a tela, transmitindo o objeto
simulado para o método
drawLeadingMargin()
e verificando se os métodos corretos são chamados com os parâmetros
adequados.
Você pode encontrar mais exemplos de testes de períodos em BulletPointSpanTest (link em inglês).
Práticas recomendadas para o uso de períodos
Há várias formas eficientes de configurar texto em um TextView
com relação à memória, dependendo das suas necessidades.
Anexar ou remover um período sem alterar o texto subjacente
TextView.setText()
contém várias sobrecargas que processam períodos de modos diferentes. Por exemplo, você pode
definir um objeto de texto Spannable
com o seguinte código:
Kotlin
textView.setText(spannableObject)
Java
textView.setText(spannableObject);
Ao chamar essa sobrecarga de setText()
, o TextView
cria uma cópia do
Spannable
como um SpannedString
e o mantém na memória como um CharSequence
.
Isso significa que o texto e os períodos são imutáveis. Portanto, quando você precisar
atualizar o texto ou os períodos, crie um novo objeto Spannable
e chame
setText()
novamente, o que também aciona uma nova medição e um novo desenho do
layout.
Para indicar que os períodos precisam ser mutáveis, você pode usar
setText(CharSequence text, TextView.BufferType
type)
,
como mostrado no exemplo a seguir:
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);
Neste exemplo, o parâmetro
BufferType.SPANNABLE
faz com que o TextView
crie um SpannableString
, e o
objeto CharSequence
mantido pelo TextView
agora tem marcação mutável e
texto imutável. Para atualizar o período, recupere o texto como um Spannable
e atualize os períodos conforme necessário.
Quando você anexa, desanexa ou reposiciona períodos, o TextView
é atualizado
automaticamente para refletir a mudança no texto. Se você mudar um atributo interno
de um período existente, chame invalidate()
para fazer mudanças na aparência ou
requestLayout()
para fazer alterações na métrica.
Configurar texto em um TextView várias vezes
Em alguns casos, como ao usar um
RecyclerView.ViewHolder
,
você pode reutilizar um TextView
e configurar o texto várias vezes. Por
padrão, independentemente de você definir BufferType
, o TextView
cria
uma cópia do objeto CharSequence
e a mantém na memória. Isso torna todas as atualizações de
TextView
intencionais. Não é possível atualizar o objeto
CharSequence
original para atualizar o texto. Isso significa que sempre que você definir um novo texto, o TextView
vai criar um novo objeto.
Caso você queira ter mais controle sobre esse processo e evitar a criação de objetos
extras, pode implementar seu próprio
Spannable.Factory
e substituir
newSpannable()
.
Em vez de criar um novo objeto de texto, você pode transmitir e retornar o
CharSequence
existente como um Spannable
, conforme demonstrado no exemplo a seguir:
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; } };
É necessário usar textView.setText(spannableObject, BufferType.SPANNABLE)
ao
definir o texto. Caso contrário, o CharSequence
de origem será criado como uma instância de Spanned
e não poderá ser transmitido para Spannable
, fazendo com que newSpannable()
gere uma
ClassCastException
.
Depois de substituir newSpannable()
, instrua o TextView
a usar o novo Factory
:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
Defina o objeto Spannable.Factory
uma vez, logo depois de receber uma referência para seu
TextView
. Se você estiver usando um RecyclerView
, defina o objeto Factory
quando
inflar suas visualizações pela primeira vez. Isso evita a criação de objetos extras quando o
RecyclerView
vincula um novo item ao ViewHolder
.
Mudar atributos de período interno
Se você precisar mudar apenas um atributo interno de um período mutável, como a
cor de marcador em um período de marcador personalizado, evite a sobrecarga que chamar
setText()
várias vezes gera mantendo uma referência ao período quando ele for criado.
Quando for necessário modificar o período, você poderá modificar a referência e chamar
invalidate()
ou requestLayout()
no TextView
, dependendo do tipo de
atributo alterado.
No exemplo de código a seguir, uma implementação padrão de marcador personalizado tem uma cor vermelha padrão que muda para cinza quando um botão é tocado:
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(); } }); } }
Usar as funções de extensão do Android KTX
O Android KTX também contém funções de extensão que facilitam o trabalho com períodos. Para saber mais, consulte a documentação do pacote androidx.core.text.