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

Chuyển màu dạng lưới tạo ra các chuyển đổi 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. Không giống như kiểu chuyển màu tuyến tính hoặc xuyên tâm, kiểu chuyển màu dạng lưới sẽ nội suy màu một cách mượt mà trên một lưới. Sử dụng chuyển màu dạng lưới để tạo các phần tử thẩm mỹ mượt mà và tự nhiên trong giao diện người dùng.

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

Khái niệm chính

Để tạo một 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à các dải 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 tung và trục hoành. Lưới rowscolumns chứa (hàng+1)×(cột+1) đỉnh. Ví dụ: một lưới 1×1 bao gồm 4 đỉnh tạo thành một mảng.
  • Toạ độ được chuẩn hoá: Tất cả vị trí đỉnh đều sử dụng hệ toạ độ được chuẩn hoá, trong đó (0f, 0f) biểu thị phía trên cùng bên trái và (1f, 1f) biểu thị phía dưới cùng bên phải của ranh giới bản vẽ.
  • Điểm kiểm soát Bezier (tiếp tuyến): Mỗi đỉnh chứa tối đa 4 điểm kiểm soát Bezier không bắt buộc. 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 đường tiếp tuyến để đảm bảo các hiệu ứng chuyển đổi mượt mà trên 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 kiểm soát của chúng sẽ tạo ra một mảng bezier.
  • Nội suy màu: Khung này tính toán màu sắc 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.

Vẽ bằng MeshGradientPainter

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

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

Để tạo một chuyển màu cơ bản cho lưới tĩnh, 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)
)

Gradient 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 dạng lưới cơ bản có 4 màu, 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 để duy trì các chuyển đổi lưới mượt mà. 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 chọn lọc đẩy, kéo hoặc kẹp chặt các phần màu nhất định.

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

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 có điểm cong ở trên cùng bên trái.
Hình 3. Uốn cong đỉnh trên cùng bên trái bằng một đ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, tức 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)
        // ...
)

Lưới chuyển màu có 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. Lưới chuyển màu có các điểm kiểm soát bezier và màu sóng, đồng thời các điểm lưới được minh hoạ ở trên cùng.

Tạo ảnh động cho một 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à theo dõi trạng thái có thể thay đổi. Bạn có thể tạo hiệu ứng cho 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. Ảnh động có chuyển màu dạng lưới với các điểm để minh hoạ ảnh động.