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 เพื่อแสดงในมุมมองแบบเต็มหน้าจอ คุณสามารถคิดถึงเรื่องนี้ได้ดังต่อไปนี้

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

ขั้นตอนที่ 1 สร้างโปรเจ็กต์ MiniPaint
- สร้างโปรเจ็กต์ Kotlin ใหม่ชื่อ MiniPaint ที่ใช้เทมเพลตกิจกรรมเปล่า
- เปิดไฟล์
app/res/values/colors.xmlแล้วเพิ่ม 2 สีต่อไปนี้
<color name="colorBackground">#FFFF5500</color>
<color name="colorPaint">#FFFFEB3B</color>- เปิด
styles.xml - ในองค์ประกอบหลักของสไตล์
AppThemeที่ระบุ ให้แทนที่DarkActionBarด้วยNoActionBarซึ่งจะเป็นการนำแถบการทำงานออกเพื่อให้คุณวาดแบบเต็มหน้าจอได้
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">ขั้นตอนที่ 2 สร้างคลาส MyCanvasView
ในขั้นตอนนี้ คุณจะสร้างมุมมองที่กำหนดเอง MyCanvasView สำหรับการวาด
- ใน
app/java/com.example.android.minipaintแพ็กเกจ ให้สร้างใหม่ > ไฟล์/คลาส Kotlin ชื่อMyCanvasView - สร้างคลาส
MyCanvasViewให้ขยายคลาสViewและส่งในcontext: Contextยอมรับการนำเข้าที่แนะนำ
import android.content.Context
import android.view.View
class MyCanvasView(context: Context) : View(context) {
}ขั้นตอนที่ 3 ตั้งค่า MyCanvasView เป็นมุมมองเนื้อหา
หากต้องการแสดงสิ่งที่จะวาดใน MyCanvasView คุณต้องตั้งค่าเป็นมุมมองเนื้อหาของ MainActivity
- เปิด
strings.xmlและกำหนดสตริงที่จะใช้สำหรับคำอธิบายเนื้อหาของมุมมอง
<string name="canvasContentDescription">Mini Paint is a simple line drawing app.
Drag your fingers to draw. Rotate the phone to clear.</string>- เปิด
MainActivity.kt - ใน
onCreate()ให้ลบsetContentView(R.layout.activity_main) - สร้างอินสแตนซ์ของ
MyCanvasView
val myCanvasView = MyCanvasView(this)- จากนั้นขอแบบเต็มหน้าจอสำหรับเลย์เอาต์ของ
myCanvasViewโดยทำได้โดยตั้งค่าSYSTEM_UI_FLAG_FULLSCREENเป็นเปิดmyCanvasViewด้วยวิธีนี้ มุมมองจะแสดงเต็มหน้าจอ
myCanvasView.systemUiVisibility = SYSTEM_UI_FLAG_FULLSCREEN- เพิ่มคำอธิบายเนื้อหา
myCanvasView.contentDescription = getString(R.string.canvasContentDescription)- จากนั้นตั้งค่ามุมมองเนื้อหาเป็น
myCanvasView
setContentView(myCanvasView)- เรียกใช้แอป คุณจะเห็นหน้าจอสีขาวทั้งหมด เนื่องจาก Canvas ไม่มีขนาดและคุณยังไม่ได้วาดอะไรเลย
ขั้นตอนที่ 1 ลบล้าง onSizeChanged()
ระบบ Android จะเรียกใช้เมธอด onSizeChanged() ทุกครั้งที่มุมมองเปลี่ยนขนาด เนื่องจากมุมมองเริ่มต้นโดยไม่มีขนาด ระบบจึงเรียกใช้เมธอด onSizeChanged() ของมุมมองหลังจากที่กิจกรรมสร้างและขยายมุมมองเป็นครั้งแรก onSizeChanged() วิธีนี้จึงเป็นที่ที่เหมาะที่สุดในการสร้างและตั้งค่า Canvas ของมุมมอง
- ใน
MyCanvasViewที่ระดับคลาส ให้กำหนดตัวแปรสำหรับ Canvas และบิตแมป โทรหาextraCanvasและextraBitmapซึ่งเป็นบิตแมปและ Canvas สำหรับแคชสิ่งที่วาดไว้ก่อนหน้านี้
private lateinit var extraCanvas: Canvas
private lateinit var extraBitmap: Bitmap- กำหนดตัวแปรระดับคลาส
backgroundColorสำหรับสีพื้นหลังของ Canvas และเริ่มต้นเป็นcolorBackgroundที่คุณกำหนดไว้ก่อนหน้านี้
private val backgroundColor = ResourcesCompat.getColor(resources, R.color.colorBackground, null)- ใน
MyCanvasViewให้ลบล้างวิธีการonSizeChanged()ระบบ Android จะเรียกใช้เมธอดการเรียกกลับนี้โดยมีขนาดหน้าจอที่เปลี่ยนแปลงแล้ว นั่นคือ มีความกว้างและความสูงใหม่ (ที่จะเปลี่ยน) และความกว้างและความสูงเดิม (ที่จะเปลี่ยนจาก)
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
super.onSizeChanged(width, height, oldWidth, oldHeight)
}- ภายใน
onSizeChanged()ให้สร้างอินสแตนซ์ของBitmapโดยใช้ความกว้างและความสูงใหม่ ซึ่งเป็นขนาดหน้าจอ แล้วกำหนดให้กับextraBitmapอาร์กิวเมนต์ที่ 3 คือการกำหนดค่าสีบิตแมปARGB_8888จัดเก็บแต่ละสีใน 4 ไบต์และเป็นรูปแบบที่แนะนำ
extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)- สร้างอินสแตนซ์
CanvasจากextraBitmapแล้วมอบหมายให้extraCanvas
extraCanvas = Canvas(extraBitmap)- ระบุสีพื้นหลังที่จะใช้เติม
extraCanvas
extraCanvas.drawColor(backgroundColor)- เมื่อดูที่
onSizeChanged()ระบบจะสร้างบิตแมปและ Canvas ใหม่ทุกครั้งที่ฟังก์ชันทำงาน คุณต้องใช้บิตแมปใหม่เนื่องจากขนาดมีการเปลี่ยนแปลง อย่างไรก็ตาม นี่คือข้อบกพร่องด้านหน่วยความจำที่ทำให้บิตแมปเก่าๆ ยังคงอยู่ หากต้องการแก้ไขปัญหานี้ ให้รีไซเคิลextraBitmapก่อนสร้างรายการถัดไปโดยเพิ่มโค้ดนี้หลังการเรียกใช้superทันที
if (::extraBitmap.isInitialized) extraBitmap.recycle()ขั้นตอนที่ 2 ลบล้าง onDraw()
งานวาดทั้งหมดสำหรับ MyCanvasView จะเกิดขึ้นใน onDraw()
เริ่มต้นด้วยการแสดง Canvas โดยเติมสีพื้นหลังที่คุณตั้งค่าไว้ใน onSizeChanged() ให้เต็มหน้าจอ
- ลบล้าง
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() และใช้เพื่อวาดบนบิตแมป
- เรียกใช้แอป คุณควรเห็นทั้งหน้าจอเป็นสีพื้นหลังที่ระบุ

หากต้องการวาด คุณต้องมีออบเจ็กต์ Paint ที่ระบุวิธีจัดรูปแบบเมื่อวาด และออบเจ็กต์ Path ที่ระบุสิ่งที่กำลังวาด
ขั้นตอนที่ 1 เริ่มต้นออบเจ็กต์ Paint
- ใน MyCanvasView.kt ที่ระดับไฟล์ด้านบน ให้กำหนดค่าคงที่สำหรับความกว้างของเส้น
private const val STROKE_WIDTH = 12f // has to be float- ที่ระดับคลาสของ
MyCanvasViewให้กำหนดตัวแปรdrawColorสำหรับเก็บสีที่จะวาด และเริ่มต้นด้วยทรัพยากรcolorPaintที่คุณกำหนดไว้ก่อนหน้านี้
private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)- ที่ระดับชั้นเรียน ให้เพิ่มตัวแปร
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จะระบุวิธีที่เส้นและส่วนโค้งเชื่อมต่อกันในเส้นทางที่ขีด ค่าเริ่มต้นคือMITERstrokeCapกำหนดรูปร่างของปลายเส้นให้เป็นหมวกPaint.Capระบุวิธีเริ่มต้นและสิ้นสุดเส้นและเส้นทางที่ขีด ค่าเริ่มต้นคือBUTTstrokeWidthระบุความกว้างของเส้นในหน่วยพิกเซล ค่าเริ่มต้นคือความกว้างของเส้นผม ซึ่งบางมาก จึงตั้งค่าเป็นค่าคงที่STROKE_WIDTHที่คุณกำหนดไว้ก่อนหน้านี้
ขั้นตอนที่ 2 เริ่มต้นออบเจ็กต์เส้นทาง
Path คือเส้นทางของสิ่งที่ผู้ใช้กำลังวาด
- ใน
MyCanvasViewให้เพิ่มตัวแปรpathและเริ่มต้นด้วยออบเจ็กต์Pathเพื่อจัดเก็บเส้นทางที่วาดเมื่อติดตามการแตะของผู้ใช้บนหน้าจอ นำเข้าandroid.graphics.PathสำหรับPath
private var path = Path()ขั้นตอนที่ 1 ตอบสนองต่อการเคลื่อนไหวบนจอแสดงผล
ระบบจะเรียกใช้เมธอด onTouchEvent() ในมุมมองทุกครั้งที่ผู้ใช้แตะจอแสดงผล
- ใน
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
}- ที่ระดับคลาส ให้เพิ่มตัวแปร
motionTouchEventXและmotionTouchEventYที่ขาดหายไปสำหรับการแคชพิกัด x และ y ของเหตุการณ์การแตะปัจจุบัน (พิกัดMotionEvent) เริ่มต้นด้วย0f
private var motionTouchEventX = 0f
private var motionTouchEventY = 0f- สร้าง Stub สำหรับฟังก์ชัน 3 รายการ ได้แก่
touchStart(),touchMove()และtouchUp()
private fun touchStart() {}
private fun touchMove() {}
private fun touchUp() {}- โค้ดควรจะสร้างและเรียกใช้ได้ แต่คุณจะยังไม่เห็นอะไรที่แตกต่างจากพื้นหลังสี
ขั้นตอนที่ 2 ใช้ touchStart()
ระบบจะเรียกใช้เมธอดนี้เมื่อผู้ใช้แตะหน้าจอเป็นครั้งแรก
- ที่ระดับคลาส ให้เพิ่มตัวแปรเพื่อแคชค่า x และ y ล่าสุด หลังจากที่ผู้ใช้หยุดเคลื่อนที่และยกนิ้วที่สัมผัสอยู่ จุดเหล่านี้จะเป็นจุดเริ่มต้นของเส้นทางถัดไป (ส่วนถัดไปของเส้นที่จะวาด)
private var currentX = 0f
private var currentY = 0f- ใช้เมธอด
touchStart()ดังนี้ รีเซ็ตpathย้ายไปยังพิกัด x-y ของเหตุการณ์การแตะ (motionTouchEventXและmotionTouchEventY) แล้วกําหนดcurrentXและcurrentYให้กับค่านั้น
private fun touchStart() {
path.reset()
path.moveTo(motionTouchEventX, motionTouchEventY)
currentX = motionTouchEventX
currentY = motionTouchEventY
}ขั้นตอนที่ 3 ใช้ touchMove()
- ที่ระดับชั้นเรียน ให้เพิ่มตัวแปร
touchToleranceแล้วตั้งค่าเป็นViewConfiguration.get(context).scaledTouchSlop
private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlopการใช้เส้นทางทำให้ไม่จำเป็นต้องวาดทุกพิกเซลและขอรีเฟรชการแสดงผลทุกครั้ง แต่คุณสามารถ (และควร) ประมาณเส้นทางระหว่างจุดต่างๆ เพื่อให้ได้ประสิทธิภาพที่ดียิ่งขึ้น
- หากนิ้วขยับเพียงเล็กน้อย ก็ไม่จำเป็นต้องวาด
- หากนิ้วเคลื่อนที่น้อยกว่าระยะทาง
touchToleranceอย่าลาก scaledTouchSlopจะแสดงระยะทางเป็นพิกเซลที่การแตะสามารถเลื่อนไปได้ก่อนที่ระบบจะคิดว่าผู้ใช้กำลังเลื่อน
- กำหนด
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()
}รายละเอียดเพิ่มเติมเกี่ยวกับวิธีนี้
- คำนวณระยะทางที่เคลื่อนที่ (
dx, dy) - หากการเคลื่อนไหวอยู่ไกลกว่าความคลาดเคลื่อนในการสัมผัส ให้เพิ่มกลุ่มลงในเส้นทาง
- ตั้งจุดเริ่มต้นของกลุ่มถัดไปเป็นจุดสิ้นสุดของกลุ่มนี้
- การใช้
quadTo()แทนlineTo()จะสร้างเส้นที่วาดได้อย่างราบรื่นโดยไม่มีมุม ดูเส้นโค้งเบซิเยร์ - เรียกใช้
invalidate()เพื่อ (เรียกใช้onDraw()และ) วาดมุมมองใหม่
ขั้นตอนที่ 4: ใช้ touchUp()
เมื่อผู้ใช้ปล่อยนิ้ว สิ่งที่ต้องทำคือรีเซ็ตเส้นทางเพื่อไม่ให้วาดซ้ำ ไม่มีการวาดภาพ จึงไม่จำเป็นต้องลบล้าง
- ใช้เมธอด
touchUp()
private fun touchUp() {
// Reset the path so it doesn't get drawn again.
path.reset()
}- เรียกใช้โค้ดและใช้นิ้ววาดบนหน้าจอ โปรดทราบว่าหากหมุนอุปกรณ์ ระบบจะล้างหน้าจอเนื่องจากไม่ได้บันทึกสถานะการวาด สำหรับแอปตัวอย่างนี้ เราออกแบบมาเช่นนี้เพื่อให้ผู้ใช้มีวิธีง่ายๆ ในการล้างหน้าจอ

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

งาน (ไม่บังคับ): จัดเก็บข้อมูลในเส้นทาง
ในแอปปัจจุบัน ระบบจะจัดเก็บข้อมูลการวาดในบิตแมป แม้ว่านี่จะเป็นโซลูชันที่ดี แต่ก็ไม่ใช่เพียงวิธีเดียวที่เป็นไปได้ในการจัดเก็บข้อมูลการวาด วิธีจัดเก็บประวัติการวาดขึ้นอยู่กับแอปและความต้องการต่างๆ ของคุณ เช่น หากคุณวาดรูปร่าง คุณสามารถบันทึกรายการรูปร่างพร้อมตำแหน่งและขนาดได้ สำหรับแอป MiniPaint คุณสามารถบันทึกเส้นทางเป็น Path ได้ หากต้องการลองใช้ฟีเจอร์นี้ ให้ดูโครงร่างทั่วไปเกี่ยวกับวิธีดำเนินการได้ที่ด้านล่าง
- ใน
MyCanvasViewให้นำโค้ดทั้งหมดสำหรับextraCanvasและextraBitmapออก - เพิ่มตัวแปรสำหรับเส้นทางที่ผ่านมาและเส้นทางที่กำลังวาด
// Path representing the drawing so far
private val drawing = Path()
// Path representing what's currently being drawn
private val curPath = Path()- ใน
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)- ใน
touchUp()ให้เพิ่มเส้นทางปัจจุบันไปยังเส้นทางก่อนหน้า แล้วรีเซ็ตเส้นทางปัจจุบัน
// Add the current path to the drawing so far
drawing.addPath(curPath)
// Rewind the current path for the next touch
curPath.reset()- เรียกใช้แอปของคุณ และแน่นอนว่าไม่ควรมีความแตกต่างใดๆ
ดาวน์โหลดโค้ดสำหรับ Codelab ที่เสร็จสมบูรณ์
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-canvas
หรือคุณจะดาวน์โหลดที่เก็บเป็นไฟล์ Zip, แตกไฟล์ และเปิดใน Android Studio ก็ได้
Canvasคือพื้นผิวสำหรับวาดภาพ 2 มิติที่มีวิธีการวาดCanvasสามารถเชื่อมโยงกับอินสแตนซ์Viewที่แสดงได้- ออบเจ็กต์
Paintจะเก็บข้อมูลรูปแบบและสีเกี่ยวกับวิธีวาดเรขาคณิต (เช่น เส้น สี่เหลี่ยมผืนผ้า วงรี และเส้นทาง) และข้อความ - รูปแบบทั่วไปในการทำงานกับ Canvas คือการสร้างมุมมองที่กำหนดเองและลบล้างเมธอด
onDraw()และonSizeChanged() - แทนที่เมธอด
onTouchEvent()เพื่อบันทึกการแตะของผู้ใช้และตอบสนองต่อการแตะเหล่านั้นด้วยการวาดสิ่งต่างๆ - คุณใช้บิตแมปเพิ่มเติมเพื่อแคชข้อมูลสำหรับการวาดภาพที่มีการเปลี่ยนแปลงเมื่อเวลาผ่านไปได้ หรือจะจัดเก็บรูปร่างหรือเส้นทางก็ได้
หลักสูตร Udacity:
เอกสารประกอบสำหรับนักพัฒนาแอป Android
Canvasชั้นเรียนBitmapชั้นเรียนViewชั้นเรียนPaintชั้นเรียน- การกำหนดค่า
Bitmap.config Pathชั้นเรียน- หน้า Wikipedia ของเส้นโค้งเบซิเยร์
- Canvas และ Drawables
- ชุดบทความสถาปัตยกรรมกราฟิก (ขั้นสูง)
- drawables
- onDraw()
- onSizeChanged()
MotionEventViewConfiguration.get(context).scaledTouchSlop
ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้
- มอบหมายการบ้านหากจำเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
- ให้คะแนนงานการบ้าน
ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม
หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ
ตอบคำถามต่อไปนี้
คำถามที่ 1
คุณต้องมีคอมโพเนนต์ใดต่อไปนี้ในการทำงานกับ Canvas เลือกได้มากกว่า 1 ข้อ
▢ Bitmap
▢ Paint
▢ Path
▢ View
คำถามที่ 2
การเรียกใช้ invalidate() จะทำอะไร (โดยทั่วไป)
▢ ทำให้แอปของคุณไม่ถูกต้องและรีสตาร์ท
▢ ลบภาพวาดออกจากบิตแมป
▢ ระบุว่าไม่ควรเรียกใช้โค้ดก่อนหน้า
▢ บอกระบบว่าต้องวาดหน้าจอใหม่
คำถามที่ 3
ออบเจ็กต์ Canvas, Bitmap และ Paint มีฟังก์ชันอะไรบ้าง
▢ พื้นผิวการวาดภาพ 2 มิติ บิตแมปที่แสดงบนหน้าจอ ข้อมูลการจัดรูปแบบสำหรับการวาดภาพ
▢ พื้นผิวการวาดภาพ 3 มิติ บิตแมปสำหรับการแคชเส้นทาง ข้อมูลการจัดรูปแบบสำหรับการวาด
▢ พื้นผิวการวาดภาพ 2 มิติ บิตแมปที่แสดงบนหน้าจอ การจัดรูปแบบสำหรับมุมมอง
▢ แคชสำหรับข้อมูลการวาด บิตแมปที่จะวาด สไตล์สำหรับวาด
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin