การวาดบนออบเจ็กต์ Canvas

Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Android ขั้นสูงใน Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ แต่ไม่จำเป็นต้องทำ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin

บทนำ

ใน Android คุณมีเทคนิคหลายอย่างที่ใช้ในการติดตั้งใช้งานกราฟิก 2 มิติและภาพเคลื่อนไหวที่กำหนดเองในมุมมองได้

นอกเหนือจากการใช้ drawable แล้ว คุณยังสร้างภาพวาด 2 มิติได้โดยใช้วิธีการวาดของคลาส Canvas Canvas คือพื้นผิวสำหรับวาดภาพ 2 มิติซึ่งมีวิธีการวาดภาพ ซึ่งจะเป็นประโยชน์เมื่อแอปต้องวาดซ้ำเป็นประจำ เนื่องจากสิ่งที่ผู้ใช้เห็นจะเปลี่ยนแปลงไปตามเวลา ใน Codelab นี้ คุณจะได้เรียนรู้วิธีสร้างและวาดบน Canvas ที่แสดงใน View

ประเภทของการดำเนินการที่คุณทำได้ใน Canvas มีดังนี้

  • เติมสีทั้งผืนผ้าใบ
  • วาดรูปร่าง เช่น สี่เหลี่ยมจัตุรัส ส่วนโค้ง และเส้นทางที่มีสไตล์ตามที่กำหนดไว้ในออบเจ็กต์ Paint ออบเจ็กต์ Paint จะจัดเก็บข้อมูลรูปแบบและสีเกี่ยวกับวิธีวาดเรขาคณิต (เช่น เส้น สี่เหลี่ยมผืนผ้า วงรี และเส้นทาง) หรือเช่น แบบอักษรของข้อความ
  • ใช้การเปลี่ยนรูปแบบ เช่น การแปล การปรับขนาด หรือการเปลี่ยนรูปแบบที่กำหนดเอง
  • ครอบตัด คือการใช้รูปร่างหรือเส้นทางกับ Canvas เพื่อกำหนดส่วนที่มองเห็นได้

วิธีคิดเกี่ยวกับการวาดภาพใน Android (แบบง่ายสุดๆ)

การวาดใน Android หรือระบบที่ทันสมัยอื่นๆ เป็นกระบวนการที่ซับซ้อนซึ่งประกอบด้วยเลเยอร์ของการแยกส่วนและการเพิ่มประสิทธิภาพไปจนถึงฮาร์ดแวร์ วิธีที่ Android วาดเป็นหัวข้อที่น่าสนใจซึ่งมีการเขียนถึงกันมาก และรายละเอียดของหัวข้อนี้อยู่นอกขอบเขตของ Codelab นี้

ในบริบทของโค้ดแล็บนี้และแอปที่วาดบน Canvas เพื่อแสดงในมุมมองแบบเต็มหน้าจอ คุณสามารถคิดถึงเรื่องนี้ได้ดังต่อไปนี้

  1. คุณต้องมีมุมมองเพื่อแสดงสิ่งที่คุณกำลังวาด ซึ่งอาจเป็นมุมมองใดมุมมองหนึ่งที่ระบบ Android จัดเตรียมไว้ หรือใน Codelab นี้ คุณจะสร้างมุมมองที่กำหนดเองซึ่งทำหน้าที่เป็นมุมมองเนื้อหาสำหรับแอป (MyCanvasView)
  2. มุมมองนี้เช่นเดียวกับมุมมองอื่นๆ มาพร้อมกับ Canvas ของตัวเอง (canvas)
  3. หากต้องการวาดบน Canvas ของ View ด้วยวิธีพื้นฐานที่สุด ให้แทนที่เมธอด onDraw() ของ View นั้น แล้ววาดบน Canvas ของ View
  4. เมื่อสร้างภาพวาด คุณต้องแคชสิ่งที่วาดไว้ก่อนหน้านี้ การแคชข้อมูลมีหลายวิธี วิธีหนึ่งคือการแคชในบิตแมป (extraBitmap) อีกวิธีคือการบันทึกประวัติสิ่งที่คุณวาดเป็นพิกัดและคำสั่ง
  5. หากต้องการวาดลงในบิตแมปแคช (extraBitmap) โดยใช้ Canvas Drawing API คุณต้องสร้าง Canvas แคช (extraCanvas) สำหรับบิตแมปแคช
  6. จากนั้นคุณจะวาดบน Canvas ของการแคช (extraCanvas) ซึ่งจะวาดลงในบิตแมปของการแคช (extraBitmap)
  7. หากต้องการแสดงทุกอย่างที่วาดบนหน้าจอ คุณต้องบอก Canvas ของ View (canvas) ให้วาดบิตแมปที่แคชไว้ (extraBitmap)

สิ่งที่คุณควรทราบอยู่แล้ว

  • วิธีสร้างแอปที่มีกิจกรรม เลย์เอาต์พื้นฐาน และเรียกใช้โดยใช้ Android Studio
  • วิธีเชื่อมโยงตัวแฮนเดิลเหตุการณ์กับมุมมอง
  • วิธีสร้างมุมมองที่กำหนดเอง

สิ่งที่คุณจะได้เรียนรู้

  • วิธีสร้าง Canvas และวาดบนนั้นเพื่อตอบสนองต่อการแตะของผู้ใช้

สิ่งที่คุณต้องดำเนินการ

  • สร้างแอปที่วาดเส้นบนหน้าจอเมื่อผู้ใช้แตะหน้าจอ
  • บันทึกเหตุการณ์การเคลื่อนไหว และวาดเส้นบน Canvas ที่แสดงในมุมมองที่กำหนดเองแบบเต็มหน้าจอบนหน้าจอเพื่อตอบสนอง

แอป MiniPaint ใช้มุมมองที่กำหนดเองเพื่อแสดงเส้นตามการสัมผัสของผู้ใช้ ดังที่แสดงในภาพหน้าจอด้านล่าง

ขั้นตอนที่ 1 สร้างโปรเจ็กต์ MiniPaint

  1. สร้างโปรเจ็กต์ Kotlin ใหม่ชื่อ MiniPaint ที่ใช้เทมเพลตกิจกรรมเปล่า
  2. เปิดไฟล์ app/res/values/colors.xml แล้วเพิ่ม 2 สีต่อไปนี้
<color name="colorBackground">#FFFF5500</color>
<color name="colorPaint">#FFFFEB3B</color>
  1. เปิด styles.xml
  2. ในองค์ประกอบหลักของสไตล์ AppTheme ที่ระบุ ให้แทนที่ DarkActionBar ด้วย NoActionBar ซึ่งจะเป็นการนำแถบการทำงานออกเพื่อให้คุณวาดแบบเต็มหน้าจอได้
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

ขั้นตอนที่ 2 สร้างคลาส MyCanvasView

ในขั้นตอนนี้ คุณจะสร้างมุมมองที่กำหนดเอง MyCanvasView สำหรับการวาด

  1. ในapp/java/com.example.android.minipaintแพ็กเกจ ให้สร้างใหม่ > ไฟล์/คลาส Kotlin ชื่อ MyCanvasView
  2. สร้างคลาส MyCanvasView ให้ขยายคลาส View และส่งใน context: Context ยอมรับการนำเข้าที่แนะนำ
import android.content.Context
import android.view.View

class MyCanvasView(context: Context) : View(context) {
}

ขั้นตอนที่ 3 ตั้งค่า MyCanvasView เป็นมุมมองเนื้อหา

หากต้องการแสดงสิ่งที่จะวาดใน MyCanvasView คุณต้องตั้งค่าเป็นมุมมองเนื้อหาของ MainActivity

  1. เปิด strings.xml และกำหนดสตริงที่จะใช้สำหรับคำอธิบายเนื้อหาของมุมมอง
<string name="canvasContentDescription">Mini Paint is a simple line drawing app.
   Drag your fingers to draw. Rotate the phone to clear.</string>
  1. เปิด MainActivity.kt
  2. ใน onCreate() ให้ลบ setContentView(R.layout.activity_main)
  3. สร้างอินสแตนซ์ของ MyCanvasView
val myCanvasView = MyCanvasView(this)
  1. จากนั้นขอแบบเต็มหน้าจอสำหรับเลย์เอาต์ของ myCanvasView โดยทำได้โดยตั้งค่าSYSTEM_UI_FLAG_FULLSCREENเป็นเปิดmyCanvasView ด้วยวิธีนี้ มุมมองจะแสดงเต็มหน้าจอ
myCanvasView.systemUiVisibility = SYSTEM_UI_FLAG_FULLSCREEN
  1. เพิ่มคำอธิบายเนื้อหา
myCanvasView.contentDescription = getString(R.string.canvasContentDescription)
  1. จากนั้นตั้งค่ามุมมองเนื้อหาเป็น myCanvasView
setContentView(myCanvasView)
  1. เรียกใช้แอป คุณจะเห็นหน้าจอสีขาวทั้งหมด เนื่องจาก Canvas ไม่มีขนาดและคุณยังไม่ได้วาดอะไรเลย

ขั้นตอนที่ 1 ลบล้าง onSizeChanged()

ระบบ Android จะเรียกใช้เมธอด onSizeChanged() ทุกครั้งที่มุมมองเปลี่ยนขนาด เนื่องจากมุมมองเริ่มต้นโดยไม่มีขนาด ระบบจึงเรียกใช้เมธอด onSizeChanged() ของมุมมองหลังจากที่กิจกรรมสร้างและขยายมุมมองเป็นครั้งแรก onSizeChanged() วิธีนี้จึงเป็นที่ที่เหมาะที่สุดในการสร้างและตั้งค่า Canvas ของมุมมอง

  1. ใน MyCanvasView ที่ระดับคลาส ให้กำหนดตัวแปรสำหรับ Canvas และบิตแมป โทรหา extraCanvas และ extraBitmap ซึ่งเป็นบิตแมปและ Canvas สำหรับแคชสิ่งที่วาดไว้ก่อนหน้านี้
private lateinit var extraCanvas: Canvas
private lateinit var extraBitmap: Bitmap
  1. กำหนดตัวแปรระดับคลาส backgroundColor สำหรับสีพื้นหลังของ Canvas และเริ่มต้นเป็น colorBackground ที่คุณกำหนดไว้ก่อนหน้านี้
private val backgroundColor = ResourcesCompat.getColor(resources, R.color.colorBackground, null)
  1. ใน MyCanvasView ให้ลบล้างวิธีการ onSizeChanged() ระบบ Android จะเรียกใช้เมธอดการเรียกกลับนี้โดยมีขนาดหน้าจอที่เปลี่ยนแปลงแล้ว นั่นคือ มีความกว้างและความสูงใหม่ (ที่จะเปลี่ยน) และความกว้างและความสูงเดิม (ที่จะเปลี่ยนจาก)
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
   super.onSizeChanged(width, height, oldWidth, oldHeight)
}
  1. ภายใน onSizeChanged() ให้สร้างอินสแตนซ์ของ Bitmap โดยใช้ความกว้างและความสูงใหม่ ซึ่งเป็นขนาดหน้าจอ แล้วกำหนดให้กับ extraBitmap อาร์กิวเมนต์ที่ 3 คือการกำหนดค่าสีบิตแมป ARGB_8888 จัดเก็บแต่ละสีใน 4 ไบต์และเป็นรูปแบบที่แนะนำ
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
  1. สร้างอินสแตนซ์ Canvas จาก extraBitmap แล้วมอบหมายให้ extraCanvas
 extraCanvas = Canvas(extraBitmap)
  1. ระบุสีพื้นหลังที่จะใช้เติม extraCanvas
extraCanvas.drawColor(backgroundColor)
  1. เมื่อดูที่ onSizeChanged() ระบบจะสร้างบิตแมปและ Canvas ใหม่ทุกครั้งที่ฟังก์ชันทำงาน คุณต้องใช้บิตแมปใหม่เนื่องจากขนาดมีการเปลี่ยนแปลง อย่างไรก็ตาม นี่คือข้อบกพร่องด้านหน่วยความจำที่ทำให้บิตแมปเก่าๆ ยังคงอยู่ หากต้องการแก้ไขปัญหานี้ ให้รีไซเคิล extraBitmap ก่อนสร้างรายการถัดไปโดยเพิ่มโค้ดนี้หลังการเรียกใช้ super ทันที
if (::extraBitmap.isInitialized) extraBitmap.recycle()

ขั้นตอนที่ 2 ลบล้าง onDraw()

งานวาดทั้งหมดสำหรับ MyCanvasView จะเกิดขึ้นใน onDraw()

เริ่มต้นด้วยการแสดง Canvas โดยเติมสีพื้นหลังที่คุณตั้งค่าไว้ใน onSizeChanged() ให้เต็มหน้าจอ

  1. ลบล้าง onDraw() และวาดเนื้อหาของ extraBitmap ที่แคชไว้บน Canvas ที่เชื่อมโยงกับมุมมอง วิธี drawBitmap() Canvas มีหลายเวอร์ชัน ในโค้ดนี้ คุณจะระบุบิตแมป พิกัด x และ y (เป็นพิกเซล) ของมุมซ้ายบน และ null สำหรับ Paint เนื่องจากคุณจะตั้งค่าในภายหลัง
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
canvas.drawBitmap(extraBitmap, 0f, 0f, null)
}


โปรดทราบว่า Canvas ที่ส่งไปยัง onDraw() และระบบใช้เพื่อแสดงบิตแมปจะแตกต่างจาก Canvas ที่คุณสร้างในเมธอด onSizeChanged() และใช้เพื่อวาดบนบิตแมป

  1. เรียกใช้แอป คุณควรเห็นทั้งหน้าจอเป็นสีพื้นหลังที่ระบุ

หากต้องการวาด คุณต้องมีออบเจ็กต์ Paint ที่ระบุวิธีจัดรูปแบบเมื่อวาด และออบเจ็กต์ Path ที่ระบุสิ่งที่กำลังวาด

ขั้นตอนที่ 1 เริ่มต้นออบเจ็กต์ Paint

  1. ใน MyCanvasView.kt ที่ระดับไฟล์ด้านบน ให้กำหนดค่าคงที่สำหรับความกว้างของเส้น
private const val STROKE_WIDTH = 12f // has to be float
  1. ที่ระดับคลาสของ MyCanvasView ให้กำหนดตัวแปร drawColor สำหรับเก็บสีที่จะวาด และเริ่มต้นด้วยทรัพยากร colorPaint ที่คุณกำหนดไว้ก่อนหน้านี้
private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)
  1. ที่ระดับชั้นเรียน ให้เพิ่มตัวแปร paint สำหรับออบเจ็กต์ Paint และเริ่มต้นใช้งานดังนี้
// Set up the paint with which to draw.
private val paint = Paint().apply {
   color = drawColor
   // Smooths out edges of what is drawn without affecting shape.
   isAntiAlias = true
   // Dithering affects how colors with higher-precision than the device are down-sampled.
   isDither = true
   style = Paint.Style.STROKE // default: FILL
   strokeJoin = Paint.Join.ROUND // default: MITER
   strokeCap = Paint.Cap.ROUND // default: BUTT
   strokeWidth = STROKE_WIDTH // default: Hairline-width (really thin)
}
  • color ของ paint คือ drawColor ที่คุณกำหนดไว้ก่อนหน้านี้
  • isAntiAlias กำหนดว่าจะใช้การปรับขอบให้เรียบหรือไม่ การตั้งค่า isAntiAlias เป็น true จะทำให้ขอบของสิ่งที่วาดเรียบเนียนขึ้นโดยไม่ส่งผลต่อรูปร่าง
  • isDither เมื่อ true จะส่งผลต่อวิธีลดความละเอียดของสีที่มีความแม่นยำสูงกว่าอุปกรณ์ ตัวอย่างเช่น การดิทเทอริงเป็นวิธีที่พบได้บ่อยที่สุดในการลดช่วงสีของรูปภาพให้เหลือ 256 สี (หรือน้อยกว่า)
  • style ตั้งค่าประเภทการวาดภาพที่จะทำกับเส้น ซึ่งก็คือเส้นตรง Paint.Style ระบุว่ารูปทรงเรขาคณิตที่วาดนั้นเป็นแบบเติมสี ขีดเส้น หรือทั้งสองอย่าง (ในสีเดียวกัน) โดยค่าเริ่มต้น ระบบจะเติมสีให้กับออบเจ็กต์ที่ใช้สี ("เติม" จะระบายสีด้านในของรูปร่าง ส่วน "เส้นขีด" จะระบายสีตามโครงร่าง)
  • strokeJoin ของ Paint.Join จะระบุวิธีที่เส้นและส่วนโค้งเชื่อมต่อกันในเส้นทางที่ขีด ค่าเริ่มต้นคือ MITER
  • strokeCap กำหนดรูปร่างของปลายเส้นให้เป็นหมวก Paint.Cap ระบุวิธีเริ่มต้นและสิ้นสุดเส้นและเส้นทางที่ขีด ค่าเริ่มต้นคือ BUTT
  • strokeWidth ระบุความกว้างของเส้นในหน่วยพิกเซล ค่าเริ่มต้นคือความกว้างของเส้นผม ซึ่งบางมาก จึงตั้งค่าเป็นค่าคงที่ STROKE_WIDTH ที่คุณกำหนดไว้ก่อนหน้านี้

ขั้นตอนที่ 2 เริ่มต้นออบเจ็กต์เส้นทาง

Path คือเส้นทางของสิ่งที่ผู้ใช้กำลังวาด

  1. ใน MyCanvasView ให้เพิ่มตัวแปร path และเริ่มต้นด้วยออบเจ็กต์ Path เพื่อจัดเก็บเส้นทางที่วาดเมื่อติดตามการแตะของผู้ใช้บนหน้าจอ นำเข้า android.graphics.Path สำหรับ Path
private var path = Path()

ขั้นตอนที่ 1 ตอบสนองต่อการเคลื่อนไหวบนจอแสดงผล

ระบบจะเรียกใช้เมธอด onTouchEvent() ในมุมมองทุกครั้งที่ผู้ใช้แตะจอแสดงผล

  1. ใน MyCanvasView ให้ลบล้างเมธอด onTouchEvent() เพื่อแคชพิกัด x และ y ของ event ที่ส่งเข้ามา จากนั้นใช้whenเพื่อจัดการเหตุการณ์การเคลื่อนไหวสำหรับการแตะหน้าจอ การเลื่อนบนหน้าจอ และการปล่อยการแตะบนหน้าจอ เหตุการณ์เหล่านี้เป็นเหตุการณ์ที่น่าสนใจสำหรับการวาดเส้นบนหน้าจอ สำหรับเหตุการณ์แต่ละประเภท ให้เรียกใช้เมธอดยูทิลิตีตามที่แสดงในโค้ดด้านล่าง ดูรายการเหตุการณ์สัมผัสทั้งหมดได้ในเอกสารประกอบของคลาส MotionEvent
override fun onTouchEvent(event: MotionEvent): Boolean {
   motionTouchEventX = event.x
   motionTouchEventY = event.y

   when (event.action) {
       MotionEvent.ACTION_DOWN -> touchStart()
       MotionEvent.ACTION_MOVE -> touchMove()
       MotionEvent.ACTION_UP -> touchUp()
   }
   return true
}
  1. ที่ระดับคลาส ให้เพิ่มตัวแปร motionTouchEventX และ motionTouchEventY ที่ขาดหายไปสำหรับการแคชพิกัด x และ y ของเหตุการณ์การแตะปัจจุบัน (พิกัด MotionEvent) เริ่มต้นด้วย 0f
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f
  1. สร้าง Stub สำหรับฟังก์ชัน 3 รายการ ได้แก่ touchStart(), touchMove() และ touchUp()
private fun touchStart() {}

private fun touchMove() {}

private fun touchUp() {}
  1. โค้ดควรจะสร้างและเรียกใช้ได้ แต่คุณจะยังไม่เห็นอะไรที่แตกต่างจากพื้นหลังสี

ขั้นตอนที่ 2 ใช้ touchStart()

ระบบจะเรียกใช้เมธอดนี้เมื่อผู้ใช้แตะหน้าจอเป็นครั้งแรก

  1. ที่ระดับคลาส ให้เพิ่มตัวแปรเพื่อแคชค่า x และ y ล่าสุด หลังจากที่ผู้ใช้หยุดเคลื่อนที่และยกนิ้วที่สัมผัสอยู่ จุดเหล่านี้จะเป็นจุดเริ่มต้นของเส้นทางถัดไป (ส่วนถัดไปของเส้นที่จะวาด)
private var currentX = 0f
private var currentY = 0f
  1. ใช้เมธอด touchStart() ดังนี้ รีเซ็ต path ย้ายไปยังพิกัด x-y ของเหตุการณ์การแตะ (motionTouchEventX และ motionTouchEventY) แล้วกําหนด currentX และ currentY ให้กับค่านั้น
private fun touchStart() {
   path.reset()
   path.moveTo(motionTouchEventX, motionTouchEventY)
   currentX = motionTouchEventX
   currentY = motionTouchEventY
}

ขั้นตอนที่ 3 ใช้ touchMove()

  1. ที่ระดับชั้นเรียน ให้เพิ่มตัวแปร touchTolerance แล้วตั้งค่าเป็น ViewConfiguration.get(context).scaledTouchSlop
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop

การใช้เส้นทางทำให้ไม่จำเป็นต้องวาดทุกพิกเซลและขอรีเฟรชการแสดงผลทุกครั้ง แต่คุณสามารถ (และควร) ประมาณเส้นทางระหว่างจุดต่างๆ เพื่อให้ได้ประสิทธิภาพที่ดียิ่งขึ้น

  • หากนิ้วขยับเพียงเล็กน้อย ก็ไม่จำเป็นต้องวาด
  • หากนิ้วเคลื่อนที่น้อยกว่าระยะทาง touchTolerance อย่าลาก
  • scaledTouchSlop จะแสดงระยะทางเป็นพิกเซลที่การแตะสามารถเลื่อนไปได้ก่อนที่ระบบจะคิดว่าผู้ใช้กำลังเลื่อน
  1. กำหนดtouchMove() คำนวณระยะทางที่เดินทาง (dx, dy) สร้างเส้นโค้งระหว่าง 2 จุดและจัดเก็บไว้ใน path อัปเดตยอดรวม currentX และ currentY ที่ทำงานอยู่ และวาด path จากนั้นเรียกใช้ invalidate() เพื่อบังคับให้วาดหน้าจอใหม่ด้วย path ที่อัปเดต
private fun touchMove() {
   val dx = Math.abs(motionTouchEventX - currentX)
   val dy = Math.abs(motionTouchEventY - currentY)
   if (dx >= touchTolerance || dy >= touchTolerance) {
       // QuadTo() adds a quadratic bezier from the last point,
       // approaching control point (x1,y1), and ending at (x2,y2).
       path.quadTo(currentX, currentY, (motionTouchEventX + currentX) / 2, (motionTouchEventY + currentY) / 2)
       currentX = motionTouchEventX
       currentY = motionTouchEventY
       // Draw the path in the extra bitmap to cache it.
       extraCanvas.drawPath(path, paint)
   }
   invalidate()
}

รายละเอียดเพิ่มเติมเกี่ยวกับวิธีนี้

  1. คำนวณระยะทางที่เคลื่อนที่ (dx, dy)
  2. หากการเคลื่อนไหวอยู่ไกลกว่าความคลาดเคลื่อนในการสัมผัส ให้เพิ่มกลุ่มลงในเส้นทาง
  3. ตั้งจุดเริ่มต้นของกลุ่มถัดไปเป็นจุดสิ้นสุดของกลุ่มนี้
  4. การใช้ quadTo() แทน lineTo() จะสร้างเส้นที่วาดได้อย่างราบรื่นโดยไม่มีมุม ดูเส้นโค้งเบซิเยร์
  5. เรียกใช้ invalidate() เพื่อ (เรียกใช้ onDraw() และ) วาดมุมมองใหม่

ขั้นตอนที่ 4: ใช้ touchUp()

เมื่อผู้ใช้ปล่อยนิ้ว สิ่งที่ต้องทำคือรีเซ็ตเส้นทางเพื่อไม่ให้วาดซ้ำ ไม่มีการวาดภาพ จึงไม่จำเป็นต้องลบล้าง

  1. ใช้เมธอด touchUp()
private fun touchUp() {
   // Reset the path so it doesn't get drawn again.
   path.reset()
}
  1. เรียกใช้โค้ดและใช้นิ้ววาดบนหน้าจอ โปรดทราบว่าหากหมุนอุปกรณ์ ระบบจะล้างหน้าจอเนื่องจากไม่ได้บันทึกสถานะการวาด สำหรับแอปตัวอย่างนี้ เราออกแบบมาเช่นนี้เพื่อให้ผู้ใช้มีวิธีง่ายๆ ในการล้างหน้าจอ

ขั้นตอนที่ 5: วาดกรอบรอบภาพร่าง

ขณะที่ผู้ใช้วาดบนหน้าจอ แอปจะสร้างเส้นทางและบันทึกลงในบิตแมป extraBitmap เมธอด onDraw() จะแสดงบิตแมปเพิ่มเติมใน Canvas ของ View คุณวาดต่อได้ใน onDraw() เช่น คุณวาดรูปร่างหลังจากวาดบิตแมปได้

ในขั้นตอนนี้ คุณจะวาดกรอบรอบขอบของรูปภาพ

  1. ใน MyCanvasView ให้เพิ่มตัวแปรชื่อ frame ซึ่งมีออบเจ็กต์ Rect
private lateinit var frame: Rect
  1. ที่ส่วนท้ายของ onSizeChanged() define an inset และเพิ่มโค้ดเพื่อสร้าง Rect ที่จะใช้สำหรับเฟรม โดยใช้ขนาดใหม่และระยะขอบ
// Calculate a rectangular frame around the picture.
val inset = 40
frame = Rect(inset, inset, width - inset, height - inset)
  1. ใน onDraw()หลังจากวาดบิตแมปแล้ว ให้วาดสี่เหลี่ยมผืนผ้า
// Draw a frame around the canvas.
canvas.drawRect(frame, paint)
  1. เรียกใช้แอป สังเกตเฟรม

งาน (ไม่บังคับ): จัดเก็บข้อมูลในเส้นทาง

ในแอปปัจจุบัน ระบบจะจัดเก็บข้อมูลการวาดในบิตแมป แม้ว่านี่จะเป็นโซลูชันที่ดี แต่ก็ไม่ใช่เพียงวิธีเดียวที่เป็นไปได้ในการจัดเก็บข้อมูลการวาด วิธีจัดเก็บประวัติการวาดขึ้นอยู่กับแอปและความต้องการต่างๆ ของคุณ เช่น หากคุณวาดรูปร่าง คุณสามารถบันทึกรายการรูปร่างพร้อมตำแหน่งและขนาดได้ สำหรับแอป MiniPaint คุณสามารถบันทึกเส้นทางเป็น Path ได้ หากต้องการลองใช้ฟีเจอร์นี้ ให้ดูโครงร่างทั่วไปเกี่ยวกับวิธีดำเนินการได้ที่ด้านล่าง

  1. ใน MyCanvasView ให้นำโค้ดทั้งหมดสำหรับ extraCanvas และ extraBitmap ออก
  2. เพิ่มตัวแปรสำหรับเส้นทางที่ผ่านมาและเส้นทางที่กำลังวาด
// Path representing the drawing so far
private val drawing = Path()

// Path representing what's currently being drawn
private val curPath = Path()
  1. ใน onDraw() ให้วาดเส้นทางที่จัดเก็บไว้และเส้นทางปัจจุบันแทนที่จะวาดบิตแมป
// Draw the drawing so far
canvas.drawPath(drawing, paint)
// Draw any current squiggle
canvas.drawPath(curPath, paint)
// Draw a frame around the canvas
canvas.drawRect(frame, paint)
  1. ใน touchUp() ให้เพิ่มเส้นทางปัจจุบันไปยังเส้นทางก่อนหน้า แล้วรีเซ็ตเส้นทางปัจจุบัน
// Add the current path to the drawing so far
drawing.addPath(curPath)
// Rewind the current path for the next touch
curPath.reset()
  1. เรียกใช้แอปของคุณ และแน่นอนว่าไม่ควรมีความแตกต่างใดๆ

ดาวน์โหลดโค้ดสำหรับ Codelab ที่เสร็จสมบูรณ์

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-canvas


หรือคุณจะดาวน์โหลดที่เก็บเป็นไฟล์ Zip, แตกไฟล์ และเปิดใน Android Studio ก็ได้

ดาวน์โหลด Zip

  • Canvas คือพื้นผิวสำหรับวาดภาพ 2 มิติที่มีวิธีการวาด
  • Canvas สามารถเชื่อมโยงกับอินสแตนซ์ View ที่แสดงได้
  • ออบเจ็กต์ Paint จะเก็บข้อมูลรูปแบบและสีเกี่ยวกับวิธีวาดเรขาคณิต (เช่น เส้น สี่เหลี่ยมผืนผ้า วงรี และเส้นทาง) และข้อความ
  • รูปแบบทั่วไปในการทำงานกับ Canvas คือการสร้างมุมมองที่กำหนดเองและลบล้างเมธอด onDraw() และ onSizeChanged()
  • แทนที่เมธอด onTouchEvent() เพื่อบันทึกการแตะของผู้ใช้และตอบสนองต่อการแตะเหล่านั้นด้วยการวาดสิ่งต่างๆ
  • คุณใช้บิตแมปเพิ่มเติมเพื่อแคชข้อมูลสำหรับการวาดภาพที่มีการเปลี่ยนแปลงเมื่อเวลาผ่านไปได้ หรือจะจัดเก็บรูปร่างหรือเส้นทางก็ได้

หลักสูตร Udacity:

เอกสารประกอบสำหรับนักพัฒนาแอป Android

ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้

  • มอบหมายการบ้านหากจำเป็น
  • สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
  • ให้คะแนนงานการบ้าน

ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม

หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ

ตอบคำถามต่อไปนี้

คำถามที่ 1

คุณต้องมีคอมโพเนนต์ใดต่อไปนี้ในการทำงานกับ Canvas เลือกได้มากกว่า 1 ข้อ

Bitmap

Paint

Path

View

คำถามที่ 2

การเรียกใช้ invalidate() จะทำอะไร (โดยทั่วไป)

▢ ทำให้แอปของคุณไม่ถูกต้องและรีสตาร์ท

▢ ลบภาพวาดออกจากบิตแมป

▢ ระบุว่าไม่ควรเรียกใช้โค้ดก่อนหน้า

▢ บอกระบบว่าต้องวาดหน้าจอใหม่

คำถามที่ 3

ออบเจ็กต์ Canvas, Bitmap และ Paint มีฟังก์ชันอะไรบ้าง

▢ พื้นผิวการวาดภาพ 2 มิติ บิตแมปที่แสดงบนหน้าจอ ข้อมูลการจัดรูปแบบสำหรับการวาดภาพ

▢ พื้นผิวการวาดภาพ 3 มิติ บิตแมปสำหรับการแคชเส้นทาง ข้อมูลการจัดรูปแบบสำหรับการวาด

▢ พื้นผิวการวาดภาพ 2 มิติ บิตแมปที่แสดงบนหน้าจอ การจัดรูปแบบสำหรับมุมมอง

▢ แคชสำหรับข้อมูลการวาด บิตแมปที่จะวาด สไตล์สำหรับวาด

ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin