Chuyển màu dạng lưới

Chuyển màu dạng lưới tạo ra các hiệu ứng chuyển màu phức tạp, đa hướng bằng cách sử dụng lưới 2D gồm các mảng màu. Không giống như dải chuyển màu tuyến tính hoặc xuyên tâm, dải chuyển màu dạng lưới nội suy màu sắc một cách mượt mà trên lưới 2D. Sử dụng dải chuyển màu dạng lưới thay vì xếp chồng nhiều bút vẽ radialGradient hoặc linearGradient khi tạo nền linh hoạt, tự nhiên hoặc đa điểm trong giao diện người dùng.

Ví dụ về dải chuyển màu dạng lưới có hiển thị các điểm dải chuyển màu dạng lưới hiện tại.
Hình 1. Ví dụ về dải chuyển màu dạng lưới với màn hình hiển thị các điểm dải chuyển màu dạng lưới hiện tại.

Khái niệm chính

Để tạo dải chuyển màu dạng lưới, hãy xác định kích thước lưới, các đỉnh và hiệu ứng chuyển màu giữa các điểm:

  • Kích thước lưới: Lưới được chia thành các mảng dọc theo trục dọc và trục ngang. Một lưới gồm rows và columns chứa (hàng+1)×(cột+1) đỉnh. Ví dụ: lưới 1×1 bao gồm 4 đỉnh tạo thành một mảng.
  • Toạ độ chuẩn hoá: Tất cả vị trí đỉnh đều sử dụng hệ toạ độ chuẩn hoá, trong đó (0f, 0f) đại diện cho góc trên cùng bên trái và (1f, 1f) đại diện cho góc dưới cùng bên phải của ranh giới vẽ.
  • Điểm điều khiển Bezier (tiếp tuyến): Mỗi đỉnh chứa tối đa 4 điểm điều khiển Bezier tuỳ chọn. Các tiếp tuyến này chỉ định độ cong của cạnh giữa các đỉnh lân cận. Nếu bạn sử dụng Offset.Unspecified, Compose sẽ suy luận các tiếp tuyến để đảm bảo chuyển đổi mượt mà giữa các bản vá. Mỗi ô lưới được tạo thành từ 4 đỉnh cùng với các điểm điều khiển của chúng sẽ tạo ra một mảng bezier.
  • Nội suy màu: Khung tính toán màu giữa các đỉnh chính. Đặt hasBicubicColor thành true cho phép nội suy Catmull-Rom để chuyển màu mượt mà hơn hoặc false cho phép nội suy song tuyến tính.

Vẽ bằng MeshGradientPainter

Trong Jetpack Compose, hãy sử dụng MeshGradientPainter để kết xuất dải chuyển màu dạng lưới. MeshGradientPainter vẽ trên canvas.

Tạo dải chuyển màu lưới đơn giản

Để tạo dải chuyển màu lưới tĩnh cơ bản, hãy khởi chạy MeshGradientPainter bằng cách chỉ định kích thước và sử dụng hàm setVertex bên trong khối cấu hình để định vị các điểm góc và chỉ định màu cho các điểm đó.

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

Chuyển màu lưới cơ bản với 4 màu được xác định ở mỗi góc
Hình 2. Một dải chuyển màu lưới cơ bản có 4 màu, trong đó mỗi góc được đặt thành một trong 4 màu.

Sử dụng các điểm điều khiển Bezier cụ thể

Theo mặc định, trình tạo lưới sẽ xử lý các phép tính phức tạp để đảm bảo quá trình chuyển đổi lưới diễn ra suôn sẻ. Tuy nhiên, bạn có thể tuỳ chỉnh rõ ràng các tiếp tuyến trên bất kỳ đỉnh nào nếu muốn đẩy, kéo hoặc chụm mạnh một số phần màu nhất định.

Độ lệch của điểm điều khiển được đo lường tương ứng với vị trí của đỉnh gốc.

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

Chuyển màu dạng lưới với điểm trên cùng bên trái được bo cong.
Hình 3. Uốn cong đỉnh trên cùng bên trái bằng điểm điều khiển bezier.

Tạo lưới nâng cao

Ví dụ này cho thấy một lưới 3 x 3, nghĩa là có 16 điểm cần được chỉ định, trong đó các điểm giữa được đặt với các độ lệch khác nhau:

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

Chuyển màu lưới bằng các điểm điều khiển bezier và màu sóng, đồng thời các điểm lưới được minh hoạ ở trên cùng.
Hình 4. Chuyển màu lưới bằng các điểm điều khiển bezier và màu sóng, cùng với các điểm lưới được minh hoạ ở trên cùng.

Tạo ảnh động cho dải chuyển màu dạng lưới

Vì tham số lambda block của MeshGradientPainter được thực thi trong DrawScope, nên tham số này có thể đọc và quan sát trạng thái có thể thay đổi. Bạn có thể tạo ảnh động cho các vị trí hoặc màu sắc theo thời gian mà không cần phân bổ lại chương trình đổ bóng hoặc 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)
)

Hình 5. Chuyển màu dạng lưới động có các điểm để thể hiện ảnh động.