Gradien jaring

Gradien mesh membuat transisi warna multiarah yang kompleks menggunakan petak patch 2D. Tidak seperti gradien linear atau radial, gradien mesh menginterpolasi warna dengan lancar di seluruh petak 2D. Gunakan gradien mesh, bukan menumpuk beberapa kuas radialGradient atau linearGradient saat membuat latar belakang yang mengalir, organik, atau multi-titik di antarmuka pengguna Anda.

Contoh gradien mesh dengan tampilan titik gradien mesh 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 di antara titik:

  • Dimensi petak: Mesh dibagi menjadi beberapa patch di sepanjang sumbu vertikal dan horizontal. Petak rows dan columns berisi (baris+1)×(kolom+1) verteks. Misalnya, mesh 1×1 terdiri dari 4 verteks yang membentuk satu patch.
  • Koordinat yang dinormalisasi: Semua posisi verteks menggunakan sistem koordinat yang dinormalisasi, dengan (0f, 0f) mewakili kiri atas dan (1f, 1f) 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 tangen untuk memastikan transisi yang mulus di seluruh patch. Setiap sel petak yang dibentuk oleh 4 verteks beserta titik kontrolnya menghasilkan patch bezier.
  • Interpolasi warna: Framework menghitung warna di 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, lakukan inisialisasi MeshGradientPainter dengan menentukan dimensinya dan menggunakan fungsi setVertex di dalam blok konfigurasi untuk memosisikan 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 mesh 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 tersebut.

Menggunakan titik kontrol Bezier tertentu

Secara default, generator mesh menangani penghitungan yang kompleks untuk menjaga transisi petak tetap lancar. Namun, Anda dapat menyesuaikan tangen secara eksplisit pada satu verteks jika ingin mendorong, menarik, atau menjepit 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 mesh dengan titik kiri atas melengkung.
Gambar 3. Lengkungkan verteks kiri atas dengan titik kontrol bezier.

Membuat petak lanjutan

Contoh ini menunjukkan petak 3x3, yang berarti ada 16 titik yang perlu ditentukan, dengan titik tengah 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 mesh dengan titik kontrol bezier dan warna gelombang, serta titik mesh 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 dijalankan 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.