การวาดบนวัตถุ Canvas

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

บทนำ

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

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

ประเภทการดําเนินการที่คุณดําเนินการบนผืนผ้าใบได้มีดังนี้

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

วิธีคิดภาพวาดของ Android (ง่ายสุดๆ)

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

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

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

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

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

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

  • วิธีสร้าง Canvas และวาดรูปให้สอดคล้องกับการโต้ตอบของผู้ใช้

สิ่งที่คุณจะทํา

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

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

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

  1. สร้างโปรเจ็กต์ Kotlin ใหม่ชื่อ MiniPaint ที่ใช้เทมเพลต Empty Activity
  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 ให้สร้าง New > Kotlin File/Class ชื่อ 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. เรียกใช้แอป คุณจะเห็นหน้าจอสีขาวล้วนเนื่องจากผืนผ้าใบยังไม่มีขนาดและคุณยังไม่ได้วาดภาพใดๆ

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

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

  1. ใน MyCanvasView ในระดับตัวแปร ให้กําหนดตัวแปรสําหรับ Canvas และบิตแมป โทรหา extraCanvas และ extraBitmap นี่คือบิตแมปและภาพพิมพ์แคนวาสสําหรับแคชสิ่งที่วาดไว้ก่อนหน้านี้
private lateinit var extraCanvas: Canvas
private lateinit var extraBitmap: Bitmap
  1. กําหนดตัวแปรระดับชั้นเรียน backgroundColor สําหรับสีพื้นหลังของผืนผ้าใบและเริ่มต้นตัวแปรเป็น 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()

เริ่มจากแสดงภาพพิมพ์แคนวาส แล้วใส่สีพื้นหลังให้กับพื้นหลังที่คุณตั้งค่าไว้ใน onSizeChanged()

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


โปรดสังเกตว่าผืนผ้าใบที่ระบบส่งมายัง onDraw() และใช้โดยระบบเพื่อแสดงบิตแมปนั้นแตกต่างจากภาพที่สร้างด้วยเมธอด 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 ระบุว่ามีการเติมค่าดั้งเดิมที่วาด วาดเส้น หรือทั้งสองอย่าง (สีเดียวกัน) ค่าเริ่มต้นคือเติมวัตถุที่ใช้สี ("Fi&&tt; เปลี่ยนสีด้านในของรูปร่างในขณะที่"str" ทําตามโครงร่าง)
  • 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. สร้างตัวยึดตําแหน่งสําหรับฟังก์ชัน 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() จะสร้างเส้นที่วาดได้ลื่นไหลโดยไม่มีมุม ดู Bezier Curves
  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() จะแสดงบิตแมปเพิ่มเติมในผืนผ้าใบของมุมมอง คุณวาดได้มากขึ้นใน onDraw() ตัวอย่างเช่น คุณสามารถวาดรูปร่างได้หลังจากที่วาดบิตแมปแล้ว

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

  1. ใน MyCanvasView ให้เพิ่มตัวแปรชื่อ frame ที่มีออบเจ็กต์ Rect
private lateinit var frame: Rect
  1. ในตอนท้ายของ onSizeChanged() ให้กําหนดส่วนที่แทรก แล้วเพิ่มโค้ดเพื่อสร้าง 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 มีข้อมูลสไตล์และสีเกี่ยวกับวิธีวาดรูปทรงเรขาคณิต (เช่น เส้น สี่เหลี่ยมผืนผ้า วงรี และเส้นทาง) และข้อความ
  • รูปแบบที่พบบ่อยสําหรับการทํางานกับภาพพิมพ์แคนวาสคือการสร้างมุมมองที่กําหนดเองและลบล้างเมธอด 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 ขั้นสูงสําหรับ Android ใน Kotlin