Brush w Compose opisuje sposób rysowania czegoś na ekranie: określa kolory, które są rysowane w obszarze rysowania (np. koło, kwadrat, ścieżka). Dostępnych jest kilka wbudowanych pędzli, które przydadzą się do rysowania, np. LinearGradient, RadialGradient lub zwykły pędzel SolidColor.
Pędzle można stosować w przypadku wywołań rysowania Modifier.background(), TextStyle lub DrawScope, aby zastosować styl malowania do rysowanej treści.
Na przykład pędzla z poziomym gradientem można użyć do narysowania okręgu w DrawScope:
val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue)) Canvas( modifier = Modifier.size(200.dp), onDraw = { drawCircle(brush) } )
Pędzle gradientowe
Istnieje wiele wbudowanych pędzli gradientowych, które można wykorzystać do uzyskania różnych efektów gradientu. Te pędzle umożliwiają określenie listy kolorów, z których chcesz utworzyć gradient.
Lista dostępnych pędzli gradientowych i odpowiadających im wyników:
| Typ pędzla gradientowego | Urządzenie wyjściowe |
|---|---|
Brush.horizontalGradient(colorList) |
|
Brush.linearGradient(colorList) |
|
Brush.verticalGradient(colorList) |
|
Brush.sweepGradient(colorList)
Uwaga: aby uzyskać płynne przejście między kolorami, ustaw ostatni kolor jako kolor początkowy. |
|
Brush.radialGradient(colorList) |
|
Zmień rozkład kolorów za pomocą colorStops
Aby dostosować wygląd kolorów w gradiencie, możesz zmienić colorStopswartość każdego z nich. colorStops należy podać jako ułamek z zakresu od 0 do 1. Wartości większe niż 1 spowodują, że te kolory nie będą renderowane jako część gradientu.
Możesz skonfigurować punkty kolorów tak, aby zawierały różne ilości, np. mniej lub więcej jednego koloru:
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)) )
Kolory są rozproszone z podanym przesunięciem zdefiniowanym w colorStop
parze, mniej żółte niż czerwone i niebieskie.
Powtórz wzór za pomocą TileMode
Każdy pędzel gradientowy ma opcję ustawienia na nim TileMode. Jeśli nie ustawisz początku i końca gradientu, możesz nie zauważyć ikony TileMode, ponieważ domyślnie wypełni ona cały obszar. Wzór TileMode będzie powielany tylko wtedy, gdy rozmiar obszaru jest większy niż rozmiar pędzla.
Poniższy kod powtórzy wzór gradientu 4 razy, ponieważ endX ma wartość 50.dp, a rozmiar ma wartość 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 ) ) )
W tabeli poniżej znajdziesz szczegółowe informacje o tym, jak poszczególne tryby kafelków działają w przypadku HorizontalGradientprzykładu powyżej:
| Tryb kafelków | Urządzenie wyjściowe |
|---|---|
TileMode.Repeated: krawędź jest powtarzana od ostatniego do pierwszego koloru. |
|
TileMode.Mirror: krawędź jest odzwierciedlana od ostatniego koloru do pierwszego. |
|
TileMode.Clamp: krawędź jest przycinana do koloru końcowego. Następnie wypełni pozostałą część regionu najbliższym kolorem. |
|
TileMode.Decal: renderowanie tylko do rozmiaru granic. TileMode.Decal wykorzystuje przezroczystą czerń do próbkowania treści poza oryginalnymi granicami, a TileMode.Clamp próbkuje kolor krawędzi. |
|
TileMode działa podobnie w przypadku innych gradientów kierunkowych, z tą różnicą, że powtórzenie występuje w innym kierunku.
Zmiana rozmiaru pędzla
Jeśli znasz rozmiar obszaru, w którym będzie rysowany pędzel, możesz ustawić kafel endX, jak pokazano powyżej w sekcji TileMode. Jeśli znajdujesz się w DrawScope, możesz użyć właściwości size, aby uzyskać rozmiar obszaru.
Jeśli nie znasz rozmiaru obszaru rysowania (np. jeśli do elementu Brush przypisany jest tekst), możesz rozszerzyć Shader i wykorzystać rozmiar obszaru rysowania w funkcji createShader.
W tym przykładzie podziel rozmiar przez 4, aby powtórzyć wzór 4 razy:
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) )
Możesz też zmienić rozmiar pędzla w przypadku innych gradientów, np. gradientów promieniowych. Jeśli nie określisz rozmiaru i środka, gradient zajmie całą przestrzeń DrawScope, a środek gradientu promieniowego będzie domyślnie znajdować się w środku przestrzeni DrawScope. W rezultacie środek gradientu promienistego będzie się znajdować na środku mniejszego wymiaru (szerokości lub wysokości):
Box( modifier = Modifier .fillMaxSize() .background( Brush.radialGradient( listOf(Color(0xFF2be4dc), Color(0xFF243484)) ) ) )
Gdy zmienisz gradient promienisty, aby ustawić maksymalny rozmiar promienia, zobaczysz, że daje on lepszy efekt:
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) )
Warto zauważyć, że rzeczywisty rozmiar przekazywany do tworzenia cieniowania jest określany na podstawie miejsca, z którego jest wywoływany. Domyślnie funkcja Brush wewnętrznie ponownie przydzieli pamięć Shader, jeśli rozmiar różni się od ostatniego utworzenia Brush lub jeśli obiekt stanu użyty do utworzenia shadera uległ zmianie.
Poniższy kod tworzy shader 3 razy z różnymi rozmiarami, ponieważ zmienia się rozmiar obszaru rysowania:
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) } } } )
Używanie obrazu jako pędzla
Aby użyć obiektu ImageBitmap jako Brush, wczytaj obraz jako ImageBitmap i utwórz pędzel 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))
Pędzel jest stosowany w przypadku kilku różnych typów rysowania: tła, tekstu i obszaru rysowania. Wynik będzie wyglądać tak:
Zwróć uwagę, że tekst jest teraz renderowany za pomocą parametru ImageBitmap, który służy do rysowania pikseli tekstu.
Zaawansowany przykład: pędzel niestandardowy
Pędzel AGSL RuntimeShader
AGSL oferuje podzbiór funkcji cieniowania GLSL. Shadery można pisać w języku AGSL i używać ich z pędzlem w Compose.
Aby utworzyć pędzel Shader, najpierw zdefiniuj Shader jako ciąg znaków Shadera 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()
Powyższy shader przyjmuje 2 kolory wejściowe, oblicza odległość od lewego dolnego rogu (vec2(0, 1)) obszaru rysowania i wykonuje mix między tymi 2 kolorami na podstawie odległości. Daje to efekt gradientu.
Następnie utwórz pędzel cieniujący i ustaw zmienne uniform dla resolution – rozmiaru obszaru rysowania oraz color i color2, które chcesz wykorzystać jako dane wejściowe dla niestandardowego gradientu:
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) ) }
Po uruchomieniu tego kodu na ekranie zobaczysz:
Warto zauważyć, że za pomocą shaderów można robić znacznie więcej niż tylko gradienty, ponieważ wszystko opiera się na obliczeniach matematycznych. Więcej informacji o AGSL znajdziesz w dokumentacji tego języka.
Dodatkowe materiały
Więcej przykładów użycia pędzla w Compose znajdziesz w tych materiałach:
- Animowanie pędzla Kolorowanie tekstu w Compose 🖌️
- Niestandardowe grafiki i układy w Compose – Android Dev Summit 2022
- JetLagged Sample - RuntimeShader Brush
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony.
- Modyfikatory grafiki
- Grafika w komponowaniu
- Styl tekstu