Vários apps exigem um controle preciso sobre o que é desenhado na tela. Isso pode ser algo simples, como colocar uma caixa ou um círculo no lugar certo da tela, ou pode ser uma organização elaborada de elementos gráficos em vários estilos diferentes.
Desenho básico com modificadores e DrawScope
A principal forma de desenhar algo personalizado no Compose é com modificadores, como
Modifier.drawWithContent
,
Modifier.drawBehind
e
Modifier.drawWithCache
.
Por exemplo, se quiser desenhar algo atrás do seu elemento combinável, você pode usar o
modificador drawBehind
para começar a executar comandos de desenho:
Spacer( modifier = Modifier .fillMaxSize() .drawBehind { // this = DrawScope } )
Se você só precisa de um combinável, use o elemento
Canvas
. Ele atua como um
wrapper conveniente ao redor do Modifier.drawBehind
. O Canvas
é posicionado
no layout da mesma forma que qualquer outro elemento de interface do Compose. Dentro do
Canvas
, é possível desenhar elementos com controle preciso sobre o estilo e a
localização.
Todos os modificadores de desenho expõem um DrawScope
, um ambiente de desenho com escopo,
que mantém o próprio estado. Isso permite que você defina os parâmetros de um grupo de
elementos gráficos. O DrawScope
fornece vários campos úteis, como size
,
um objeto Size
que especifica as dimensões atuais do DrawScope
.
Para desenhar algo, você pode usar uma das várias funções de desenho no DrawScope
. Por
exemplo, o código a seguir desenha um retângulo no canto superior esquerdo da
tela:
Canvas(modifier = Modifier.fillMaxSize()) { val canvasQuadrantSize = size / 2F drawRect( color = Color.Magenta, size = canvasQuadrantSize ) }
![Retângulo rosa desenhado em um fundo branco que ocupa um quarto da tela](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_draw_rect.png?authuser=19&hl=pt)
Para saber mais sobre os diferentes modificadores de desenho, consulte a documentação Modificadores gráficos.
Sistema de coordenadas
Para desenhar algo na tela, é necessário saber o deslocamento (x
e y
) e o tamanho do
item. Em vários dos métodos de desenho no DrawScope
, a posição e o tamanho
são fornecidos por valores de parâmetro padrão. Os parâmetros padrão geralmente
posicionam o item no ponto [0, 0]
da tela e fornecem um size
padrão que preenche toda a área de desenho, como no exemplo acima. Observe
que o retângulo está posicionado no canto superior esquerdo. Para ajustar o tamanho e a posição do
item, é necessário entender o sistema de coordenadas no Compose.
A origem do sistema de coordenadas ([0,0]
) está no pixel superior esquerdo na
área de desenho. O x
aumenta com o movimento para a direita, e o y
com o movimento
para baixo.
![Grade com o sistema de coordenadas mostrando o canto superior esquerdo [0, 0] e o canto inferior direito [largura, altura]](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_coordinate_system_drawing.png?authuser=19&hl=pt)
Por exemplo, se você quiser desenhar uma linha diagonal do canto superior direito
da área da tela até o canto inferior esquerdo, use a função
DrawScope.drawLine()
e especifique o deslocamento inicial e final com
as posições correspondentes de x e y:
Canvas(modifier = Modifier.fillMaxSize()) { val canvasWidth = size.width val canvasHeight = size.height drawLine( start = Offset(x = canvasWidth, y = 0f), end = Offset(x = 0f, y = canvasHeight), color = Color.Blue ) }
Transformações básicas
O DrawScope
oferece transformações para mudar onde ou como os comandos de desenho
são executados.
Escala
Use
DrawScope.scale()
para aumentar o tamanho das operações de desenho por um fator. Operações como
scale()
são válidas para todas as operações de desenho no lambda correspondente. Por
exemplo, o código a seguir aumenta a scaleX
em 10 vezes e a scaleY
em 15
vezes:
Canvas(modifier = Modifier.fillMaxSize()) { scale(scaleX = 10f, scaleY = 15f) { drawCircle(Color.Blue, radius = 20.dp.toPx()) } }
![Um círculo dimensionado de maneira não uniforme](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_scale.png?authuser=19&hl=pt)
Translação
Use
DrawScope.translate()
para mover as operações de desenho para cima, para baixo, para a esquerda ou para a direita. Por exemplo, o
código abaixo move o desenho 100 px para a direita e 300 px para cima:
Canvas(modifier = Modifier.fillMaxSize()) { translate(left = 100f, top = -300f) { drawCircle(Color.Blue, radius = 200.dp.toPx()) } }
![Um círculo movido para fora do centro](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_translate.png?authuser=19&hl=pt)
Rotação
Use
DrawScope.rotate()
para girar as operações de desenho em torno de um ponto. Por exemplo, o
código a seguir gira um retângulo 45 graus:
Canvas(modifier = Modifier.fillMaxSize()) { rotate(degrees = 45F) { drawRect( color = Color.Gray, topLeft = Offset(x = size.width / 3F, y = size.height / 3F), size = size / 3F ) } }
![Um smartphone com um retângulo rotacionado em 45 graus no centro da tela](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_rotate.png?authuser=19&hl=pt)
rotate()
para aplicar uma rotação ao escopo de desenho atual, girando o retângulo em 45 graus.
Recuo
Use DrawScope.inset()
para ajustar os parâmetros padrão do DrawScope
atual, mudando os limites e fazendo a translação dos desenhos
adequadamente:
Canvas(modifier = Modifier.fillMaxSize()) { val canvasQuadrantSize = size / 2F inset(horizontal = 50f, vertical = 30f) { drawRect(color = Color.Green, size = canvasQuadrantSize) } }
Esse código adiciona padding aos comandos de desenho:
![Um retângulo com padding ao redor dele](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_inset.png?authuser=19&hl=pt)
Várias transformações
Para aplicar várias transformações aos seus desenhos, use a função
DrawScope.withTransform()
. Ela cria e
aplica uma única transformação que combina todas as mudanças desejadas. Usar
withTransform()
é mais eficiente do que fazer chamadas aninhadas para transformações
individuais. Isso ocorre porque todas as transformações são executadas em uma
única operação, e o Compose não precisa calcular
e salvar cada uma das transformações aninhadas.
Por exemplo, o código a seguir aplica uma translação e uma rotação ao retângulo:
Canvas(modifier = Modifier.fillMaxSize()) { withTransform({ translate(left = size.width / 5F) rotate(degrees = 45F) }) { drawRect( color = Color.Gray, topLeft = Offset(x = size.width / 3F, y = size.height / 3F), size = size / 3F ) } }
![Um smartphone com um retângulo rotacionado, deslocado para a lateral da tela](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_multiple_transforms.png?authuser=19&hl=pt)
withTransform
foi usada para aplicar uma rotação e uma
translação, girando e deslocando o retângulo para a esquerda.Operações comuns de desenho
Desenhar texto
Para desenhar texto no Compose, normalmente você pode usar o elemento combinável Text
. No entanto,
se você estiver em um DrawScope
ou quiser desenhar o texto manualmente com
personalização, use o
método
DrawScope.drawText()
.
Para desenhar texto, crie um TextMeasurer
usando rememberTextMeasurer
e chame drawText
com o medidor:
val textMeasurer = rememberTextMeasurer() Canvas(modifier = Modifier.fillMaxSize()) { drawText(textMeasurer, "Hello") }
![O texto "Hello" aparece desenhado no Canvas](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_draw_text.png?authuser=19&hl=pt)
Medir o texto
Desenhar texto funciona de forma um pouco diferente de outros comandos de desenho. Normalmente, você dá ao comando de desenho o tamanho (largura e altura) para desenhar a forma/imagem. No caso do texto, há alguns parâmetros que controlam o tamanho renderizado, como tamanho da fonte, tipos de fontes, ligaduras e espaçamento entre letras.
Com o Compose, é possível usar um TextMeasurer
para ter acesso ao tamanho
de texto medido, dependendo dos fatores acima. Se você quiser desenhar um plano de fundo
por trás do texto, poderá usar as informações medidas para descobrir o tamanho da
área em que o texto aparece:
val textMeasurer = rememberTextMeasurer() Spacer( modifier = Modifier .drawWithCache { val measuredText = textMeasurer.measure( AnnotatedString(longTextSample), constraints = Constraints.fixedWidth((size.width * 2f / 3f).toInt()), style = TextStyle(fontSize = 18.sp) ) onDrawBehind { drawRect(pinkColor, size = measuredText.size.toSize()) drawText(measuredText) } } .fillMaxSize() )
Este snippet de código produz um plano de fundo rosa para o texto:
![Texto com várias linhas ocupando ⅔ da área total e um retângulo como plano de fundo](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_draw_text_measured.png?authuser=19&hl=pt)
Ajustar as restrições, o tamanho da fonte ou qualquer propriedade que afete o tamanho medido
resulta em um novo tamanho informado. É possível definir um tamanho fixo para a width
e a height
, e o texto vai seguir o TextOverflow
definido. Por
exemplo, o código a seguir renderiza o texto em ⅓ da altura e ⅓ da largura
da área combinável e define o TextOverflow
como TextOverflow.Ellipsis
:
val textMeasurer = rememberTextMeasurer() Spacer( modifier = Modifier .drawWithCache { val measuredText = textMeasurer.measure( AnnotatedString(longTextSample), constraints = Constraints.fixed( width = (size.width / 3f).toInt(), height = (size.height / 3f).toInt() ), overflow = TextOverflow.Ellipsis, style = TextStyle(fontSize = 18.sp) ) onDrawBehind { drawRect(pinkColor, size = measuredText.size.toSize()) drawText(measuredText) } } .fillMaxSize() )
Agora, o texto é desenhado dentro das restrições, com reticências no final:
![Texto desenhado em um plano de fundo rosa, terminando com reticências](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_draw_text_ellipsis.png?authuser=19&hl=pt)
TextOverflow.Ellipsis
com restrições fixas na medição do texto.Desenhar imagem
Para desenhar uma ImageBitmap
com DrawScope
, carregue a imagem usando
ImageBitmap.imageResource()
e, em seguida, chame drawImage
:
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog) Canvas(modifier = Modifier.fillMaxSize(), onDraw = { drawImage(dogImage) })
![Imagem de um cachorro desenhada no Canvas](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_image.png?authuser=19&hl=pt)
ImageBitmap
no Canvas.Desenhar formas básicas
Há várias funções de desenho de forma no DrawScope
. Para desenhar uma forma, use uma
das funções de desenho predefinidas, como drawCircle
:
val purpleColor = Color(0xFFBA68C8) Canvas( modifier = Modifier .fillMaxSize() .padding(16.dp), onDraw = { drawCircle(purpleColor) } )
API |
Saída |
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
Desenhar caminho
Um caminho é uma série de instruções matemáticas que resultam em um desenho
depois de executado. O DrawScope
pode desenhar um caminho usando o método DrawScope.drawPath()
.
Por exemplo, digamos que você queira desenhar um triângulo. É possível gerar um caminho com
funções como lineTo()
e moveTo()
usando o tamanho da área de desenho.
Em seguida, chame drawPath()
com esse caminho recém-criado para que um triângulo apareça na tela.
Spacer( modifier = Modifier .drawWithCache { val path = Path() path.moveTo(0f, 0f) path.lineTo(size.width / 2f, size.height / 2f) path.lineTo(size.width, 0f) path.close() onDrawBehind { drawPath(path, Color.Magenta, style = Stroke(width = 10f)) } } .fillMaxSize() )
![Triângulo de caminho roxo invertido desenhado no Compose](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_draw_path.png?authuser=19&hl=pt)
Path
criado e desenhado no Compose.Como acessar o objeto Canvas
Com o DrawScope
, você não tem acesso direto a um objeto Canvas
. Você pode usar
DrawScope.drawIntoCanvas()
para ter
acesso a ele e usá-lo para chamar funções.
Por exemplo, se você tem um Drawable
personalizado em que gostaria de desenhar,
acesse a tela e chame Drawable#draw()
, transmitindo o
objeto Canvas
:
val drawable = ShapeDrawable(OvalShape()) Spacer( modifier = Modifier .drawWithContent { drawIntoCanvas { canvas -> drawable.setBounds(0, 0, size.width.toInt(), size.height.toInt()) drawable.draw(canvas.nativeCanvas) } } .fillMaxSize() )
![Um ShapeDrawable preto oval ocupando todo o espaço](https://developer.android.google.cn/static/develop/ui/compose/images/graphics/introduction/compose_graphics_canvas_draw_into_canvas.png?authuser=19&hl=pt)
Drawable
.Saiba mais
Para mais informações sobre como desenhar no Compose, consulte estes recursos:
- Modificadores gráficos: saiba mais sobre os diferentes tipos de modificadores de desenho.
- Pincel: aprenda a personalizar a pintura do seu conteúdo.
- Layouts e elementos gráficos personalizados no Compose: Conferência de Desenvolvedores Android 2022: aprenda a criar uma interface personalizada no Compose com layouts e elementos gráficos.
- Exemplo do JetLagged (link em inglês): exemplo do Compose que mostra como desenhar um gráfico personalizado.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Modificadores gráficos
- Gráficos no Compose
- Linhas de alinhamento no Jetpack Compose