Os períodos são poderosos objetos de marcação que você pode usar para definir o estilo do texto na
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, como 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 TextPaint
, desenhar em uma
Canvas
e altere 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 diferem com base no fato de o texto ser mutável, se o 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 |
As três classes estendem o Spanned
interface gráfica do usuário. 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 você precisar anexar um pequeno número de períodos a um único objeto de texto e
o texto em si é somente leitura, use
SpannableString
. - Se você precisar modificar o texto após a criação e anexar períodos a
o texto, use
SpannableStringBuilder
. - Se você precisar anexar um grande número de períodos a um objeto de texto, independentemente
de o texto em si 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á
são aplicadas ao texto, e os parâmetros start e end indicam a porção
do texto ao qual você está aplicando o período.
Se você inserir texto dentro dos limites de um período, ele se expandirá automaticamente para
incluem o texto inserido. Ao inserir texto no período
limites, ou seja, nos índices de início ou fim, as sinalizações
determina se o período se expande para incluir o texto inserido. Usar
as
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
para incluir o texto inserido e usar
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
para excluir o texto inserido.
O exemplo a seguir mostra como anexar um
ForegroundColorSpan
para um
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 );
Como o período é definido usando Spannable.SPAN_EXCLUSIVE_INCLUSIVE
, ele
expande-se para incluir o texto inserido nos limites do período, como mostrado
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)");
Você pode anexar vários períodos ao mesmo texto. O exemplo a seguir mostra como para 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 );
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 o texto do texto métricas.
- Escopo do período: alguns períodos podem ser aplicados a caracteres individuais, enquanto outros precisa ser aplicada 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 aos caracteres afetam a aparência do texto, como
alterar a cor do texto ou do plano de fundo e adicionar sublinhados ou tachados. Esses
os períodos estendem
classe CharacterStyle
.
O exemplo de código abaixo mostra como aplicar uma UnderlineSpan
ao sublinhado
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);
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 CharacterStyle
definem como desenhar texto, fornecendo acesso a
atualize a TextPaint
.
Períodos que afetam as métricas de texto
Outros períodos que se aplicam aos caracteres afetam as métricas de texto, como
altura e tamanho do texto. Esses vãos estendem
MetricAffectingSpan
.
O exemplo de código a seguir cria uma
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);
Aplicar um período que afeta as métricas de texto faz com que um objeto de observação medir novamente o texto para garantir o layout e a renderização corretos, por exemplo, alterando tamanho do texto pode fazer com que as palavras apareçam em linhas diferentes. A aplicação da regra período aciona uma nova medição, um novo cálculo do layout do texto e um redesenho o 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
se estende
CharacterSpan
, as subclasses afetam a aparência do texto no caractere
nível
Períodos que afetam parágrafos
Um período também pode afetar o texto em parágrafos, como mudar o
alinhamento ou na margem de um bloco de texto. Períodos que afetam parágrafos inteiros
implementam ParagraphStyle
. Para
usar esses períodos, você os anexa ao parágrafo inteiro, excluindo o final
caractere de nova linha. Se você tentar aplicar um período de parágrafo a algo diferente
um parágrafo inteiro, o Android não aplicará o período.
A figura 8 mostra como o Android separa os parágrafos no texto.
O exemplo de código a seguir aplica uma
QuoteSpan
a um parágrafo. Observe que
se você anexar o período a qualquer posição que não seja o início ou o fim de uma
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);
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 o período afeta o texto em nível de caracteres ou parágrafos e também se isso afeta o layout ou a aparência do texto. Isso ajuda você determinar quais classes de base podem ser estendidas e quais interfaces podem ser necessárias; precisam ser implementados. 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 do texto e
cor, estenda RelativeSizeSpan
. Pela herança, RelativeSizeSpan
estende CharacterStyle
e implementa as duas interfaces Update
. Como este
já fornece callbacks para updateDrawState
e updateMeasureState
.
substitua esses callbacks para implementar seu comportamento personalizado. A
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); } }
Neste exemplo, mostramos como criar um período personalizado. Você pode conseguir o mesmo
aplicando uma RelativeSizeSpan
e uma ForegroundColorSpan
ao texto.
Uso do período de teste
A interface Spanned
permite definir e recuperar períodos de
em textos. Ao testar, implemente uma interface Android JUnit
test para verificar se os períodos corretos foram adicionados
nos locais corretos. O exemplo de Estilo de texto
App
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 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 (link em inglês) no GitHub.
Testar períodos personalizados
Ao testar períodos, verificar se TextPaint
contém o
modificações 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 especificados, e há uma lacuna
entre a margem esquerda da área do desenhável e o marcador.
Você pode testar o comportamento dessa classe implementando um teste AndroidJUnit para verificar o seguinte:
- Se você aplicar corretamente o período, um marcador do tamanho especificado e aparece na tela, e o espaço adequado existe entre os lados margem e o marcador.
- Se você não aplicar o período, nenhum comportamento personalizado será exibido.
Confira a implementação desses testes na documentação TextStyling exemplo (em inglês) no GitHub.
É possível testar as interações de Canvas simulando a tela, passando o
ao objeto
drawLeadingMargin()
e verificando se os métodos corretos são chamados com o
parâmetros.
Você pode encontrar mais amostras de teste de período em BulletPointSpanTest (link em inglês).
Práticas recomendadas para o uso de períodos
Há várias maneiras eficientes de definir texto em uma TextView
com eficiência de memória, dependendo
de acordo com 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 maneira diferente. Por exemplo, é possível
defina 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
atualize 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 novo desenho da
o mesmo layout organizacional.
Para indicar que os períodos precisam ser mutáveis, você pode usar
setText(CharSequence text, TextView.BufferType
type)
,
conforme mostrado neste exemplo:
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,
BufferType.SPANNABLE
faz com que TextView
crie um SpannableString
, e o
O objeto CharSequence
mantido pelo TextView
agora tem uma marcação mutável e
um texto imutável. Para atualizar o período, extraia o texto como uma Spannable
e, em seguida,
atualizar 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ê alterar um atributo interno
de um período existente, chame invalidate()
para fazer mudanças relacionadas à aparência ou
requestLayout()
para fazer mudanças relacionadas às métricas.
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. De
padrão, independentemente de você definir o BufferType
, o TextView
cria
uma cópia do objeto CharSequence
e a mantém na memória. Isso faz com que
TextView
atualizações intencionais. Não é possível atualizar a versão original.
Objeto CharSequence
para atualizar o texto. Ou seja, toda vez que você definir
texto, o TextView
cria um novo objeto.
Se quiser ter mais controle sobre esse processo e evitar o objeto extra
é possível implementar sua própria
Spannable.Factory
e substituir
newSpannable()
Em vez de criar um novo objeto de texto, é possível transmitir e retornar o objeto
CharSequence
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; } };
Use textView.setText(spannableObject, BufferType.SPANNABLE)
quando
definindo o texto. Caso contrário, a CharSequence
de origem será criada como um Spanned
.
instância e não pode 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 ao seu
TextView
. Se você estiver usando um RecyclerView
, defina o objeto Factory
ao
primeiro, infle suas visualizações. Isso evita a criação de objetos extras quando seu
RecyclerView
vincula um novo item ao seu ViewHolder
.
Mudar atributos de período interno
Se você precisar mudar apenas um atributo interno de um período mutável, como o
cor de marcador em um período de marcador personalizado, poderá evitar a sobrecarga de chamar
setText()
várias vezes, mantendo uma referência ao período à medida que ele é criado.
Quando precisar modificar o período, modifique a referência e chame
invalidate()
ou requestLayout()
na TextView
, dependendo do tipo de
que você alterou.
No exemplo de código a seguir, uma implementação de marcador personalizada tem um cor padrão de vermelho 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 tornam o trabalho com períodos mais fácil. Para saber mais, consulte a documentação do pacote androidx.core.text.