O texto é uma peça central de qualquer IU, e o Jetpack Compose facilita a exibição ou criação de textos. O Compose usa a composição de elementos básicos, o que significa que não é necessário substituir propriedades e métodos ou estender grandes classes para fazer com que um design e uma lógica que possam ser compostos funcionem da forma que você quer.
Como base, o Compose fornece um BasicText
e um BasicTextField
, que são as
estruturas básicas para exibir o texto e processar a entrada do usuário. Em um nível mais alto, o Compose
fornece o Text
e o TextField
,
que são elementos que podem ser compostos de acordo com as diretrizes doMaterial Design. É recomendável
usar esses elementos, porque eles têm uma aparência familiar aos usuários do Android e incluem
outras opções para simplificar a personalização, sem que seja necessário programar muito
código.
Como exibir texto
A forma mais básica de exibir textos é usando o elemento Text
que pode ser composto com uma
String
como argumento:
@Composable
fun SimpleText() {
Text("Hello World")
}
Exibir texto do recurso
Recomendamos que você use recursos de string
em vez de valores de Text
fixos no código, porque, dessa forma, é possível compartilhar as mesmas strings com as
visualizações do Android e preparar o app para internacionalização:
@Composable
fun StringResourceText() {
Text(stringResource(R.string.hello_world))
}
Como definir o estilo do texto
O elemento Text
que pode ser composto tem vários parâmetros opcionais para definir o estilo do conteúdo.
Veja abaixo uma lista dos parâmetros que abrangem os casos de uso mais comuns relacionados a textos. Para
ver todos os parâmetros de Text
, recomendamos que você consulte o código-fonte
do texto
do Compose (link em inglês).
Sempre que você definir um desses parâmetros, o mesmo estilo será aplicado a todo o valor do texto. Caso seja necessário aplicar vários estilos na mesma linha ou parágrafo, consulte a seção sobre vários estilos inline.
Como mudar a cor do texto
@Composable
fun BlueText() {
Text("Hello World", color = Color.Blue)
}
Como mudar o tamanho do texto
@Composable
fun BigText() {
Text("Hello World", fontSize = 30.sp)
}
Como deixar o texto em itálico
Use o parâmetro fontStyle
para aplicar itálico ao texto ou defina outro
FontStyle
.
@Composable
fun ItalicText() {
Text("Hello World", fontStyle = FontStyle.Italic)
}
Como deixar o texto em negrito
Use o parâmetro fontWeight
para aplicar negrito ao texto ou defina outro FontWeight
.
@Composable
fun BoldText() {
Text("Hello World", fontWeight = FontWeight.Bold)
}
Alinhamentos de texto
O parâmetro textAlign
permite definir o alinhamento do texto em uma
área de superfície do elemento Text
que pode ser composto.
Por padrão, o Text
seleciona o alinhamento natural do texto, dependendo do
valor do conteúdo:
- Borda esquerda do contêiner
Text
para alfabetos que seguem o sentido da esquerda para a direita, como latino, cirílico ou hangul - Borda direita do contêiner
Text
para alfabetos que seguem o sentido da direita para a esquerda, como árabe ou hebraico
@Preview(showBackground = true)
@Composable
fun CenterText() {
Text("Hello World", textAlign = TextAlign.Center,
modifier = Modifier.width(150.dp))
}
Caso queira definir manualmente o alinhamento do texto de um
elemento Text
que pode ser composto, use
TextAlign.Start
e TextAlign.End
, em vez de TextAlign.Left
e
TextAlign.Right
, respectivamente, porque eles se referem à borda correta do Text
, dependendo da orientação do idioma preferencial. Por exemplo,
TextAlign.End
fica alinhado à direita para textos em francês e à esquerda
para textos em árabe, mas TextAlign.Right
alinha o texto à direita independentemente
do alfabeto usado.
Sombra
O parâmetro style
permite definir um objeto do tipo TextStyle
e configurar vários parâmetros, como uma sombra.
Shadow
recebe uma cor
para a sombra, o deslocamento ou a localização em relação ao Text
e
o raio de desfoque, que é a intensidade do efeito desfocado.
@Preview(showBackground = true)
@Composable
fun TextShadow() {
val offset = Offset(5.0f, 10.0f)
Text(
text = "Hello world!",
style = TextStyle(
fontSize = 24.sp,
shadow = Shadow(
color = Color.Blue,
offset = offset,
blurRadius = 3f
)
)
)
}
Como trabalhar com fontes
Text
tem um parâmetro fontFamily
para permitir a configuração da fonte usada no
elemento que pode ser composto. Por padrão, as famílias de fontes Serif, Sans Serif, fontes monoespaçadas e cursivas
estão incluídas:
@Composable
fun DifferentFonts() {
Column {
Text("Hello World", fontFamily = FontFamily.Serif)
Text("Hello World", fontFamily = FontFamily.SansSerif)
}
}
É possível usar o atributo fontFamily
para trabalhar com fontes personalizadas,
definidas na pasta res/font
:
O exemplo abaixo mostra como definir uma fontFamily
com base nesses arquivos de
fonte e como usar a função Font
:
val firaSansFamily = FontFamily(
Font(R.font.firasans_light, FontWeight.Light),
Font(R.font.firasans_regular, FontWeight.Normal),
Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
Font(R.font.firasans_medium, FontWeight.Medium),
Font(R.font.firasans_bold, FontWeight.Bold)
)
Por fim, você pode transmitir essa fontFamily
para o elemento Text
que pode ser composto. Como uma
fontFamily
pode incluir pesos diferentes, é possível definir o fontWeight
manualmente para
selecionar o peso certo para o texto:
Column {
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
Text(
..., fontFamily = firaSansFamily, fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Italic
)
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
Text(..., fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}
Para saber como definir a tipografia no app todo, consulte a documentação sobre temas.
Vários estilos em um texto
Para definir estilos diferentes no mesmo elemento que pode ser composto Text
,
use uma AnnotatedString
,
uma string que pode receber anotações de estilo arbitrárias.
AnnotatedString
é uma classe de dados que contém:
- Um valor
Text
- Uma
List
deSpanStyleRange
, equivalente ao estilo inline com intervalo de posições dentro do valor de texto - Uma
List
deParagraphStyleRange
, especificando o alinhamento do texto, a direção do texto, a altura da linha e o estilo de recuo do texto
O TextStyle
é usado
no Text
que pode ser composto, ao passo que o SpanStyle
e o ParagraphStyle
são usados na AnnotatedString
.
A diferença entre SpanStyle
e ParagraphStyle
é que ParagraphStyle
pode ser aplicado a um parágrafo inteiro, ao passo que SpanStyle
pode ser aplicado no
nível do caractere. Depois que uma parte do texto é marcada com um ParagraphStyle
,
ela fica separada do restante, como se tivesse um feed de linha
no início e no fim.
A AnnotatedString
tem um builder de tipo
seguro (link em inglês)
para facilitar a criação: buildAnnotatedString
@Composable
fun MultipleStylesInText() {
Text(
buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.Blue)) {
append("H")
}
append("ello ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
append("W")
}
append("orld")
}
)
}
Podemos definir o ParagraphStyle
da mesma maneira:
@Composable
fun ParagraphStyle() {
Text(
buildAnnotatedString {
withStyle(style = ParagraphStyle(lineHeight = 30.sp)) {
withStyle(style = SpanStyle(color = Color.Blue)) {
append("Hello\n")
}
withStyle(
style = SpanStyle(
fontWeight = FontWeight.Bold,
color = Color.Red
)
) {
append("World\n")
}
append("Compose")
}
}
)
}
Número máximo de linhas
Para limitar o número de linhas visíveis em um Text
que pode ser composto, defina o parâmetro
maxLines
:
@Composable
fun LongText() {
Text("hello ".repeat(50), maxLines = 2)
}
Excesso de texto
Ao limitar um texto longo, é recomendável indicar um TextOverflow
,
que só será exibido se o texto na tela for truncado. Para fazer isso, defina o parâmetro
textOverflow
:
@Composable
fun OverflowedText() {
Text("Hello Compose ".repeat(50), maxLines = 2, overflow = TextOverflow.Ellipsis)
}
APIs includeFontPadding e lineHeight
includeFontPadding
é uma propriedade legada que adiciona padding extra com base nas métricas de fonte na parte de cima da primeira linha e
na parte de baixo da última linha de um texto. No Compose 1.2.0, includeFontPadding
está definido como
true
por padrão.
Recomendamos definir includeFontPadding
como false
para remover o padding extra.
Use a API experimental/descontinuada PlatformTextStyle
no Compose 1.2.0
e ajuste o texto, se necessário.
@Composable
fun AlignedText() {
Text(
text = myText,
style = LocalTextStyle.current.merge(
TextStyle(
lineHeight = 2.5.em,
platformStyle = PlatformTextStyle(
includeFontPadding = false
),
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None
)
)
)
)
}
Nas próximas versões do Compose, a includeFontPadding
vai ser definida como false
por padrão, e
a API PlatformTextStyle
vai ser removida.
Consulte a postagem do blog Como corrigir o padding da fonte do texto no Compose (em inglês)
para saber mais sobre o contexto dessa mudança, como includeFontPadding
funcionava no sistema de visualização e as mudanças que fizemos no Compose e nas novas
APIs LineHeightStyle
.
Temas
Para usar o tema do app para definir o estilo de texto, consulte a documentação sobre temas.
Interações do usuário
O Jetpack Compose permite interatividade detalhada no Text
. A seleção de texto agora
é mais flexível e pode ser feita em layouts que podem ser compostos. As interações do usuário
nos textos são diferentes de outros layouts que podem ser compostos, já que não é possível adicionar um modificador
a uma parte de um elemento de Text
. Esta seção destaca as diferentes APIs
para permitir interações do usuário.
Como selecionar texto
Por padrão, os elementos que podem ser compostos não são selecionáveis, ou seja, os usuários não
podem selecionar e copiar textos do app. Para ativar a seleção de texto, é necessário unir
os elementos de texto com um elemento que pode ser composto SelectionContainer
:
@Composable
fun SelectableText() {
SelectionContainer {
Text("This text is selectable")
}
}
Você pode desativar a seleção de texto em partes específicas de uma área selecionável. Para
fazer isso, é necessário unir a parte que não pode ser selecionada a um elemento que pode ser composto
DisableSelection
:
@Composable
fun PartiallySelectableText() {
SelectionContainer {
Column {
Text("This text is selectable")
Text("This one too")
Text("This one as well")
DisableSelection {
Text("But not this one")
Text("Neither this one")
}
Text("But again, you can select this one")
Text("And this one too")
}
}
}
Como descobrir a posição de um clique no texto
Para detectar cliques no Text
, adicione o modificador
clickable
. No entanto, se você quiser descobrir a posição de um clique em um
Text
que pode ser composto, como no caso em que há ações diferentes baseadas em
partes distintas do texto, é necessário usar um
ClickableText
:
@Composable
fun SimpleClickableText() {
ClickableText(
text = AnnotatedString("Click Me"),
onClick = { offset ->
Log.d("ClickableText", "$offset -th character is clicked.")
}
)
}
Clique com anotação
Quando um usuário clica em um elemento de composição Text
, você pode anexar mais
informações a uma parte do valor do Text
, como um URL anexado a uma palavra específica
que é aberto em um navegador. Para fazer isso, você precisa anexar uma
anotação, que usa uma tag (String
), um item (String
) e um intervalo de texto
como parâmetros. A partir de uma AnnotatedString
, essas anotações podem ser filtradas
de acordo com as tags ou intervalos de texto. Veja um exemplo:
@Composable
fun AnnotatedClickableText() {
val annotatedText = buildAnnotatedString {
append("Click ")
// We attach this *URL* annotation to the following content
// until `pop()` is called
pushStringAnnotation(tag = "URL",
annotation = "https://developer.android.com")
withStyle(style = SpanStyle(color = Color.Blue,
fontWeight = FontWeight.Bold)) {
append("here")
}
pop()
}
ClickableText(
text = annotatedText,
onClick = { offset ->
// We check if there is an *URL* annotation attached to the text
// at the clicked position
annotatedText.getStringAnnotations(tag = "URL", start = offset,
end = offset)
.firstOrNull()?.let { annotation ->
// If yes, we log its value
Log.d("Clicked URL", annotation.item)
}
}
)
}
Como inserir e modificar texto
TextField
permite que os usuários insiram e modifiquem o texto. Há dois níveis de
implementações de TextField
:
TextField
é a implementação do Material Design. Recomendamos que você escolha essa implementação, que segue as diretrizes do Material Design (link em inglês):- A função
BasicTextField
permite que os usuários editem o texto usando o teclado de software ou hardware, mas não oferece decorações como dicas ou marcadores de posição.
@Composable
fun SimpleFilledTextFieldSample() {
var text by remember { mutableStateOf("Hello") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
@Composable
fun SimpleOutlinedTextFieldSample() {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Label") }
)
}
Como definir o estilo de TextField
TextField
e BasicTextField
têm vários parâmetros em comum
para personalização. A lista completa do TextField
está disponível no código-fonte
do TextField
(link em inglês).
Veja uma lista não exaustiva de alguns parâmetros úteis:
singleLine
maxLines
textStyle
@Composable
fun StyledTextField() {
var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }
TextField(
value = value,
onValueChange = { value = it },
label = { Text("Enter text") },
maxLines = 2,
textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(20.dp)
)
}
Recomendamos o uso de TextField
em vez de BasicTextField
quando o design chama um
TextFieldField ou OutlineTextField do Material Design. No entanto, BasicTextField
precisa ser usado
ao criar designs que não precisam das decorações da especificação do Material Design.
Opções do teclado
O TextField
permite definir opções de configuração do teclado, como o layout
dele, ou ativar a correção automática, caso haja suporte. Algumas
opções podem não ser garantidas se o teclado de software não estiver de acordo com as
opções apresentadas aqui. Veja a lista de opções de teclado
compatíveis:
capitalization
autoCorrect
keyboardType
imeAction
Formatação
O TextField
permite definir uma VisualTransformation
no valor de entrada, como substituir caracteres por *
para senhas ou
inserir hifens a cada quatro (4) dígitos em número de cartão de crédito:
@Composable
fun PasswordTextField() {
var password by rememberSaveable { mutableStateOf("") }
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Enter password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
)
}
Mais exemplos estão disponíveis no código-fonte de VisualTransformSamples.
Como limpar entradas
Uma tarefa comum ao editar o texto é remover os caracteres iniciais ou transformar a string de entrada sempre que ela mudar.
Como modelo, suponha que o teclado possa fazer edições grandes e arbitrárias
em cada onValueChange
. Isso pode acontecer, por exemplo, se o usuário usar a
correção automática, substituir uma palavra por um emoji ou usar outros recursos de edição inteligente. Para
processar a ação corretamente, programe qualquer lógica de transformação com o pressuposto de que
o texto atual transmitido para o método onValueChange
não está relacionado aos valores anteriores ou aos próximos
que serão transmitidos para onValueChange
.
Para implementar um campo de texto que não aceita zeros à esquerda, remova todos esses zeros sempre que o valor mudar.
@Composable
fun NoLeadingZeroes() {
var input by rememberSaveable { mutableStateOf("") }
TextField(
value = input,
onValueChange = { newText ->
input = newText.trimStart { it == '0' }
}
)
}
Para controlar a posição do cursor ao limpar o texto, use a sobrecarga TextFieldValue
da função TextField
como parte do estado.
Fontes para download
Do Compose 1.2-alpha07 em diante, é possível usar a API de fontes para download no app do Compose para fazer o download de fontes do Google de forma assíncrona e as usar no seu app.
No momento, não há suporte às fontes para download fornecidas por provedores personalizados.
Como usar as fontes para download de forma programática
Para fazer o download de uma fonte de forma programática no seu app, siga estas etapas:
- Adicione a dependência:
Groovy
dependencies { ... implementation "androidx.compose.ui:ui-text-google-fonts:1.2.0-beta01" }
Kotlin
dependencies { ... implementation("androidx.compose.ui:ui-text-google-fonts:1.2.0-beta01") }
- Inicialize a classe
GoogleFont.Provider
com as credenciais da biblioteca Google Fonts.@OptIn(ExperimentalTextApi::class) val provider = GoogleFont.Provider( providerAuthority = "com.google.android.gms.fonts", providerPackage = "com.google.android.gms", certificates = R.array.com_google_android_gms_fonts_certs )
Os parâmetros que o provedor recebe são estes:- A autoridade de provedor de fontes para a biblioteca Google Fonts.
- O pacote de provedor de fontes para confirmar a identidade do provedor.
- Uma lista de conjuntos de hashes para que os certificados confirmem a identidade
do provedor. Você pode encontrar os hashes necessários para o provedor da biblioteca Google Fonts
no arquivo
font_certs.xml
(link em inglês) do app de exemplo JetChat.
ExperimentalTextApi
para poder usar APIs de fontes para download no seu app. - Defina uma
FontFamily
da seguinte maneira:val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = GoogleFont(name), fontProvider = provider) )
Você pode consultar outros parâmetros para sua fonte, como o peso e o estilo, usandoFontWeight
eFontStyle
, respectivamente:val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = GoogleFont(name), fontProvider = provider, weight = FontWeight.Bold, style = FontStyle.Italic) )
- Configure a
FontFamily
que você quer usar na função de composição Text e pronto.Text( fontFamily = fontFamily, text = "Hello World!" )
FontFamily
.val MyTypography = Typography( body1 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = ... ), body2 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.Bold, letterSpacing = ... ), h4 = TextStyle( fontFamily = fontFamily, fontWeight = FontWeight.SemiBold ... ), ...
Em seguida, defina a tipografia para usar o tema do seu app:MyAppTheme( typography = MyTypography ) { ...
Para ver um exemplo de app que está implementando fontes para download no Compose com o Material3, confira o app de exemplo JetChat (links em inglês).
Fontes substitutas
É possível determinar uma cadeia de fontes substitutas que serão usadas caso o download da fonte não seja concluído corretamente. Por exemplo, caso você tenha a fonte para download definida desta forma:
import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider), Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold) )
Você pode definir os padrões da fonte para os dois pesos:
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.googlefonts.Font val fontName = GoogleFont("Lobster Two") val fontFamily = FontFamily( Font(googleFont = fontName, fontProvider = provider), Font(resId = R.font.my_font_regular), Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold), Font(resId = R.font.my_font_regular_bold, weight = FontWeight.Bold) )
Verifique se você está adicionando as importações corretas.
Essa definição cria uma FontFamily
contendo duas cadeias,
uma para cada peso. O mecanismo de carregamento primeiro vai tentar resolver a fonte on-line
e depois a localizada na pasta de recursos R.font
local.
Como depurar a implementação
Para verificar se o download da fonte está sendo feito corretamente, você pode definir um gerenciador de corrotina de depuração. Ele fornece o comportamento do que fazer caso a fonte não carregue de forma assíncrona.
Comece criando uma interface
CoroutineExceptionHandler
(link em inglês).
val handler = CoroutineExceptionHandler { _, throwable ->
// process the Throwable
Log.e(TAG, "There has been an issue: ", throwable)
}
Em seguida, ela precisa ser transmitida para o método
createFontFamilyResolver
para que o resolvedor o use o novo gerenciador:
CompositionLocalProvider(
LocalFontFamilyResolver provides createFontFamilyResolver(LocalContext.current, handler)
) {
Column {
Text(
text = "Hello World!",
style = MaterialTheme.typography.body1
)
}
}
Você também pode usar a API
isAvailableOnDevice
do provedor para testar se ele está disponível e se os certificados estão
configurados corretamente. Para fazer isso, chame o método isAvailableOnDevice
,
que vai retornar um valor falso se o provedor estiver configurado incorretamente.
val context = LocalContext.current
LaunchedEffect(Unit) {
if (provider.isAvailableOnDevice(context)) {
Log.d(TAG, "Success!")
}
}
Avisos
A biblioteca Google Fonts leva vários meses para disponibilizar novas fontes no Android.
Há um intervalo entre quando uma fonte é adicionada na página
fonts.google.com e o momento em que
ela é disponibilizada pela API de fontes para download no sistema de visualização ou
no Compose. As fontes recém-adicionadas podem não ser carregadas no seu app com uma
IllegalStateException
(link em inglês).
Para ajudar os desenvolvedores a identificar esse erro em relação a outros tipos de erro de carregamento de fonte, adicionamos uma mensagem descritiva das mudanças
neste link sobre a exceção no Compose.