onDraw() — là gì, cơ chế gọi và ghi đè phương thức

Tác giả: IT Sectr Đã đăng: 2026-07-22 Thời gian đọc: 10 phút

onDraw() — là phương thức quan trọng của lớp View trong Android SDK, chịu trách nhiệm vẽ nội dung của view. Hệ thống gọi onDraw mỗi khi giao diện của View được cập nhật — khi kết xuất ban đầu, sau invalidate() hoặc khi kích thước thay đổi. Theo Android Developers Documentation (2026), việc ghi đè onDraw bằng Canvas API là cách chính để tạo đồ họa tùy chỉnh trong các ứng dụng Android.

Điểm chính

  • onDraw(Canvas) — phương thức View được hệ thống gọi để vẽ nội dung lên Canvas
  • invalidate() — yêu cầu vẽ lại View, dẫn đến việc gọi onDraw trong chu kỳ khung hình tiếp theo
  • Canvas API cung cấp các phương thức drawCircle, drawBitmap, drawText, drawPath và các phương thức khác để vẽ
  • Hệ thống Android thực hiện việc vẽ trong luồng UI, vì vậy onDraw phải nhẹ và nhanh
  • Hardware acceleration tự động tăng tốc việc vẽ qua GPU, kể từ Android 3.0 (API 11)

onDraw() là gì?

onDraw(Canvas canvas) — là một phương thức protected của lớp android.view.View, được hệ thống Android gọi khi View cần được vẽ. Nhà phát triển ghi đè phương thức này để triển khai việc vẽ tùy chỉnh: hình dạng, văn bản, hình ảnh, gradient, hoạt ảnh. Tham số Canvas là bề mặt đồ họa mà trên đó tất cả các thao tác vẽ được thực hiện.

Chữ ký của phương thức rất đơn giản: override fun onDraw(canvas: Canvas). Bên trong phương thức này không thể tạo các đối tượng mới (để tránh tải lên GC), thực hiện các thao tác lâu dài hoặc làm việc với mạng. View cũng chứa phương thức onDraw trong vòng đời của nó giữa onLayout (tính toán vị trí) và dispatchDraw (vẽ các phần tử con).

Khi kế thừa từ View, việc ghi đè onDraw là bắt buộc để hiển thị bất kỳ nội dung nào — nếu không View sẽ trống rỗng. Khi kế thừa từ các lớp con hiện có (TextView, ImageView, Button), việc ghi đè onDraw là tùy chọn và được dùng để thêm đồ họa bổ sung lên trên nội dung chuẩn.

Vòng vẽ của View

Android thực hiện việc vẽ View theo một trình tự xác định nghiêm ngặt, được gọi từ luồng chính (UI thread). Vòng đầy đủ gồm ba giai đoạn: measure (đo kích thước), layout (tính toán vị trí) và draw (vẽ). onDraw là một phần của giai đoạn draw và được gọi sau khi kích thước cũng như vị trí của View đã được xác định.

Giai đoạn draw: dispatchDraw và onDraw

draw(Canvas canvas) — là một phương thức public của View, gọi onDraw và dispatchDraw. Phương thức draw không dành cho việc ghi đè — thay vào đó, onDraw được ghi đè. Sau khi thực hiện onDraw cho View hiện tại, dispatchDraw được gọi, gọi đệ quy draw cho tất cả các View con (nếu View hiện tại là một thùng chứa ViewGroup).

Khi nào hệ thống gọi onDraw

onDraw được gọi trong các điều kiện sau: 1) View lần đầu tiên trở nên hiển thị (sau setVisibility(VISIBLE)); 2) ứng dụng gọi invalidate() cho View này hoặc cha của nó; 3) kích thước View thay đổi (layout mới); 4) hoạt ảnh liên quan đến View thay đổi (qua ValueAnimator hoặc ObjectAnimator); 5) trạng thái của View thay đổi (pressed, focused, enabled).

Tăng tốc phần cứng

Từ Android 3.0 (API 11), hầu hết các thao tác Canvas được thực hiện với tăng tốc phần cứng qua GPU. Nhà phát triển có thể kiểm tra xem việc tăng tốc có được bật hay không qua canvas.isHardwareAccelerated(). Khi tăng tốc phần cứng, một số thao tác Canvas không được hỗ trợ (ví dụ: clipPath với vùng không phải hình chữ nhật) và có thể bị bỏ qua hoặc gây ra lỗi.

Canvas API cho đồ họa tùy chỉnh

Canvas cung cấp một bộ phương thức phong phú để vẽ. Tất cả các phương thức hoạt động trong hệ tọa độ hiện tại, có thể được thay đổi qua các phép biến đổi (translate, rotate, scale, skew). Mỗi phương thức nhận một đối tượng Paint, xác định màu sắc, kiểu, độ dày đường kẻ, hiệu ứng và chống răng cưa.

Phương thức CanvasMục đíchCác tham số chính của Paint
drawCircleVẽ hình tròncenterX, centerY, radius, paint
drawRectVẽ hình chữ nhậtleft, top, right, bottom, paint
drawLineVẽ đường kẻstartX, startY, endX, endY, paint
drawPathVẽ đường viền tùy ýpath, paint
drawBitmapVẽ hình ảnhbitmap, srcRect, dstRect, paint
drawTextVẽ văn bảntext, x, y, paint
drawArcVẽ cung hoặc hình quạtovalRect, startAngle, sweepAngle, useCenter, paint
drawOvalVẽ hình bầu dụcovalRect, paint

Đối tượng Paint

Paint xác định kiểu vẽ: màu sắc (setColor), độ dày đường kẻ (setStrokeWidth), kiểu tô (setStyle — FILL, STROKE, FILL_AND_STROKE), chống răng cưa (setAntiAlias), hiệu ứng (setShadowLayer, setMaskFilter) và gradient (setShader). Nên tạo và cấu hình Paint một lần trong constructor của View, thay vì bên trong onDraw, để tránh tạo đối tượng trong vòng vẽ.

Tọa độ và phép biến đổi

Canvas sử dụng hệ tọa độ trong đó trục X hướng sang phải, còn trục Y hướng xuống dưới. Gốc tọa độ (0, 0) — góc trên bên trái của View. Trước khi gọi các phương thức draw, nhà phát triển có thể thay đổi hệ tọa độ qua canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py). Tất cả các thao tác vẽ tiếp theo sẽ được thực hiện trong hệ tọa độ đã biến đổi.

Hiệu suất của onDraw

onDraw được thực hiện trong luồng chính UI, và thời lượng của nó ảnh hưởng trực tiếp đến tốc độ khung hình (fps). Để duy trì 60 fps, phương thức onDraw phải hoàn thành trong không quá 16 mili giây. Bất kỳ sự chậm lại nào dẫn đến việc rớt khung hình (jank), được nhìn nhận trực quan như hoạt ảnh bị giật hoặc độ trễ khi cuộn.

Quy tắc cho onDraw hiệu suất cao

Các quy tắc chính để tối ưu onDraw bao gồm: 1) không tạo đối tượng bên trong onDraw — tất cả Paint, Path, Rect phải là các trường của lớp; 2) không cấp phát bộ nhớ (new) — điều này gây ra thu gom rác (GC) và rớt khung hình; 3) lưu vào bộ nhớ đệm các thao tác phức tạp trong Bitmap qua Bitmap.createBitmap và vẽ Bitmap đã sẵn sàng; 4) không thực hiện các thao tác nhập/xuất hoặc tải tài nguyên.

Sử dụng clipRect để tối ưu hóa

clipRect giới hạn vùng vẽ bằng hình chữ nhật xác định. Được dùng để Canvas không mất thời gian vẽ các điểm ảnh nằm ngoài vùng nhìn thấy. Đặc biệt hiệu quả khi cuộn danh sách lớn hoặc phóng to/thu nhỏ: hãy đặt clipRect cho vùng nhìn thấy (dirty rect), và Canvas sẽ chỉ vẽ những gì nhìn thấy được.

Layer — lưu và khôi phục

canvas.save() lưu trạng thái hiện tại của Canvas (phép biến đổi và clip) vào ngăn xếp. canvas.restore() khôi phục trạng thái đã lưu gần nhất. Điều này cần thiết cho các phép biến đổi tạm thời: lưu trạng thái, áp dụng translate/rotate, vẽ, sau đó khôi phục. save và restore rất nhẹ và không ảnh hưởng đến hiệu suất khi sử dụng hợp lý (dưới 10–15 cấp lồng nhau).

invalidate và postInvalidate

invalidate() — là phương thức View, thông báo cho hệ thống rằng nội dung của View đã lỗi thời và cần được vẽ lại. Việc gọi invalidate khởi tạo một vòng draw mới, trong đó onDraw sẽ được gọi. invalidate chỉ nên được gọi từ luồng UI (main thread). Để gọi từ luồng nền, sử dụng postInvalidate.

postInvalidate() — là phiên bản an toàn luồng của invalidate, gửi yêu cầu vẽ lại vào hàng đợi của luồng chính qua Handler. Được dùng trong các tác vụ nền khi cần cập nhật View sau khi hoàn tất việc tải dữ liệu hoặc tính toán. Kết hợp với runOnUiThread để đồng bộ với UI.

Đối với việc vẽ lại từng phần, sử dụng invalidate(Rect dirty) hoặc invalidate(int l, int t, int r, int b). Điều này cho hệ thống biết rằng chỉ vùng xác định thay đổi, không phải toàn bộ View. Android tối ưu việc vẽ, chỉ vẽ lại dirty rect, giúp tăng đáng kể hiệu suất trong các hoạt ảnh có cập nhật từng phần.

Ví dụ code bằng Kotlin

Hãy xem các ví dụ thực tế về việc ghi đè onDraw để tạo View tùy chỉnh trong Android bằng Kotlin.

View tùy chỉnh — vòng tiến trình tròn

Ví dụ tạo một chỉ báo tiến trình hình tròn với phần điền được hoạt hình hóa. onDraw vẽ hai hình tròn: nền màu xám và hình tròn màu được điền, cung của nó được xác định bởi tiến trình hiện tại. Việc gọi invalidate trong setProgress đảm bảo vẽ lại khi giá trị thay đổi.

kotlin
class CircularProgressView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    private val bgPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 12f
        color = Color.parseColor("#E0E0E0")
        isAntiAlias = true
    }

    private val progressPaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 12f
        color = Color.BLUE
        isAntiAlias = true
        strokeCap = Paint.Cap.ROUND
    }

    var progress: Float = 0f
        set(value) {
            field = value.coerceIn(0f, 1f)
            invalidate()
        }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val size = minOf(width, height) - 24f
        val oval = RectF(12f, 12f,
                          12f + size, 12f + size)

        canvas.drawArc(oval, 270f, 360f, false, bgPaint)
        canvas.drawArc(oval, 270f,
                        progress * 360f,
                        false, progressPaint)
    }
}

View tùy chỉnh — nền gradient với văn bản

Ví dụ vẽ một hình chữ nhật với gradient và văn bản phía trên. Gradient được tạo qua LinearGradient (Shader), còn văn bản — qua drawText. Cả hai Paint được tạo một lần trong constructor, còn trong onDraw chỉ thay đổi kích thước của hình chữ nhật tùy theo chiều rộng hiện tại của View.

kotlin
class GradientTextView(context: Context)
    : View(context) {

    private val shaderPaint = Paint().apply {
        isAntiAlias = true
    }

    private val textPaint = Paint().apply {
        color = Color.WHITE
        textSize = 48f
        isAntiAlias = true
        textAlign = Paint.Align.CENTER
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val gradient = LinearGradient(
            0f, 0f, width.toFloat(), 0f,
            Color.parseColor("#FF6B35"),
            Color.parseColor("#FF0040"),
            Shader.TileMode.CLAMP
        )
        shaderPaint.shader = gradient
        canvas.drawRect(0f, 0f,
                        width.toFloat(),
                        height.toFloat(), shaderPaint)

        val xCenter = width / 2f
        val yCenter = height / 2f
        val yOffset = (textPaint.descent() +
                        textPaint.ascent()) / 2f

        canvas.drawText("Xin chào từ Canvas",
                      xCenter, yCenter - yOffset,
                      textPaint)
    }
}

Đồng hồ hoạt hình sử dụng invalidate

Ví dụ tạo một đồng hồ hoạt hình, cập nhật việc vẽ mỗi 1000 mili giây. postDelayed trong onDraw gọi invalidate sau một giây, tạo vòng lặp vẽ lại vô hạn. Các kim đồng hồ được vẽ qua drawLine với độ dài và độ dày khác nhau.

kotlin
class AnalogClockView(context: Context)
    : View(context) {

    private val handPaint = Paint().apply {
        style = Paint.Style.STROKE
        isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val cx = width / 2f
        val cy = height / 2f
        val radius = minOf(cx, cy) - 20f

        val now = Calendar.getInstance()
        val hours = now.get(Calendar.HOUR) % 12
        val minutes = now.get(Calendar.MINUTE)
        val seconds = now.get(Calendar.SECOND)

        val secondAngle = Math.toRadians(
            seconds * 6f - 90f)
        handPaint.strokeWidth = 2f
        handPaint.color = Color.RED
        canvas.drawLine(cx, cy,
            cx + cos(secondAngle) * radius * 0.8f,
            cy + sin(secondAngle) * radius * 0.8f,
            handPaint)

        postDelayed({ invalidate() }, 1000)
    }
}

Các câu hỏi thường gặp

Có thể gọi trực tiếp onDraw không?

Không, không nên gọi trực tiếp onDraw. Để yêu cầu vẽ lại, hãy sử dụng invalidate() (từ luồng UI) hoặc postInvalidate() (từ luồng nền). Hệ thống tự quyết định khi nào gọi onDraw và tối ưu việc vẽ, kết hợp nhiều invalidate thành một lượt vẽ.

Tại sao onDraw không được gọi sau invalidate?

Nguyên nhân: View bị ẩn (setVisibility(GONE) hoặc INVISIBLE); View chưa được thêm vào phân cấp của Window; kích thước View bằng 0 (width hoặc height = 0); gọi setWillNotDraw(true) (mặc định là true đối với ViewGroup). Hãy kiểm tra từng điểm này khi gỡ lỗi.

onDraw có thể được gọi bao nhiêu lần?

Ở 60 fps, onDraw có thể được gọi tới 60 lần mỗi giây. Ở 120 fps (trên các thiết bị có màn hình 120 Hz) — tới 120 lần. Mỗi lần gọi phải nằm trong khoảng ~8–16 mili giây. Khi có hoạt ảnh chủ động qua ObjectAnimator hoặc ValueAnimator, tần suất được xác định bởi số khung hình của hoạt ảnh.

Sự khác biệt giữa onDraw và dispatchDraw là gì?

onDraw chịu trách nhiệm vẽ nội dung của chính View. dispatchDraw chịu trách nhiệm vẽ các View con (trong ViewGroup). dispatchDraw được gọi sau onDraw và đệ quy đi qua tất cả các phần tử con. Đối với View thông thường (không phải ViewGroup), dispatchDraw rỗng.

Có thể sử dụng Canvas bên ngoài onDraw không?

, Canvas có thể được tạo cho Bitmap qua Canvas(bitmap) và vẽ trên đó ở bất kỳ luồng nào. Điều này được dùng để tính toán trước đồ họa phức tạp trong nền. Tuy nhiên, Canvas được truyền vào onDraw thuộc về hệ thống và chỉ có thể được sử dụng trong luồng UI bên trong phương thức onDraw.

Tổng kết

  • onDraw(Canvas) — phương thức View quan trọng để vẽ tùy chỉnh bằng Canvas API trong Android
  • Vòng vẽ bao gồm các giai đoạn measure, layout và draw — onDraw được gọi trong giai đoạn draw
  • Canvas API bao gồm các phương thức drawCircle, drawRect, drawPath, drawBitmap, drawText và các phương thức khác
  • invalidate() yêu cầu vẽ lại từ luồng UI, postInvalidate() — từ luồng nền
  • Vẽ lại từng phần qua invalidate(Rect) tối ưu việc vẽ, chỉ cập nhật vùng dirty
  • Hiệu suất của onDraw rất quan trọng: việc tạo đối tượng bên trong phương thức gây ra GC và rớt khung hình
  • Tăng tốc phần cứng (từ API 11) tự động tăng tốc các thao tác Canvas qua GPU

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.

Thảo luận dự án

Đọc thêm