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 ) }
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 ) }
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ư
drawRect
và
drawCircle
.
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
)
}
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
)
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
)
}
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
)
}
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.