การไล่ระดับสีแบบตาข่าย

การไล่ระดับสีแบบเมชจะสร้างการเปลี่ยนสีแบบหลายทิศทางที่ซับซ้อนโดยใช้ตารางกริด 2 มิติของแพตช์ การไล่ระดับสีแบบเมชจะประมาณค่าสีในตารางกริด 2 มิติได้อย่างราบรื่น ซึ่งแตกต่างจากการไล่ระดับสีแบบเส้นตรงหรือแบบรัศมี ใช้การไล่ระดับสีแบบเมชแทนการซ้อนแปรง radialGradient หรือ linearGradient หลายๆ อันเมื่อสร้างพื้นหลังแบบไหล แบบออร์แกนิก หรือแบบหลายจุดในอินเทอร์เฟซผู้ใช้

ตัวอย่างการไล่ระดับสีแบบเมชที่แสดงจุดไล่ระดับสีแบบเมชปัจจุบัน
รูปที่ 1 ตัวอย่างการไล่ระดับสีแบบเมชพร้อมการแสดงจุดไล่ระดับสีแบบเมชปัจจุบัน

แนวคิดสำคัญ

หากต้องการสร้างการไล่ระดับสีแบบเมช ให้กำหนดขนาดตารางกริด จุดยอด และการเปลี่ยนสีระหว่างจุดต่างๆ ดังนี้

  • ขนาดตารางกริด: เมชจะแบ่งออกเป็นแพตช์ตามแกนแนวตั้งและแนวนอน ตารางกริดขนาด rows และ columns มีจุดยอด (แถว+1)×(คอลัมน์+1) จุด ตัวอย่างเช่น เมช 1×1 ประกอบด้วยจุดยอด 4 จุดที่รวมกันเป็นแพตช์ 1 รายการ
  • พิกัดที่ปรับมาตรฐาน: ตำแหน่งจุดยอดทั้งหมดใช้ระบบพิกัดที่ปรับมาตรฐาน โดยที่ (0f, 0f) แสดงถึงมุมซ้ายบน และ (1f, 1f) แสดงถึงมุมขวาล่างของขอบเขตการวาด
  • จุดควบคุม Bezier (แทนเจนต์): จุดยอดแต่ละจุดมีจุดควบคุม Bezier ที่ไม่บังคับได้สูงสุด 4 จุด เส้นสัมผัสเหล่านี้จะระบุความโค้งของขอบระหว่างจุดยอดที่อยู่ติดกัน หากคุณใช้ Offset.Unspecified, Compose จะอนุมานแทนเจนต์เพื่อให้แน่ใจว่าการเปลี่ยนผ่านระหว่างแพตช์ต่างๆ เป็นไปอย่างราบรื่น เซลล์ตารางกริดแต่ละเซลล์ที่เกิดจากจุดยอด 4 จุดพร้อมกับจุดควบคุมจะสร้างแพตช์เบซิเยร์
  • การประมาณค่าสี: เฟรมเวิร์กจะคำนวณสีระหว่างจุดยอดหลัก ตั้งค่า hasBicubicColor เป็น true สำหรับการประมาณค่าในช่วงแบบ Catmull-Rom เพื่อให้การเปลี่ยนสีราบรื่นขึ้น หรือ false สำหรับการประมาณค่าในช่วงแบบ Bilinear

วาดด้วย MeshGradientPainter

ใน Jetpack Compose ให้ใช้ MeshGradientPainter เพื่อแสดงผลการไล่ระดับสีแบบเมช MeshGradientPainter วาดบน Canvas

สร้างการไล่ระดับสีแบบเมชอย่างง่าย

หากต้องการสร้างการไล่ระดับสีแบบเมชคงที่พื้นฐาน ให้เริ่มต้น MeshGradientPainter โดยระบุมิติข้อมูลและใช้ฟังก์ชัน setVertex ภายในบล็อกการกำหนดค่าเพื่อจัดตำแหน่งจุดมุมและกำหนดสี

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

การไล่ระดับสีแบบเมชพื้นฐานที่มี 4 สีที่กำหนดไว้ที่แต่ละมุม
รูปที่ 2 การไล่ระดับสีแบบเมชพื้นฐานที่มี 4 สี โดยแต่ละมุมจะตั้งค่าเป็นสีใดสีหนึ่งจาก 4 สี

ใช้จุดควบคุม Bezier ที่เฉพาะเจาะจง

โดยค่าเริ่มต้น เครื่องมือสร้าง Mesh จะจัดการการคำนวณที่ซับซ้อนเพื่อให้การเปลี่ยนตารางกริดเป็นไปอย่างราบรื่น อย่างไรก็ตาม คุณสามารถปรับแต่งเส้นสัมผัสบนจุดยอดจุดเดียวได้อย่างชัดเจนหากต้องการเลือกดัน ดึง หรือบีบส่วนสีบางส่วนอย่างชัดเจน

ระบบจะวัดออฟเซ็ตการควบคุมโดยสัมพันธ์กับตำแหน่งของจุดยอดโฮสต์

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

การไล่ระดับสีแบบเมชที่มีจุดด้านซ้ายบนโค้งมน
รูปที่ 3 ทำให้จุดยอดด้านซ้ายบนโค้งด้วยจุดควบคุมเบซิเยร์

สร้างตารางกริดขั้นสูง

ตัวอย่างนี้แสดงตารางกริดขนาด 3 คูณ 3 ซึ่งหมายความว่ามี 16 จุดที่ต้องระบุ โดยจุดตรงกลางจะตั้งค่าด้วยออฟเซ็ตที่แตกต่างกัน ดังนี้

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

การไล่ระดับสีแบบเมชที่มีจุดควบคุมเบซิเยร์และสีคลื่น และจุดเมชที่แสดงอยู่ด้านบน
รูปที่ 4 การไล่ระดับสีแบบเมชที่มีจุดควบคุมเบซิเยร์และสีคลื่น พร้อมภาพประกอบจุดเมชที่ด้านบน

สร้างภาพเคลื่อนไหวให้กับการไล่ระดับสีแบบเมช

เนื่องจากพารามิเตอร์ Lambda block ของ MeshGradientPainter ทำงานภายใน DrawScope จึงสามารถอ่านและสังเกตสถานะที่เปลี่ยนแปลงได้ คุณสามารถทำให้ตำแหน่งหรือสีเคลื่อนไหวเมื่อเวลาผ่านไปโดยไม่ต้องจัดสรรเฉดสีหรือบิตแมปใหม่

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

รูปที่ 5 การไล่ระดับสีแบบเมชเคลื่อนไหวพร้อมจุดเพื่อแสดงภาพเคลื่อนไหว