Canvas ใน Android — เป็นคลาสจากแพ็คเกจ android.graphics ที่ให้ API สำหรับการเรนเดอร์ 2D บน View Canvas ทำหน้าที่เหมือนผืนผ้าใบที่นักพัฒนาวาดรูปทรงเรขาคณิต ข้อความ รูปภาพ และเส้นทางโดยใช้วิธีการ draw* ต่างๆ ตามเอกสารสำหรับนักพัฒนา Android (2025) Canvas เป็นเครื่องมือหลักสำหรับการเรนเดอร์แบบกำหนดเองใน Android และใช้ร่วมกับคลาส Paint เพื่อกำหนดสไตล์และสี แต่ละการเรียก onDraw() จะได้รับอินสแตนซ์ Canvas ที่พร้อมใช้งาน ซึ่งเชื่อมโยงกับบัฟเฟอร์บิตแมปของวิว
ประเด็นสำคัญ
Canvas — เป็นคลาสกราฟิก 2D หลักใน Android ที่ให้วิธีการสำหรับการวาดบนระนาบ Canvas แต่ละอันเชื่อมโยงกับ Bitmap — พื้นที่หน่วยความจำที่บันทึกผลลัพธ์ของการดำเนินการวาด เมื่อ View ถูกวาดใหม่ ระบบจะส่งต่อไปยัง onDraw() ซึ่งเป็น Canvas ที่เชื่อมโยงกับบัฟเฟอร์ของ View นั้นแล้ว
Canvas ทำงานในระบบพิกัดที่ (0,0) คือมุมซ้ายบน แกน X ไปทางขวา แกน Y ลงด้านล่าง การดำเนินการวาดทั้งหมดใช้สถานะปัจจุบันของ Canvas รวมถึงเมทริกซ์การแปลง พื้นที่คลิป และสไตล์ Paint ตามเอกสาร Android (2025) Canvas API มีวิธีการ draw* มากกว่า 30 วิธีสำหรับประเภทต่าง ๆ ของพรอมิทีฟกราฟิก
ตั้งแต่ Android 3.0 (API 11) Canvas รองรับการเร่งด้วยฮาร์ดแวร์ผ่าน DisplayList ในโหมดเร่งความเร็ว การดำเนินการของ Canvas จะถูกบันทึกลงใน DisplayList และดำเนินการบน GPU ซึ่งให้ประสิทธิภาพที่เพิ่มขึ้นอย่างมาก แต่มีข้อจำกัด: การดำเนินการ Canvas บางอย่าง (clipPath กับคลิปที่ไม่ใช่ Path) ไม่ได้รับการสนับสนุนในโหมดเร่งความเร็ว
Canvas มีชุดวิธีการสำหรับวาดพรอมิทีฟพื้นฐาน แต่ละวิธีการรับพารามิเตอร์ทางเรขาคณิตและออบเจ็กต์ Paint ที่กำหนดสไตล์ภาพ
| วิธีการ | วัตถุประสงค์ | ตัวอย่าง |
|---|---|---|
| drawRect() | วาดสี่เหลี่ยมผืนผ้า | canvas.drawRect(10f, 10f, 100f, 100f, paint) |
| drawCircle() | วาดวงกลม | canvas.drawCircle(50f, 50f, 30f, paint) |
| drawLine() | วาดเส้น | canvas.drawLine(0f, 0f, 100f, 100f, paint) |
| drawText() | วาดข้อความ | canvas.drawText(“Hello”, 10f, 20f, paint) |
| drawBitmap() | วาดรูปภาพ | canvas.drawBitmap(bitmap, 0f, 0f, paint) |
| drawPath() | วาดเส้นทางตามอำเภอใจ | canvas.drawPath(path, paint) |
| drawOval() | วาดรูปวงรี | canvas.drawOval(rectF, paint) |
| drawArc() | วาดส่วนโค้งหรือเซกเตอร์ | canvas.drawArc(rectF, 0f, 90f, true, paint) |
แต่ละวิธีสามารถเรียกใช้ในลำดับใดก็ได้ — Canvas จะวาดตามลำดับการเรียก โดยวางองค์ประกอบใหม่ทับองค์ประกอบก่อนหน้า สำหรับองค์ประกอบที่ซับซ้อน จะใช้เทคนิคการเรนเดอร์หลายชั้นพร้อมการบันทึกและกู้คืนสถานะของ Canvas
Paint — เป็นคลาสที่กำหนดว่า Canvas จะวาดอย่างไร หากไม่มี Paint วิธีการของ Canvas ก็ไร้ประโยชน์ — มันกำหนดสี ความหนาของเส้น สไตล์การเติม ขนาดข้อความ เอฟเฟกต์ (เงา การไล่สี การเบลอ) และโหมดการผสม
Paint มีคุณสมบัติสำคัญ: color (สี), style (FILL, STROKE หรือ FILL_AND_STROKE), strokeWidth (ความหนาของเส้น), textSize (ขนาดข้อความ), isAntiAlias (การลดรอยหยัก), shader (การไล่สีหรือรูปภาพ) การสร้างออบเจ็กต์ Paint เป็นการดำเนินการที่ใช้ทรัพยากรมาก ดังนั้นควรสร้างครั้งเดียวและนำกลับมาใช้ใหม่
private val fillPaint = Paint().apply {
color = Color.parseColor("#FF6200EE")
style = Paint.Style.FILL
isAntiAlias = true
}
private val strokePaint = Paint().apply {
color = Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 4f
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(100f, 100f, 80f, fillPaint)
canvas.drawCircle(100f, 100f, 80f, strokePaint)
}
ในตัวอย่างนี้ ใช้ออบเจ็กต์ Paint สองอัน: อันหนึ่งสำหรับเติมวงกลมด้วยสีม่วง อีกอันสำหรับเส้นขอบสีขาว การแยกการเติมและเส้นขอบเป็นแนวปฏิบัติมาตรฐานที่ช่วยให้ควบคุมลักษณะที่ปรากฏได้อย่างยืดหยุ่นโดยไม่ต้องสร้างออบเจ็กต์ใหม่
สำหรับการเติมแบบไล่สี Paint รับ Shader — คลาสย่อย LinearGradient, RadialGradient, SweepGradient Shader เชื่อมโยงกับ Paint ผ่านเมธอด setShader() และกำหนดว่าสีเปลี่ยนแปลงอย่างไรทั่วพื้นที่วาด
Canvas รองรับการแปลงเมทริกซ์ของระบบพิกัด: translate() (การเลื่อน), rotate() (การหมุน), scale() (การปรับขนาด) และ skew() (การเอียง) การแปลงจะสะสม: แต่ละการเรียกที่ตามมาจะถูกนำไปใช้ทับรายการก่อนหน้า
การจัดการสแต็กการแปลงทำผ่าน save() และ restore() save() บันทึกสถานะปัจจุบันของ Canvas (เมทริกซ์, คลิป) ลงในสแต็ก restore() กู้คืนสถานะที่บันทึกล่าสุด ระหว่าง save() และ restore() สามารถใช้การแปลงแบบซ้อนได้
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.save() // Save initial state
canvas.translate(100f, 100f) // Translate origin
// First square
canvas.drawRect(0f, 0f, 50f, 50f, fillPaint)
canvas.save() // Save after translate
canvas.rotate(45f, 25f, 25f) // Rotate around square center
// Rotated square
canvas.drawRect(0f, 0f, 50f, 50f, strokePaint)
canvas.restore() // Restore after rotate
canvas.restore() // Restore initial state
}
ใช้ save() ก่อนเริ่มการแปลงและ restore() หลังจากเสร็จสิ้นเสมอ คู่ save/restore ที่ไม่สมดุลนำไปสู่การสะสมของเมทริกซ์และการเรนเดอร์องค์ประกอบที่ตามมาอย่างไม่ถูกต้อง
สำหรับกราฟิกที่ซับซ้อน Canvas จะรวมกับ Bitmap และ Path Bitmap ใช้สำหรับโหลดรูปภาพและแคชเฟรมที่เรนเดอร์แล้ว Path ใช้สำหรับสร้างเส้นโค้ง Bezier ตามอำเภอใจ รูปหลายเหลี่ยม และเส้นขอบที่ซับซ้อน
เทคนิคการเรนเดอร์นอกหน้าจอ: สร้าง Bitmap วาดฉากที่ซับซ้อนลงบนนั้นผ่าน Canvas ที่แยกต่างหาก จากนั้น Bitmap ที่เสร็จแล้วจะถูกแสดงผลใน onDraw() ผ่าน drawBitmap() ซึ่งช่วยให้สามารถอัปเดตกราฟิกที่ซับซ้อนได้โดยไม่ต้องวาดใหม่ทั้งหมดในทุกเฟรม
private var offscreenBitmap: Bitmap? = null
private var offscreenCanvas: Canvas? = null
fun buildOffscreenScene(width: Int, height: Int) {
offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
offscreenCanvas = Canvas(offscreenBitmap)
// Render scene once
offscreenCanvas!!.drawColor(Color.WHITE)
drawGrid(offscreenCanvas!!)
drawComplexShape(offscreenCanvas!!)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
offscreenBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
}
Path ใช้สำหรับสร้างเส้นขอบที่ซับซ้อน: เส้นโค้ง Bezier (quadTo, cubicTo), ส่วนโค้ง (arcTo), เส้นตรง (lineTo) Path สามารถปิด (close()) สำหรับรูปทรงที่เติมหรือเปิดสำหรับเส้น วิธีการหลัก drawPath() รวม Path และ Paint สำหรับการเรนเดอร์ขั้นสุดท้าย
ประสิทธิภาพการเรนเดอร์ Canvas ส่งผลโดยตรงต่อ FPS ของแอปพลิเคชัน ปัญหาหลัก: การจัดสรรหน่วยความจำมากเกินไปใน onDraw(), การเรียก invalidate() บ่อยครั้ง และการใช้การเร่งด้วยฮาร์ดแวร์ที่ไม่เหมาะสม
Paint, Path, Rect, RectF และออบเจ็กต์อื่นๆ ควรสร้างครั้งเดียวในตัวเริ่มต้นหรือเมธอด setup() การจัดสรรแต่ละครั้งใน onDraw() สร้างภาระให้กับตัวเก็บขยะ ซึ่งนำไปสู่การกระตุกขนาดเล็ก ข้อยกเว้นคือ static หรือ companion object สำหรับออบเจ็กต์ที่ไม่เปลี่ยนรูป
หาก View ส่วนใหญ่ถูกซ่อนอยู่นอกพื้นที่ที่มองเห็น (เช่น ใน ScrollView) ให้ใช้ canvas.clipRect() เพื่อจำกัดการเรนเดอร์ การเร่งด้วยฮาร์ดแวร์ในกรณีนี้จะปรับการคลิปพิกเซลที่มองไม่เห็นให้เหมาะสมยิ่งขึ้น
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Clip to visible area
canvas.save()
canvas.clipRect(scrollX, scrollY, scrollX + width, scrollY + height)
// Render large scene
renderScene(canvas)
canvas.restore()
}
ใช้ View.setLayerType() กับ LAYER_TYPE_HARDWARE สำหรับ View ที่มีกราฟิก Canvas ซับซ้อนซึ่งอัปเดตไม่บ่อย สำหรับแอนิเมชันที่อัปเดตบ่อย ให้ใช้ LAYER_TYPE_NONE เพื่อหลีกเลี่ยงการอัปเดตเลเยอร์ฮาร์ดแวร์ที่มีต้นทุนสูง ทำการโปรไฟล์โดยใช้ GPU Profile Rendering และ Android Studio Profiler
คำถามที่พบบ่อย
ได้ คุณสามารถสร้าง Canvas(Bitmap) — คอนสตรักเตอร์ที่รับ Bitmap ซึ่งใช้สำหรับการเรนเดอร์นอกหน้าจอ: คุณวาดบน Bitmap จากนั้นแสดงผลผ่าน drawBitmap() Canvas สามารถสร้างจาก Surface (สำหรับ SurfaceView) หรือผ่าน Picture ได้เช่นกัน
Canvas — เป็น API สำหรับวาดบน View ในเธรด UI SurfaceView — เป็น Surface ที่แยกต่างหากซึ่งสามารถอัปเดตจากเธรดพื้นหลังผ่าน Canvas ของตัวเอง (lockCanvas() / unlockCanvasAndPost()) SurfaceView เหมาะสำหรับเกมและวิดีโอ
สาเหตุที่พบบ่อยที่สุดคือคู่ save() และ restore() ที่ไม่สมดุล หากหลังจากการแปลง (translate, rotate) ไม่เรียก restore() Canvas จะยังคงอยู่ในระบบพิกัดที่ถูกปรับเปลี่ยน และองค์ประกอบที่ตามมาจะถูกวาดด้วยค่าชดเชย ตรวจสอบคู่ save()/restore() ของคุณ
ใช้ Paint.setTextAlign(Paint.Align.CENTER) สำหรับการจัดกึ่งกลางแนวนอน และ Paint.getTextBounds() หรือ Paint.descent() / ascent() สำหรับแนวตั้ง การจัดกึ่งกลาง: canvas.drawText(ข้อความ, centerX, centerY - (ascent + descent) / 2, paint)
Canvas เองไม่ได้สร้างแอนิเมชัน — มันเพียงแค่วาดสถานะปัจจุบัน แอนิเมชันทำได้โดยการเรียก invalidate() (หรือ postInvalidateOnAnimation()) อย่างต่อเนื่อง โดยวาดเฟรมใหม่ใน onDraw() แต่ละครั้ง สำหรับแอนิเมชันที่ราบรื่น ให้ใช้ ValueAnimator หรือ Choreographer เพื่อซิงค์กับ Vsync
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม