Pędzel: gradienty i cieniowanie

Symbol Brush w funkcji tworzenia wiadomości opisuje, jak coś jest narysowane na ekranie: określa kolory, które są rysowane w obszarze rysowania (tzn. koło, kwadrat czy ścieżka). Jest kilka wbudowanych pędzli, które przydają się do rysowania. na przykład LinearGradient, RadialGradient lub zwykły pędzel SolidColor.

pędzli można używać z: Modifier.background(), TextStyle oraz DrawScope rysuj wywołania, aby zastosować styl malowania do treści. rysowanie.

Na przykład za pomocą pędzla do rysowania poziomego gradientu można rysować koło DrawScope:

val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue))
Canvas(
    modifier = Modifier.size(200.dp),
    onDraw = {
        drawCircle(brush)
    }
)

Koło narysowane z gradientem poziomym
Rysunek 1. Koło narysowane z gradientem poziomym

Pędzle gradientowe

Jest wiele wbudowanych pędzli gradientowych, za pomocą których można tworzyć Efekty gradientu. Te pędzle umożliwiają określenie listy kolorów Aplikacja chce utworzyć gradient.

Lista dostępnych pędzli gradientu i odpowiadające im dane wyjściowe:

Typ pędzla gradientowego Wyjście
Brush.horizontalGradient(colorList) Gradient poziomy
Brush.linearGradient(colorList) Gradient liniowy
Brush.verticalGradient(colorList) Gradient pionowy
Brush.sweepGradient(colorList)
Uwaga: aby uzyskać płynne przejście między kolorami, jako ostatni ustaw kolor początkowy.
Gradient typu „zamach”
Brush.radialGradient(colorList) Gradient promieniowy

Zmień rozkład kolorów za pomocą funkcji colorStops

Aby dostosować sposób wyświetlania kolorów w gradientie, dostosowuj Wartość każdej kolumny to colorStops. Wartość colorStops powinna być określona jako ułamek, pomiędzy 0 a 1. Jeśli ustawisz wartość większą niż 1, kolory nie będą renderowane. jako część gradientu.

Możesz skonfigurować liczbę stopni, aby różniły się wielkością, np. mniejsze lub więcej z 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 przy podanym przesunięciu, zgodnie z definicją w tabeli colorStop. mniej żółty niż czerwony i niebieski.

Skonfigurowano pędzlem z różnymi kolorami
Rysunek 2.: pędzel skonfigurowany z różnymi stopniami kolorów

Powtórz wzór za pomocą funkcji TileMode

Każdy pędzel gradientowy ma opcję ustawienia TileMode. Nie możesz zwróć uwagę na TileMode, jeśli nie ustawiono początku i końca gradientu. domyślnie wypełnia on cały obszar. Element TileMode będzie wyświetlać kafelki tylko z gradientem jeśli rozmiar obszaru jest większy niż rozmiar pędzla.

Ten kod 4 razy powtórzy wzorzec gradientu, ponieważ endX to ma wartość 50.dp, a rozmiar jest ustawiony na 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 tej tabeli znajdziesz szczegółowe informacje o tym, jak różne tryby kafelka działają w przypadku Przykład powyżej HorizontalGradient:

Tryb kafelka Wyjście
TileMode.Repeated: krawędź jest powtarzana od ostatniego do pierwszego koloru. Powtórzenie TileMode
TileMode.Mirror: odbicie lustrzane krawędzi od ostatniego koloru do pierwszego. Lustro TileMode
TileMode.Clamp: krawędź jest przycięta do ostatecznego koloru. Reszta obszaru zostanie pomalowana na najbliższy kolor. Zacisk trybu kafelka
TileMode.Decal: renderuj tylko do rozmiaru granic. W modelu TileMode.Decal użyto przezroczystej czerni do próbkowania treści poza pierwotnymi granicami, a TileMode.Clamp – kolor krawędzi. Naklejka trybu kafelka

Funkcja TileMode działa podobnie w przypadku innych gradientów kierunkowych – jest to kierunek, w którym następuje powtórzenie.

Zmień rozmiar pędzla

Jeśli znasz rozmiar obszaru, w którym będzie rysowany pędzel, możesz ustaw kafelek endX w taki sposób, jak pokazaliśmy powyżej w sekcji TileMode. Jeśli jesteś w w tabeli DrawScope, możesz użyć jej właściwości size, by określić wielkość obszaru.

Jeśli nie znasz rozmiaru obszaru rysowania (na przykład, jeśli Brush jest przypisany do tekstu), możesz rozszerzyć zakres Shader i wykorzystać rozmiar obszar 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)
)

Rozmiar cieniowania podzielony przez 4
Rysunek 3. Rozmiar cieniowania podzielony przez 4

Możesz też zmienić rozmiar pędzla dowolnego innego gradientu, na przykład promieniowego. gradientów. Jeśli nie określisz rozmiaru i środka, gradient będzie zajmować pełne granice gradientu DrawScope i domyślne ustawienia środka gradientu promieniowego do środka granic DrawScope. W wyniku tego gradient promieniowy jest jako środek mniejszego wymiaru (szerokości lub wysokość):

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(
            Brush.radialGradient(
                listOf(Color(0xFF2be4dc), Color(0xFF243484))
            )
        )
)

Ustawiono gradient promieniowy bez zmiany rozmiaru
Rysunek 4. Zestaw gradientów promieniowych bez zmian rozmiaru

Po zmianie gradientu promieniowego w celu ustawienia promienia na maksymalny wymiar, uzyskasz lepszy efekt gradientu promieniowego:

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)
)

Większy promień w gradientie promieniowym, w zależności od rozmiaru obszaru.
Rysunek 5. Większy promień w przypadku gradientu promieniowego (w zależności od wielkości powierzchni)

Warto zauważyć, że rzeczywisty rozmiar przekazywany przy tworzeniu Moduł cieniujący jest określany na podstawie miejsca wywołania. Domyślnie Brush będzie zmienić przydział elementu Shader wewnętrznie, jeśli rozmiar różni się od ostatniego elementu Brush lub jeśli obiekt stanu używany do tworzenia cieniowania został została zmieniona.

Ten kod tworzy cieniowanie trzy razy z różnymi wartościami wraz ze zmianą rozmiaru 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ć 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 można stosować do kilku różnych rodzajów rysowania: tła, i Canvas. Zwrócony wynik:

Różne sposoby używania pędzla ImageShader
Rysunek 6. Rysowanie tła, rysowanie tekstu i okrąg za pomocą pędzla ImageShader

Zwróć uwagę, że tekst jest teraz również renderowany za pomocą komponentu ImageBitmap do malowania pikseli dla tekstu.

Przykład zaawansowany: niestandardowy pędzel

Szczoteczka AGSL RuntimeShader

AGSL udostępnia podzbiór funkcji Shadera GLSL. Ścianki napisane w języku migowym i używane za pomocą pędzla w funkcji Compose.

Aby utworzyć pędzel do cieniowania, najpierw zdefiniuj ciąg Shader jako ciąg AGSL do cieniowania:

@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 cieniowanie przyjmuje 2 kolory wejściowe, oblicza odległość od dołu po lewej (vec2(0, 1)) obszaru rysowania i wykonuje mix między dwoma kolorami na podstawie odległości. Daje to efekt gradientu.

Następnie utwórz pędzel do cieni i ustaw uniformy dla urządzenia resolution – jego rozmiaru obszaru rysowania oraz elementów color i color2, których chcesz użyć jako danych wejściowych Twój niestandardowy gradient:

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)
    )
}

Gdy go uruchomisz, na ekranie zobaczysz taki kod:

Niestandardowy Shader AGSL działający w funkcji Compose
Rysunek 7. Niestandardowy Shader AGSL działający podczas tworzenia wiadomości

Warto zauważyć, że z cieniowaniem można zrobić znacznie więcej niż tylko za pomocą gradientów. Wszystkie obliczenia są oparte na matematyce. Więcej informacji o amerykańskim języku migowym znajdziesz w Dokumentacja AGSL.

Dodatkowe materiały

Więcej przykładów użycia pędzla w funkcji tworzenia wiadomości znajdziesz w tych materiałach:

. .