Paint là lớp chính để tạo kiểu đồ họa trong Android Canvas. Nó xác định màu sắc, độ dày đường kẻ, kiểu tô màu, hiệu ứng làm mờ và phông chữ cho tất cả các thao tác vẽ trong giao diện người dùng. Nếu không có Paint, Canvas không thể áp dụng các thuộc tính trực quan cho các phần tử được hiển thị. Theo Google Developers, 2026, Paint hỗ trợ tới 42 tham số kiểu, bao gồm gradient và họa tiết.
Điểm chính
Paint là một lớp từ gói android.graphics lưu trữ tất cả cài đặt kiểu cho các thao tác vẽ trên Canvas. Nó không chứa pixel mà xác định cách các đường kẻ, hình dạng và văn bản sẽ trông như thế nào. Mỗi lần gọi phương thức Canvas — drawCircle, drawLine, drawText — sử dụng Paint để diễn giải biểu diễn trực quan.
Paint hoạt động theo nguyên tắc đối tượng cấu hình. Bạn tạo một thể hiện, đặt tham số một lần và sử dụng lại cho nhiều thao tác. Điều này giảm thiểu cấp phát trong vòng lặp vẽ và cải thiện hiệu suất lên tới 30% so với việc tạo đối tượng mới mỗi khung hình. Theo Google I/O 2024, tối ưu hóa Paint là một trong những thực hành chính để đạt 60 FPS trong đồ họa tùy chỉnh.
Tất cả cài đặt Paint được chia thành nhiều danh mục: màu sắc và độ trong suốt, kiểu nét vẽ và tô màu, xử lý văn bản và phông chữ, hiệu ứng làm mờ và mặt nạ. Mỗi danh mục được điều khiển bởi các phương thức setter độc lập có thể kết hợp theo bất kỳ sự kết hợp nào. Điều này làm cho API Paint trở nên linh hoạt, nhưng đòi hỏi phải hiểu sự tương tác của các tham số — ví dụ, Style ảnh hưởng đến việc Shader được áp dụng cho nét vẽ hay phần tô.
setColor là phương thức chính để đặt màu cho tất cả các thao tác vẽ tiếp theo. Màu sắc được truyền ở định dạng ARGB (số nguyên 32 bit), trong đó 8 bit cao là kênh alpha và phần còn lại là các kênh đỏ, xanh lục và xanh lam. Giá trị 0xFF0000FF đặt màu xanh lam đục.
Phương thức setAlpha cho phép điều chỉnh độ trong suốt một cách độc lập trong phạm vi từ 0 (hoàn toàn trong suốt) đến 255 (hoàn toàn đục). Không giống như chỉ định alpha trong setColor, phương thức này không ảnh hưởng đến tông màu đã đặt — nó chỉ điều chỉnh độ trong suốt tổng thể. Điều này thuận tiện cho việc tạo hoạt cảnh xuất hiện và biến mất của các phần tử mà không thay đổi màu sắc.
ColorFilter là một cơ chế biến đổi màu của từng pixel trước khi kết xuất. Ba chế độ được hỗ trợ: LightingColorFilter để nhân và cộng kênh, PorterDuffColorFilter để đèp chồng bằng chế độ hòa trộn và ColorMatrixColorFilter để biến đổi ma trận đầy đủ. ColorFilter được áp dụng sau Shader nhưng trước khi xuất ra Canvas.
Đối với hầu hết các tác vụ, setColor hoặc setAlpha tiêu chuẩn là đủ. ColorFilter chủ yếu được sử dụng cho các hiệu ứng giảm bão hòa, màu nâu đỏ hoặc đảo ngược. Sử dụng ColorFilter làm tăng tải GPU, vì vậy Google khuyên nên giảm thiểu việc sử dụng nó trong các lệnh gọi theo chu kỳ tới onDraw.
| Phương thức | Mô tả | Hiệu suất |
|---|---|---|
| setColor | Đặt màu ở ARGB | O(1) — không có chi phí |
| setAlpha | Điều chỉnh độ trong suốt (0-255) | O(1) — thao tác nhẹ |
| setColorFilter | Áp dụng hiệu chỉnh màu | O(n) — ảnh hưởng FPS khi gọi thường xuyên |
Phương thức setStyle chấp nhận một trong ba giá trị Paint.Style: FILL (tô), STROKE (đường viền) hoặc FILL_AND_STROKE (cả hai). Việc chọn kiểu xác định xem một hình dạng sẽ được hiển thị dưới dạng hình bóng, khung dây hay đường viền có tô bên trong. Theo mặc định, Paint được tạo với kiểu FILL.
setStrokeWidth đặt độ dày đường kẻ bằng pixel cho chế độ STROKE. Đối với hình tròn và góc bo tròn, sử dụng setStrokeCap (Cap.ROUND, BUTT, SQUARE) và setStrokeJoin (Join.MITER, ROUND, BEVEL). Các tham số này ảnh hưởng nghiêm trọng đến giao diện của đồ thị, đường dẫn và đường viền trong các View tùy chỉnh.
Ở độ dày dưới 1 pixel, một đường được vẽ với độ chính xác subpixel nhưng có thể trông bị mờ trên màn hình mật độ thấp. Đối với màn hình Retina Android, hãy sử dụng TypedValue.applyDimension để chuyển đổi dp thành pixel. Độ dày 2 dp đảm bảo hiển thị rõ ràng trên hầu hết các thiết bị.
DashPathEffect tạo các đường đứt nét và gạch nối. Hàm tạo chấp nhận một mảng độ dài gạch ngang và khoảng trống xen kẽ và độ lệch pha ban đầu. Ví dụ, mảng [10, 5, 3, 5] tạo một đường có gạch dài, khoảng trống ngắn, gạch ngắn và khoảng trống. Hiệu ứng này được sử dụng tích cực trong các trình chỉnh sửa đồ họa và công cụ chú thích.
Việc kết hợp các kiểu được thực hiện thông qua setPathEffect. Bằng cách kết hợp CornerPathEffect, DashPathEffect và DiscretePathEffect, bạn có thể tạo các đường trực quan phức tạp cho biểu đồ, nhãn và các phần tử trang trí. Mỗi PathEffect được tính toán trên CPU, vì vậy trong các hoạt ảnh có nhiều đường, nên rasterize trước hiệu ứng vào Bitmap.
val paint = Paint().apply {
setStyle(Paint.Style.STROKE)
setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics))
setPathEffect(DashPathEffect(floatArrayOf(10f, 5f, 3f, 5f), 0f))
}
Typeface là một đối tượng xác định kiểu chữ cho tất cả các thao tác văn bản của Paint. Android hỗ trợ phông chữ hệ thống (Roboto, Noto Sans), phông chữ tùy chỉnh từ tài nguyên assets và phông chữ có thể tải xuống qua API Downloadable Fonts. Typeface được đặt bằng cách sử dụng setTypeface trên một thể hiện Paint.
Phương thức setTextSize đặt kích thước phông chữ bằng pixel. Để hiển thị chính xác trên các màn hình khác nhau, nên chỉ định kích thước bằng sp bằng cách sử dụng TypedValue.applyDimension. Khoảng cách dòng được điều chỉnh bằng setTextScaleX để kéo dãn ngang và được hỗ trợ bởi văn bản nhiều dòng tích hợp qua Canvas.drawText.
Để căn chỉnh văn bản, setTextAlign được sử dụng với các giá trị LEFT, CENTER và RIGHT. Với CENTER, tọa độ x được truyền cho drawText trở thành điểm trung tâm của văn bản — điều này đặc biệt thuận tiện cho các nhãn dưới biểu tượng và tiêu đề bên trong hình dạng. Để định vị chính xác, các phương thức measureText và getTextBounds được sử dụng, trả về chiều rộng và hình chữ nhật bao quanh của văn bản.
setUnderlineText và setStrikeThruText bật lần lượt gạch chân và gạch ngang. Các tham số này hoạt động ở cấp độ kiểu chữ và áp dụng cho tất cả văn bản được vẽ bởi Paint này. Để định dạng linh hoạt các phần khác nhau của cùng một chuỗi, hãy sử dụng các đối tượng Paint riêng biệt với các cài đặt khác nhau hoặc chuyển đổi tham số giữa các lệnh gọi drawText.
| Phương thức | Mục đích | Đơn vị |
|---|---|---|
| setTextSize | Kích thước phông chữ | px (pixel) |
| setTextAlign | Căn chỉnh ngang | LEFT, CENTER, RIGHT |
| setTypeface | Kiểu chữ | Đối tượng Typeface |
| setUnderlineText | Gạch chân | Boolean |
Shader là lớp cơ sở để tô các hình dạng bằng gradient, họa tiết bitmap và tổng hợp. Thông qua phương thức setShader, bạn có thể gán bất kỳ lớp con nào của nó: LinearGradient, RadialGradient, SweepGradient, BitmapShader hoặc ComposeShader. Shader thay thế màu được đặt bởi setColor và được áp dụng cho tất cả các phần tử được vẽ.
LinearGradient tạo một chuyển tiếp mượt mà giữa hai hoặc nhiều màu dọc theo một đường thẳng. Hàm tạo chấp nhận điểm bắt đầu và kết thúc, một mảng màu và vị trí của chúng (chế độ tile). Chế độ tile xác định hành vi vượt ra ngoài gradient: CLAMP (lặp lại màu cuối cùng), MIRROR (phản chiếu) hoặc REPEAT (lặp lại). LinearGradient được sử dụng tích cực cho tô nền và thanh tiến trình.
RadialGradient tạo một gradient tròn từ trung tâm ra các cạnh. Trung tâm được chỉ định bằng tọa độ, bán kính xác định vùng chuyển tiếp màu. Giao diện của gradient xuyên tâm giống như ánh sáng từ một nguồn điểm — điều này thường được sử dụng để mô phỏng độ sáng của nút và làm nổi bật các phần tử hoạt động.
BlurMaskFilter thêm hiệu ứng làm mờ xung quanh các phần tử được vẽ. Hàm tạo chấp nhận bán kính làm mờ và một kiểu: NORMAL (làm mờ mọi hướng), INNER (chỉ bên trong hình dạng), OUTER (chỉ bên ngoài) và SOLID (hình dạng + làm mờ bên ngoài). BlurMaskFilter hoạt động với mọi loại Paint và thường được sử dụng để tạo bóng và hiệu ứng phát sáng.
val paint = Paint().apply {
setShader(LinearGradient(
0f, 0f, width, height,
intArrayOf(Color.RED, Color.BLUE),
null,
Shader.TileMode.CLAMP
))
setMaskFilter(BlurMaskFilter(20f, BlurMaskFilter.Blur.NORMAL))
}
Hãy xem xét một ví dụ hoàn chỉnh về View tùy chỉnh vẽ một hình tròn với tô gradient và nhãn văn bản. Sự kết hợp giữa Paint và Canvas cho phép tạo đồ họa phức tạp với mã tối thiểu. Tất cả cài đặt được chuyển vào khởi tạo để tăng hiệu suất.
class GradientCircleView@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val fillPaint = Paint().apply {
setShader(RadialGradient(
0f, 0f, 200f,
intArrayOf(Color.CYAN, Color.DKGRAY),
null, Shader.TileMode.CLAMP
))
}
private val textPaint = Paint().apply {
setColor(Color.WHITE)
setTextSize(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 24f, resources.displayMetrics
))
setTextAlign(Paint.Align.CENTER)
setMaskFilter(BlurMaskFilter(5f, BlurMaskFilter.Blur.NORMAL))
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(200f, 200f, 150f, fillPaint)
canvas.drawText("Gradient", 200f, 210f, textPaint)
}
}
Mã sử dụng hai đối tượng Paint riêng biệt: một để tô hình tròn bằng gradient xuyên tâm và đối tượng thứ hai cho văn bản có bóng qua BlurMaskFilter. Sự tách biệt này cho phép cấu hình tham số một cách độc lập và tránh đặt lại trạng thái Paint giữa việc vẽ các loại phần tử khác nhau. Để cải thiện hiệu suất, cả hai đối tượng Paint được tạo trong hàm tạo, không phải trong onDraw.
Khi thay đổi tỷ lệ trên các thiết bị có mật độ pixel khác nhau, các giá trị kích thước phải được tính toán lại bằng TypedValue.applyDimension. Nếu không, văn bản và gradient sẽ trông khác nhau trên màn hình mdpi và xxxhdpi. Trong ví dụ, kích thước văn bản được chỉ định bằng sp, tự động thích ứng với cài đặt kích thước phông chữ hệ thống của người dùng.
Câu hỏi thường gặp
Canvas thực hiện các thao tác vẽ (drawCircle, drawLine), trong khi Paint xác định kiểu — màu sắc, độ dày, phông chữ. Canvas không chứa cài đặt kiểu, vì vậy nếu không có Paint, bất kỳ thao tác vẽ nào cũng không có hiệu ứng trực quan.
Có, một đối tượng Paint duy nhất có thể được tái sử dụng cho nhiều hình dạng. Nên tạo các đối tượng Paint riêng cho các loại phần tử khác nhau (nền, văn bản, đường viền), nhưng không phải cho mỗi hình dạng — điều này giảm tải cho bộ thu gom rác.
Phương thức reset đưa Paint về trạng thái tương đương với một thể hiện mới. Thay vào đó, bạn có thể sử dụng set với cờ, nhưng reset được ưu tiên hơn cho khả năng đọc mã. Sau khi đặt lại, tất cả các tham số kiểu lấy giá trị mặc định.
Tạo Paint bên trong onDraw gây cấp phát trong mỗi khung hình, dẫn đến đóng băng GC. Tất cả các đối tượng Paint nên được tạo trong hàm tạo View. Thay đổi tham số trong onDraw qua setter là an toàn và không tạo đối tượng mới.
Văn bản trong Android luôn được vẽ với nền trong suốt. Phương thức drawText chỉ xuất ra glyph, không có hình chữ nhật nền. Nếu cần nền dưới văn bản, trước tiên hãy vẽ một hình chữ nhật bằng drawRoundRect với Paint riêng, sau đó vẽ văn bản.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm