Gradienty siatkowe

Gradienty siatkowe tworzą złożone, wielokierunkowe przejścia kolorów za pomocą dwuwymiarowej siatki łatek. W przeciwieństwie do gradientów liniowych i promieniowych gradienty siatkowe płynnie interpolują kolory w siatce. Używaj gradientów siatkowych, aby tworzyć płynne i organiczne elementy estetyczne w interfejsie.

Przykład gradientu siatkowego z wyświetlonymi bieżącymi punktami gradientu siatkowego.
Rysunek 1. Przykład gradientu siatkowego z wyświetlonymi bieżącymi punktami gradientu siatkowego.

Kluczowych pojęć

Aby utworzyć gradient siatki, zdefiniuj wymiary siatki, wierzchołki i przejścia kolorów między punktami:

  • Wymiary siatki: siatka jest dzielona na fragmenty wzdłuż osi pionowej i poziomej. Siatka o wymiarach rowscolumns zawiera (wiersze+1)×(kolumny+1) wierzchołków. Na przykład siatka 1 × 1 składa się z 4 wierzchołków tworzących 1 płat.
  • Znormalizowane współrzędne: wszystkie pozycje wierzchołków korzystają ze znormalizowanego systemu współrzędnych, w którym (0f, 0f) reprezentuje lewy górny róg, a (1f, 1f) – prawy dolny róg obszaru rysowania.
  • Punkty kontrolne Beziera (styczne): każdy wierzchołek zawiera maksymalnie 4 opcjonalne punkty kontrolne Beziera. Te styczne określają krzywiznę krawędzi między sąsiednimi wierzchołkami. Jeśli użyjesz Offset.Unspecified, funkcja Compose wywnioskuje styczne, aby zapewnić płynne przejścia między płatami. Każda komórka siatki utworzona przez 4 wierzchołki wraz z punktami kontrolnymi generuje płat Beziera.
  • Interpolacja kolorów: framework oblicza kolory między głównymi wierzchołkami. Ustaw hasBicubicColor na true, aby włączyć interpolację Catmull-Rom, która zapewnia płynniejsze przejścia kolorów, lub false, aby włączyć interpolację dwuliniową.

Rysuj za pomocą MeshGradientPainter

W Jetpack Compose użyj MeshGradientPainter do renderowania gradientu siatkowego. MeshGradientPainter rysuje na obszarze roboczym.

Tworzenie prostego gradientu siatkowego

Aby utworzyć podstawowy gradient siatki statycznej, zainicjuj MeshGradientPainter, określając jego wymiary i używając funkcji setVertex w bloku konfiguracji, aby umieścić punkty narożne i przypisać im kolory.

val rows = 1
val columns = 1

val gradientPainter = remember {
    MeshGradientPainter(rows, columns) {
        // Parameters: row, column, position, color
        setVertex(0, 0, Offset(0f, 0f), Color.Red)     // Top-Left
        setVertex(0, 1, Offset(1f, 0f), Color.Blue)    // Top-Right
        setVertex(1, 0, Offset(0f, 1f), Color.Green)   // Bottom-Left
        setVertex(1, 1, Offset(1f, 1f), Color.Yellow)  // Bottom-Right
    }
}

Box(
    modifier = modifier
        .aspectRatio(16/9f)
        .fillMaxWidth()
        .paint(gradientPainter)
)

Podstawowy gradient siatkowy z 4 kolorami zdefiniowanymi w każdym rogu
Rysunek 2. Podstawowy gradient siatkowy z 4 kolorami, w którym każdy róg ma jeden z nich.

Używanie konkretnych punktów kontrolnych Beziera

Domyślnie generator siatki wykonuje złożone obliczenia, aby przejścia siatki były płynne. Możesz jednak wyraźnie dostosować styczne w dowolnym pojedynczym wierzchołku, jeśli chcesz selektywnie przesuwać, rozciągać lub mocno ściskać określone sekcje kolorów.

Przesunięcia sterujące są mierzone względem pozycji wierzchołka hosta.

val customTangentPainter = remember {
    MeshGradientPainter(rows = 1, columns = 1) {
        // Tweak the top-left vertex to curve outwards to the right and bottom
        setVertex(
            row = 0,
            column = 0,
            position = Offset(0f, 0f),
            color = Color.Magenta,
            rightControlPoint = Offset(0.4f, 0.1f),
            bottomControlPoint = Offset(0.1f, 0.4f)
        )

        // Other points can remain unspecified to use default inferred fallback tangents
        setVertex(0, 1, Offset(1f, 0f), Color.Cyan)
        setVertex(1, 0, Offset(0f, 1f), Color.Blue)
        setVertex(1, 1, Offset(1f, 1f), Color.Black)
    }
}
Box(
    modifier = modifier
        .aspectRatio(16/9f)
        .fillMaxWidth()
        .paint(customTangentPainter)
)

Gradient siatki z zakrzywionym lewym górnym punktem.
Rysunek 3 Zakrzywienie lewego górnego wierzchołka za pomocą punktu kontrolnego Beziera.

Tworzenie zaawansowanych siatek

Ten przykład przedstawia siatkę 3 x 3, co oznacza, że trzeba określić 16 punktów, a punkty środkowe są ustawione z różnymi przesunięciami:

val points = remember {
    listOf(
        Offset(0.0f, 0.0f), Offset(0.3f, 0.0f), Offset(0.7f, 0.0f), Offset(1.0f, 0.0f),
        Offset(0.0f, 0.3f), Offset(0.2f, 0.4f), Offset(0.7f, 0.2f), Offset(1.0f, 0.3f),
        Offset(0.0f, 0.7f), Offset(0.3f, 0.8f), Offset(0.7f, 0.6f), Offset(1.0f, 0.7f),
        Offset(0.0f, 1.0f), Offset(0.3f, 1.0f), Offset(0.7f, 1.0f), Offset(1.0f, 1.0f)
    )
}

val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
        // Row 0
        setVertex(0, 0, points[0], yellow)
        setVertex(0, 1, points[1], orange)
        setVertex(0, 2, points[2], yellow)
        setVertex(0, 3, points[3], purple)

        // Row 1
        setVertex(1, 0, points[4], pink)
        setVertex(1, 1, points[5], yellow)
        setVertex(1, 2, points[6], pink)
        setVertex(1, 3, points[7], purple)

        // Row 2
        setVertex(2, 0, points[8], indigo)
        setVertex(2, 1, points[9], pink)
        setVertex(2, 2, points[10], purple)
        setVertex(2, 3, points[11], indigo)

        // Row 3
        setVertex(3, 0, points[12], purple)
        setVertex(3, 1, points[13], indigo)
        setVertex(3, 2, points[14], pink)
        setVertex(3, 3, points[15], yellow)
    }
}

Box(
    modifier = modifier.padding(32.dp)
        .aspectRatio(16 / 9f)
        .fillMaxWidth()
        .paint(gradientPainter)
        // ...
)

Gradient siatkowy z punktami kontrolnymi Beziera i kolorami fal oraz punktami siatki zilustrowanymi na jego tle.
Rysunek 4. Gradient siatkowy z punktami kontrolnymi Beziera i kolorami fal oraz punktami siatki zilustrowanymi na jego tle.

Animowanie gradientu siatkowego

Ponieważ parametr lambda block funkcji MeshGradientPainter jest wykonywany w ramach DrawScope, może odczytywać i obserwować stan modyfikowalny. Możesz animować pozycje lub kolory w czasie bez ponownego przydzielania shaderów ani map bitowych.

val infiniteTransition = rememberInfiniteTransition(label = "meshMovement")
val animatedOffset by infiniteTransition.animateFloat(
    initialValue = -0.1f,
    targetValue = 0.1f,
    animationSpec = infiniteRepeatable(
        animation = tween(2500, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "offset"
)

val coral = Color(255, 90, 90)
val peach = Color(255, 139, 90)
val amber = Color(255, 169, 90)
val sunshine = Color(255, 212, 90)
val indigo = Color(0xFF5856D6)
val pink = Color(0xFFFF2D55)


val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
        // Row 0
        setVertex(0, 0, Offset(0.0f, 0.0f), indigo)
        setVertex(0, 1, Offset(0.3f, 0.0f), peach)
        setVertex(0, 2, Offset(0.7f, 0.0f), amber)
        setVertex(0, 3, Offset(1.0f, 0.0f), sunshine)
        // Row 1
        setVertex(1, 0, Offset(0.0f, 0.3f), pink)
        setVertex(1, 1, Offset(0.2f, 0.4f) + Offset(animatedOffset, animatedOffset), coral)
        setVertex(1, 2, Offset(0.7f, 0.2f) + Offset(animatedOffset, animatedOffset), peach)
        setVertex(1, 3, Offset(1.0f, 0.3f), indigo)

        // Row 2
        setVertex(2, 0, Offset(0.0f, 0.7f), coral)
        setVertex(2, 1, Offset(0.3f, 0.8f) + Offset(animatedOffset, 0f), pink)
        setVertex(2, 2, Offset(0.7f, 0.6f) + Offset(animatedOffset, 0f), sunshine)
        setVertex(2, 3, Offset(1.0f, 0.7f), amber)

        // Row 3
        setVertex(3, 0, Offset(0.0f, 1.0f), sunshine)
        setVertex(3, 1, Offset(0.3f, 1.0f), amber)
        setVertex(3, 2, Offset(0.7f, 1.0f), pink)
        setVertex(3, 3, Offset(1.0f, 1.0f), indigo)
    }
}


Box(
    modifier = modifier.padding(32.dp)
        .safeContentPadding()
        .aspectRatio(16 / 9f)
        .fillMaxWidth()
        .paint(gradientPainter)
)

Rysunek 5. Animowany gradient siatkowy z punktami pokazującymi animację.