Um Brush
(pincel) no Compose descreve como algo é desenhado na tela. Ele
determina as cores que estão na área de desenho (ou seja, um círculo,
quadrado ou caminho). Alguns pincéis integrados são úteis para desenhar,
como LinearGradient
, RadialGradient
ou um pincel
SolidColor
simples.
Os pincéis podem ser usados com chamadas de desenho Modifier.background()
, TextStyle
ou
DrawScope
para aplicar o estilo de pintura ao conteúdo
que está sendo desenhado.
Por exemplo, um pincel de gradiente horizontal pode ser usado para desenhar um círculo em
DrawScope
:
val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue)) Canvas( modifier = Modifier.size(200.dp), onDraw = { drawCircle(brush) } )
Pincéis de gradiente
Vários pincéis de gradiente integrados podem ser usados para alcançar diferentes efeitos de gradiente. Eles pincéis permitem especificar a lista de cores com a qual você quer criar um gradiente.
A seguir, incluímos uma lista dos pincéis de gradiente disponíveis e a saída correspondente:
Tipo de pincel de gradiente | Saída |
---|---|
Brush.horizontalGradient(colorList) |
|
Brush.linearGradient(colorList) |
|
Brush.verticalGradient(colorList) |
|
Brush.sweepGradient(colorList)
Observação: para fazer uma transição suave entre cores, defina a última cor como a inicial. |
|
Brush.radialGradient(colorList) |
Mudar a distribuição de cores com colorStops
Para personalizar a forma como as cores aparecem no gradiente, ajuste o
valor colorStops
de cada uma. Ele precisa ser especificado como uma fração,
entre 0 e 1. Valores maiores que 1 fazem com que essas cores não sejam renderizadas
como parte do gradiente.
Você pode configurar as paradas de cor para terem quantidades diferentes (por exemplo, menos ou mais de uma cor):
val colorStops = arrayOf( 0.0f to Color.Yellow, 0.2f to Color.Red, 1f to Color.Blue ) Box( modifier = Modifier .requiredSize(200.dp) .background(Brush.horizontalGradient(colorStops = colorStops)) )
As cores são dispersadas na compensação fornecida conforme definido no par de colorStop
,
com menos amarelo do que vermelho e azul.
Repetir um padrão com TileMode
Cada pincel de gradiente tem a opção de definir um TileMode
. Se você não definiu um início e um fim para o gradiente, talvez não
perceba o TileMode
, já que
o padrão é preencher toda a área. Um TileMode
só vai agrupar o gradiente
se o tamanho da área for maior que o do pincel.
O código a seguir repetirá o padrão do gradiente quatro vezes, já que
endX
está definido como 50.dp
e o tamanho está definido como 200.dp
:
val listColors = listOf(Color.Yellow, Color.Red, Color.Blue) val tileSize = with(LocalDensity.current) { 50.dp.toPx() } Box( modifier = Modifier .requiredSize(200.dp) .background( Brush.horizontalGradient( listColors, endX = tileSize, tileMode = TileMode.Repeated ) ) )
Confira uma tabela que detalha o que os diferentes modos de bloco fazem no
exemplo de HorizontalGradient
acima:
TileMode | Saída |
---|---|
TileMode.Repeated : a borda é repetida da última cor para a primeira. |
|
TileMode.Mirror : a borda é espelhada da última cor para a primeira. |
|
TileMode.Clamp : a borda é limitada à cor final. Então, a cor mais próxima é usada para o restante da região. |
|
TileMode.Decal : renderiza apenas até os limites. O TileMode.Decal usa a cor preta transparente para aproveitar o conteúdo fora dos limites originais, enquanto o TileMode.Clamp utiliza a cor das bordas |
O TileMode
funciona de maneira semelhante para os outros gradientes direcionais. A
diferença está na direção em que a repetição ocorre.
Mudar o tamanho do pincel
Se você sabe o tamanho da área em que o pincel vai ser desenhado, defina
o bloco endX
como vimos acima na seção TileMode
. Se você está em
um DrawScope
, pode usar a propriedade size
para saber o tamanho da área.
Caso não saiba o tamanho da área de desenho (por exemplo, se
o Brush
estiver atribuído a "Text"), estenda o Shader
e use o tamanho
da área na função createShader
.
Neste exemplo, divida o tamanho por quatro para repetir o padrão quatro vezes:
val listColors = listOf(Color.Yellow, Color.Red, Color.Blue) val customBrush = remember { object : ShaderBrush() { override fun createShader(size: Size): Shader { return LinearGradientShader( colors = listColors, from = Offset.Zero, to = Offset(size.width / 4f, 0f), tileMode = TileMode.Mirror ) } } } Box( modifier = Modifier .requiredSize(200.dp) .background(customBrush) )
Também é possível mudar o tamanho do pincel de qualquer outro gradiente, como gradientes
radiais. Se você não especificar um tamanho e um centro, o gradiente ocupará os
limites completos do DrawScope
, e o centro do gradiente radial vai assumir como padrão
o centro dos limites do DrawScope
. O resultado é o centro do gradiente radial
aparecendo como o centro da dimensão menor (largura ou
altura):
Box( modifier = Modifier .fillMaxSize() .background( Brush.radialGradient( listOf(Color(0xFF2be4dc), Color(0xFF243484)) ) ) )
Quando o gradiente radial é mudado para usar a dimensão máxima como o tamanho do raio, é possível notar que um efeito melhor é produzido:
val largeRadialGradient = object : ShaderBrush() { override fun createShader(size: Size): Shader { val biggerDimension = maxOf(size.height, size.width) return RadialGradientShader( colors = listOf(Color(0xFF2be4dc), Color(0xFF243484)), center = size.center, radius = biggerDimension / 2f, colorStops = listOf(0f, 0.95f) ) } } Box( modifier = Modifier .fillMaxSize() .background(largeRadialGradient) )
É importante observar que o tamanho real transmitido para a criação do
sombreador depende de onde ele é invocado. Por padrão, o Brush
realoca
o Shader
internamente quando o tamanho é diferente
da última criação do Brush
ou se um objeto de estado usado na criação do sombreador
mudou.
O código abaixo cria o sombreador três vezes com tamanhos diferentes, conforme o tamanho da área de desenho muda:
val colorStops = arrayOf( 0.0f to Color.Yellow, 0.2f to Color.Red, 1f to Color.Blue ) val brush = Brush.horizontalGradient(colorStops = colorStops) Box( modifier = Modifier .requiredSize(200.dp) .drawBehind { drawRect(brush = brush) // will allocate a shader to occupy the 200 x 200 dp drawing area inset(10f) { /* Will allocate a shader to occupy the 180 x 180 dp drawing area as the inset scope reduces the drawing area by 10 pixels on the left, top, right, bottom sides */ drawRect(brush = brush) inset(5f) { /* will allocate a shader to occupy the 170 x 170 dp drawing area as the inset scope reduces the drawing area by 5 pixels on the left, top, right, bottom sides */ drawRect(brush = brush) } } } )
Usar uma imagem como um pincel
Para usar uma ImageBitmap como um Brush
, carregue a imagem como uma ImageBitmap
e crie um pincel ImageShader
:
val imageBrush = ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.dog))) // Use ImageShader Brush with background Box( modifier = Modifier .requiredSize(200.dp) .background(imageBrush) ) // Use ImageShader Brush with TextStyle Text( text = "Hello Android!", style = TextStyle( brush = imageBrush, fontWeight = FontWeight.ExtraBold, fontSize = 36.sp ) ) // Use ImageShader Brush with DrawScope#drawCircle() Canvas(onDraw = { drawCircle(imageBrush) }, modifier = Modifier.size(200.dp))
O pincel é usado em alguns tipos diferentes de desenho: fundo, texto e tela. Isso resulta no seguinte:
O texto agora também é renderizado usando a ImageBitmap
para pintar os
pixels dele.
Exemplo avançado: pincel personalizado
Escova RuntimeShader
da AGSL
A AGSL oferece um subconjunto de recursos sombreadores da GLSL. Os sombreadores podem ser programados em AGSL e usados com um pincel no Compose.
Para criar esse pincel, primeiro defina o sombreador como uma string de sombreador da AGSL:
@Language("AGSL") val CUSTOM_SHADER = """ uniform float2 resolution; layout(color) uniform half4 color; layout(color) uniform half4 color2; half4 main(in float2 fragCoord) { float2 uv = fragCoord/resolution.xy; float mixValue = distance(uv, vec2(0, 1)); return mix(color, color2, mixValue); } """.trimIndent()
O sombreador acima usa duas cores de entrada, calcula a distância em relação ao canto inferior esquerdo
(vec2(0, 1)
) da área de desenho e faz um mix
entre as duas cores
com base na distância. Isso produz um efeito de gradiente.
Em seguida, crie o pincel sombreador e defina os uniformes para a resolution
, que é o tamanho
da área de desenho, e a color
e color2
que você quer usar como entrada para
o gradiente personalizado:
val Coral = Color(0xFFF3A397) val LightYellow = Color(0xFFF8EE94) @RequiresApi(Build.VERSION_CODES.TIRAMISU) @Composable @Preview fun ShaderBrushExample() { Box( modifier = Modifier .drawWithCache { val shader = RuntimeShader(CUSTOM_SHADER) val shaderBrush = ShaderBrush(shader) shader.setFloatUniform("resolution", size.width, size.height) onDrawBehind { shader.setColorUniform( "color", android.graphics.Color.valueOf( LightYellow.red, LightYellow.green, LightYellow .blue, LightYellow.alpha ) ) shader.setColorUniform( "color2", android.graphics.Color.valueOf( Coral.red, Coral.green, Coral.blue, Coral.alpha ) ) drawRect(shaderBrush) } } .fillMaxWidth() .height(200.dp) ) }
Com isso, a tela mostra o seguinte:
Vale ressaltar que é possível fazer muito mais com sombreadores do que apenas gradientes, porque eles são cálculos matemáticos. Para mais informações, consulte a documentação da AGSL.
Outros recursos
Para mais exemplos de como usar o pincel no Compose, consulte estes recursos:
- Animar a coloração de texto com pincéis no Compose 🖌️ (link em inglês)
- Gráficos e layouts personalizados no Compose: Conferência de Desenvolvedores Android 2022
- Exemplo do JetLagged: pincel RuntimeShader (link em inglês)
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Modificadores gráficos
- Gráficos no Compose
- Aplicar estilo ao texto