Đồ hoạ trong cCompose

Jetpack Compose giúp làm việc với các hình ảnh đồ hoạ tuỳ chỉnh dễ dàng hơn. Nhiều ứng dụng cần có khả năng điều khiển chính xác nội dung trình bày trên màn hình. Việc này có thể đơn giản như đặt một hộp hoặc một vòng tròn trên màn hình ở đúng vị trí, hoặc có thể là sự sắp xếp chi tiết các thành phần đồ hoạ theo nhiều kiểu khác nhau. Cách tiếp cận khai báo của Compose giúp tất cả cấu hình đồ hoạ diễn ra ở một nơi, thay vì bị tách ra giữa lệnh gọi phương thức và một đối tượng trợ giúp trình duyệt Paint. Compose đảm nhận việc tạo và cập nhật các đối tượng cần thiết một cách hiệu quả.

Đồ hoạ khai báo bằng Compose

Compose mở rộng cách tiếp cận khai báo thành cách công cụ xử lý đồ hoạ. Cách tiếp cận của Compose mang đến một số ưu điểm:

  • Compose hạn chế tối đa trạng thái trong các thành phần đồ hoạ, giúp bạn tránh các sai sót lập trình của trạng thái này.
  • Khi bạn vẽ, tất cả các tuỳ chọn đều nằm ở đúng nơi bạn mong đợi, trong hàm có khả năng kết hợp.
  • API đồ hoạ của Compose dùng để tạo và giải phóng các đối tượng một cách hiệu quả.

Canvas

Thành phần kết hợp cốt lõi dành cho đồ hoạ tuỳ chỉnh là Canvas. Bạn đặt Canvas trong bố cục theo cách tương tự như cách bạn thực hiện cho bất kỳ thành phần giao diện người dùng nào khác trong công cụ Compose. Trong phạm vi Canvas, bạn có thể vẽ các thành phần có quyền điều khiển chính xác về kiểu và vị trí của các thành phần đó.

Ví dụ: mã này tạo một thành phần kết hợp Canvas có thể điền tất cả các không gian có sẵn trong thành phần mẹ:

Canvas(modifier = Modifier.fillMaxSize()) {
}

Canvas sẽ tự động biểu thị DrawScope, một môi trường vẽ theo phạm vi duy trì trạng thái riêng. Điều này cho phép bạn đặt các thông số cho một nhóm các thành phần đồ hoạ. DrawScope cung cấp một số trường hữu ích, như size, một đối tượng Size xác định kích thước hiện tại và tối đa của DrawScope.

Ví dụ: giả sử bạn muốn vẽ một đường chéo từ góc trên cùng bên phải của canvas đến góc dưới cùng bên trái. Bạn sẽ thực hiện việc này bằng cách thêm một thành phần kết hợp drawLine:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height

    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )
}

Một chiếc điện thoại với đường kẻ mảnh vẽ chéo trên màn hình.

Hình 1. Sử dụng drawLine để vẽ một đường kẻ trên canvas. Mã này đặt màu sắc của đường kẻ nhưng sử dụng chiều rộng mặc định.

Bạn có thể sử dụng các thông số khác để tuỳ chỉnh bản vẽ. Ví dụ: theo mặc định, đường được vẽ với chiều rộng nhỏ, hiển thị dưới dạng chiều rộng một pixel không phụ thuộc vào tỷ lệ bản vẽ. Bạn có thể ghi đè giá trị mặc định bằng cách cài đặt giá trị strokeWidth:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height

    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue,
        strokeWidth = 5F
    )
}

Một chiếc điện thoại với đường kẻ dày hơn được vẽ chéo trên màn hình.

Hình 2. Sửa đổi đường kẻ từ hình 1 bằng cách ghi đè chiều rộng mặc định.

Có nhiều hàm vẽ đơn giản khác, chẳng hạn như drawRectdrawCircle. Ví dụ: mã này vẽ một vòng tròn tô màu nền ở giữa canvas, có đường kính bằng một nửa kích thước ngắn hơn của canvas:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height
    drawCircle(
        color = Color.Blue,
        center = Offset(x = canvasWidth / 2, y = canvasHeight / 2),
        radius = size.minDimension / 4
    )
}

Một chiếc điện thoại có vòng tròn màu xanh lam được căn chỉnh ở giữa màn hình.

Hình 3. Sử dụng drawCircle để đặt một vòng tròn ở giữa canvas. Theo mặc định, drawCircle vẽ một vòng tròn tô màu nền, do đó, chúng ta không cần chỉ định rõ chế độ cài đặt đó.

Các hàm vẽ có thông số mặc định hữu ích. Ví dụ: theo mặc định, drawRectangle() sẽ điền toàn bộ phạm vi thành phần mẹ, và drawCircle() có bán kính bằng một nửa kích thước ngắn hơn của thành phần mẹ. Với Kotlin, bạn luôn có thể đặt mã của mình đơn giản và rõ ràng hơn bằng cách tận dụng các giá trị thông số mặc định và chỉ cài đặt các thông số mà bạn cần thay đổi. Bạn có thể tận dụng lợi thế này bằng cách đưa ra các thông số cụ thể cho phương thức vẽ DrawScope, vì các thành phần được vẽ sẽ dựa trên chế độ cài đặt mặc định trong phần cài đặt của phạm vi thành phần mẹ.

DrawScope

Như đã lưu ý, mỗi Canvas của Compose biểu thị một DrawScope, môi trường vẽ mà trong đó bạn thực sự đưa ra các lệnh vẽ.

Ví dụ: mã sau đây vẽ một hình chữ nhật ở góc trên bên trái của canvas:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    drawRect(
        color = Color.Green,
        size = canvasQuadrantSize
    )
}

Bạn có thể sử dụng hàm DrawScope.inset() để điều chỉnh các thông số mặc định của phạm vi hiện tại, thay đổi ranh giới bản vẽ và dịch bản vẽ tương ứng. Các thao tác như inset() áp dụng cho tất cả các thao tác vẽ trong hàm lambda tương ứng:

val canvasQuadrantSize = size / 2F
inset(50F, 30F) {
    drawRect(
        color = Color.Green,
        size = canvasQuadrantSize
    )
}

DrawScope cung cấp các phép biến đổi đơn giản khác, như rotate(). Ví dụ: mã này vẽ một hình chữ nhật tô màu nền phần thứ 9 ở giữa canvas:

val canvasSize = size
val canvasWidth = size.width
val canvasHeight = size.height
drawRect(
    color = Color.Gray,
    topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
    size = canvasSize / 3F
)

Điện thoại có hình chữ nhật tô màu nền ở giữa màn hình.

Hình 4. Sử dụng drawRect để vẽ một hình chữ nhật tô màu nền ở giữa màn hình.

Bạn có thể xoay hình chữ nhật bằng cách áp dụng chế độ xoay cho DrawScope của hình chữ nhật:

rotate(degrees = 45F) {
    drawRect(
        color = Color.Gray,
        topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
        size = canvasSize / 3F
    )
}

Một chiếc điện thoại có hình chữ nhật xoay 45 độ ở giữa màn hình.

Hình 5. Chúng tôi sử dụng rotate() để áp dụng chế độ xoay cho phạm vi bản vẽ hiện tại. Chế độ này xoay hình chữ nhật một góc 45 độ.

Nếu muốn áp dụng nhiều phép biến đổi cho các bản vẽ, cách tiếp cận tốt nhất là bạn không nên tạo các môi trường DrawScope lồng nhau. Thay vào đó, bạn nên sử dụng hàm withTransform(). Hàm này sẽ tạo và áp dụng một phép biến đổi duy nhất kết hợp tất cả các thay đổi mà bạn muốn. Việc sử dụng withTransform() hiệu quả hơn việc tạo các lệnh lồng ghép với các phép biến đổi riêng lẻ, vì bạn có thể thực hiện tất cả các phép biến đổi trong một thao tác duy nhất, thay vì việc Compose cần tính toán và lưu lại mỗi phép biến đổi lồng nhau.

Ví dụ: mã này áp dụng cả bản dịch và chế độ xoay cho hình chữ nhật:

withTransform({
    translate(left = canvasWidth / 5F)
    rotate(degrees = 45F)
}) {
    drawRect(
        color = Color.Gray,
        topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
        size = canvasSize / 3F
    )
}

Chiếc điện thoại có một hình chữ nhật xoay được dịch chuyển sang một bên của màn hình.

Hình 6. Ở đây, chúng tôi sử dụng withTransform để áp dụng cả chế độ xoay và bản dịch, xoay và dịch chuyển hình chữ nhật sang trái.