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 diversas formas, 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
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, você pode usar 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 de qual estrutura de dados subjacente contém os dados de 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 ao
texto, use
SpannableStringBuilder
. - Se precisar anexar um grande número de períodos a um objeto de texto, independentemente
de o 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 será expandido automaticamente para
incluir o texto inserido. Ao inserir texto nos limites do período,
ou seja, nos índices de 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 a seguir mostra como anexar 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 foi definido usando Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, ele
se expande para incluir o texto inserido nos limites do período, conforme mostrado no
exemplo abaixo:
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 a seguir mostra como criar um texto em negrito e 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 aplicados no nível do caractere afetam a aparência do texto, por exemplo,
mudando a cor do texto ou do plano de fundo e adicionando sublinhados ou tachados. Esses
períodos estendem a classe
CharacterStyle
.
O exemplo de código abaixo 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 aplicados no nível do caractere afetam as métricas de texto, como a altura da linha
e o tamanho do texto. Esses períodos estendem a
classe
MetricAffectingSpan
.
O exemplo de código abaixo 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 as 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 dos caracteres do texto.
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 o fim de um
parágrafo, o Android não 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 no nível do caractere ou do parágrafo 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
amplia CharacterStyle
e implementa as duas interfaces Update
. Como essa
classe já fornece callbacks para updateDrawState
e updateMeasureState
,
você pode substituí-los para implementar seu comportamento personalizado. O
código abaixo 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 de Estilo de
texto
contém um período que aplica a marcação a marcadores anexando
BulletPointSpan
ao texto. O exemplo de código abaixo 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 teste, consulte MarkdownBuilderTest (link em inglês) no GitHub.
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.
Confira a implementação desses testes no exemplo TextStyling (link em inglês) no GitHub.
Você pode testar as interações de tela 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
corretos.
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 memória para definir texto em um TextView
, 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 ficam imutáveis. Portanto, quando você precisar
atualizar o texto ou os períodos, será necessário criar um novo objeto Spannable
e chamar
setText()
novamente, o que também acionará 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)
,
conforme 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);
Nesse 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, extraia o texto como 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 relacionadas à aparência ou
requestLayout()
para fazer mudanças relacionadas à 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 faz com que todas
as atualizações de TextView
sejam 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.
Se você quiser ter mais controle sobre esse processo e evitar a criação de objetos
extras, implemente seu próprio
Spannable.Factory
e substitua
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
configurar 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
.
Após substituir newSpannable()
, diga ao TextView
para 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 o
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
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 abaixo, uma implementação personalizada de marcador 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 spans. Para saber mais, consulte a documentação do pacote androidx.core.text.