Gradien jaring

Gradien mesh membuat transisi warna yang kompleks dan multi-arah menggunakan petak 2D. Tidak seperti gradien linear atau radial, gradien mesh menginterpolasi warna dengan lancar di seluruh petak. Gunakan gradien mesh untuk membuat elemen estetika yang lancar dan organik di antarmuka pengguna.

Contoh gradien jaring dengan tampilan titik gradien jaring saat ini.
Gambar 1. Contoh gradien mesh dengan tampilan titik gradien mesh saat ini.

Konsep utama

Untuk membuat gradien mesh, tentukan dimensi petak, verteks, dan transisi warna antar-titik:

  • Dimensi petak: Mesh dibagi menjadi petak di sepanjang sumbu vertikal dan horizontal. Petak rows dan columns berisi (rows+1)×(columns+1) verteks. Misalnya, mesh 1×1 terdiri dari 4 verteks yang membentuk satu petak.
  • Koordinat yang dinormalisasi: Semua posisi verteks menggunakan sistem koordinat yang dinormalisasi dengan (0f, 0f) yang mewakili kiri atas dan (1f, 1f) yang mewakili kanan bawah batas gambar.
  • Titik kontrol Bezier (garis singgung): Setiap verteks berisi hingga empat titik kontrol bezier opsional. Garis singgung ini menentukan kelengkungan tepi antara verteks yang berdekatan. Jika Anda menggunakan Offset.Unspecified, Compose akan menyimpulkan garis singgung untuk memastikan transisi yang lancar di seluruh petak. Setiap sel petak yang dibentuk oleh 4 verteks beserta titik kontrolnya akan menghasilkan petak bezier.
  • Interpolasi warna: Framework menghitung warna antara verteks utama. Tetapkan hasBicubicColor ke true untuk interpolasi Catmull-Rom guna mendapatkan pergeseran warna yang lebih halus, atau false untuk interpolasi bilinear.

Menggambar dengan MeshGradientPainter

Di Jetpack Compose, gunakan MeshGradientPainter untuk merender gradien mesh. MeshGradientPainter menggambar di kanvas.

Membuat gradien mesh sederhana

Untuk membuat gradien mesh statis dasar, inisialisasi MeshGradientPainter dengan menentukan dimensinya dan menggunakan fungsi setVertex di dalam blok konfigurasi untuk memposisikan titik sudut dan menetapkan warnanya.

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

Gradien jaring dasar dengan 4 warna yang ditentukan di setiap sudut
Gambar 2. Gradien mesh dasar dengan empat warna, dengan setiap sudut ditetapkan ke salah satu dari empat warna.

Menggunakan titik kontrol Bezier tertentu

Secara default, generator mesh menangani perhitungan kompleks untuk menjaga transisi petak tetap lancar. Namun, Anda dapat menyesuaikan garis singgung secara eksplisit pada satu verteks jika ingin mendorong, menarik, atau mencubit bagian warna tertentu secara selektif.

Offset kontrol diukur relatif terhadap posisi verteks host.

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

Gradien jaring dengan titik kiri atas melengkung.
Gambar 3. Lengkungkan verteks kiri atas dengan titik kontrol bezier.

Membuat petak lanjutan

Contoh ini menunjukkan petak 3 x 3, yang berarti ada 16 titik yang perlu ditentukan, dengan titik tengah yang ditetapkan dengan offset yang berbeda:

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

Gradien jaring dengan titik kontrol bezier dan warna gelombang, serta titik jaring yang diilustrasikan di atasnya.
Gambar 4. Gradien mesh dengan titik kontrol bezier dan warna gelombang, serta titik mesh yang diilustrasikan di atasnya.

Menganimasikan gradien mesh

Karena parameter lambda block dari MeshGradientPainter dieksekusi dalam DrawScope, parameter ini dapat membaca dan mengamati status yang dapat diubah. Anda dapat menganimasikan posisi atau warna dari waktu ke waktu tanpa mengalokasikan ulang shader atau bitmap.

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

Gambar 5. Gradien mesh animasi dengan titik untuk menampilkan animasi.