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

Dostępnych jest wiele wbudowanych pędzli gradientów, które można wykorzystać do uzyskania różnych efektów 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 gradientu Wyjście
Brush.horizontalGradient(colorList) Gradient poziomy
Brush.linearGradient(colorList) Gradient liniowy
Brush.verticalGradient(colorList) Pionowy gradient
Brush.sweepGradient(colorList)
Uwaga: aby uzyskać płynne przejście między kolorami, ustaw ostatni kolor jako kolor początkowy.
Gradient typu „zamach”
Brush.radialGradient(colorList) Gradient promienisty

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

Aby dostosować sposób wyświetlania kolorów w gradientach, możesz zmienić wartość parametru colorStops dla każdego z nich. 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 gradientowy pędzel ma opcję 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. TileMode będzie stosować gradient tylko wtedy, gdy 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:

TileMode Wyjście
TileMode.Repeated: krawędź jest powtarzana od ostatniego do pierwszego koloru. TileMode Repeated
TileMode.Mirror: odbicie lustrzane krawędzi od ostatniego koloru do pierwszego. TileMode Mirror
TileMode.Clamp: krawędzie są przycinane 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 w podobny sposób w przypadku innych gradientów kierunkowych, z tą różnicą, że powtórzenie występuje w innym kierunku.

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 efekcie środek gradientu promieniowego będzie znajdować się w środku mniejszego wymiaru (szerokości lub wysokości):

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

Gradient promieniowy bez zmian 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 jest stosowany do kilku różnych typów rysunków: tła, tekstu i płótna. Zwrócony wynik:

Pędzel ImageShader używany na różne sposoby
Rysunek 6. Rysowanie tła, rysowanie tekstu i okrąg za pomocą pędzla ImageShader

Zwróć uwagę, że tekst jest teraz renderowany za pomocą ImageBitmap, aby wypełnić piksele 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. Powoduje 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 fragment kodu:

Niestandardowy Shader AGSL działający w funkcji Compose
Rysunek 7. Niestandardowy shader AGSL działający w Compose

Warto pamiętać, że shadery mogą służyć do innych celów niż tylko do tworzenia gradientów, ponieważ są to obliczenia 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: