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 นี้และแอปที่วาดบนผืนผ้าใบเพื่อแสดงในมุมมองเต็มหน้าจอ คุณจะมองภาพในแบบต่อไปนี้ได้
- คุณต้องมีมุมมองเพื่อแสดงสิ่งที่กําลังวาด ซึ่งอาจเป็นมุมมองจากระบบ Android หรือใน Codelab นี้ คุณสร้างมุมมองที่กําหนดเองเพื่อใช้เป็นมุมมองเนื้อหาสําหรับแอป (
MyCanvasView
) - มุมมองนี้มาพร้อมกับมุมมองของตัวเอง (
canvas
) เนื่องจากมุมมองนี้ทั้งหมด - สําหรับวิธีพื้นฐานในการวาดภาพบนผืนผ้าใบของมุมมอง คุณสามารถลบล้างวิธีการ
onDraw()
และวาดบนผืนผ้าใบของมุมมองได้ - เมื่อสร้างภาพวาด คุณต้องแคชสิ่งที่วาดไว้ก่อนหน้า การแคชข้อมูลมีอยู่หลายวิธี โดยวิธีหนึ่งจะอยู่ในบิตแมป (
extraBitmap
) อีกวิธีหนึ่งคือการบันทึกประวัติของสิ่งที่คุณวาดเป็นพิกัดและคําแนะนํา - หากต้องการวาดบิตแมปการแคช (
extraBitmap
) โดยใช้ API การวาด Canvas คุณจะต้องสร้าง Canvas การแคช (extraCanvas
) สําหรับบิตแมปการแคช - จากนั้นคุณจะต้องวาดบนผืนผ้าใบสําหรับแคช (
extraCanvas
) ซึ่งจะวาดลงในบิตแมปการแคช (extraBitmap
) - หากต้องการแสดงทุกอย่างที่วาดบนหน้าจอ คุณจะต้องบอกให้ Canvas สําหรับดู (
canvas
) วาดบิตแมปการแคช (extraBitmap
)
สิ่งที่ควรทราบอยู่แล้ว
- วิธีสร้างแอปที่มีกิจกรรม เลย์เอาต์พื้นฐาน และเรียกใช้แอปโดยใช้ Android Studio
- วิธีเชื่อมโยงเครื่องจัดการเหตุการณ์กับข้อมูลพร็อพเพอร์ตี้
- วิธีสร้างมุมมองที่กําหนดเอง
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้าง
Canvas
และวาดรูปให้สอดคล้องกับการโต้ตอบของผู้ใช้
สิ่งที่คุณจะทํา
- สร้างเส้นที่วาดบนหน้าจอบนหน้าจอเพื่อตอบสนองต่อผู้ใช้ที่แตะหน้าจอ
- จับภาพเหตุการณ์การเคลื่อนไหว และในการตอบกลับ ให้วาดเส้นบนผืนผ้าใบที่แสดงในมุมมองที่กําหนดเองแบบเต็มหน้าจอบนหน้าจอ
แอป MiniPaint จะใช้มุมมองที่กําหนดเองเพื่อแสดงเส้นที่ตอบสนองต่อการแตะของผู้ใช้ ดังที่แสดงในภาพหน้าจอด้านล่าง
ขั้นตอนที่ 1 สร้างโปรเจ็กต์ MiniPaint
- สร้างโปรเจ็กต์ Kotlin ใหม่ชื่อ MiniPaint ที่ใช้เทมเพลต Empty Activity
- เปิดไฟล์
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
ให้สร้าง New > Kotlin File/Class ชื่อ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)
- เรียกใช้แอป คุณจะเห็นหน้าจอสีขาวล้วนเนื่องจากผืนผ้าใบยังไม่มีขนาดและคุณยังไม่ได้วาดภาพใดๆ
ขั้นตอนที่ 1 ลบล้าง onSizeChanged()
ระบบ Android จะเรียกเมธอด onSizeChanged()
ทุกครั้งที่มุมมองเปลี่ยนขนาด เนื่องจากข้อมูลพร็อพเพอร์ตี้เริ่มต้นโดยไม่มีขนาด ระบบจึงเรียกใช้เมธอด onSizeChanged()
ของมุมมองหลังจากที่กิจกรรมสร้างที่สูงเกินจริง ดังนั้นวิธี onSizeChanged()
นี้จึงเป็นวิธีที่ดีที่สุดในการสร้างและตั้งค่าผืนผ้าใบสําหรับดู
- ใน
MyCanvasView
ในระดับตัวแปร ให้กําหนดตัวแปรสําหรับ Canvas และบิตแมป โทรหาextraCanvas
และextraBitmap
นี่คือบิตแมปและภาพพิมพ์แคนวาสสําหรับแคชสิ่งที่วาดไว้ก่อนหน้านี้
private lateinit var extraCanvas: Canvas
private lateinit var extraBitmap: Bitmap
- กําหนดตัวแปรระดับชั้นเรียน
backgroundColor
สําหรับสีพื้นหลังของผืนผ้าใบและเริ่มต้นตัวแปรเป็น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()
เริ่มจากแสดงภาพพิมพ์แคนวาส แล้วใส่สีพื้นหลังให้กับพื้นหลังที่คุณตั้งค่าไว้ใน onSizeChanged()
- ลบล้าง
onDraw()
และวาดเนื้อหาของextraBitmap
ที่แคชไว้บนผืนผ้าใบที่เชื่อมโยงกับมุมมอง เมธอดdrawBitmap()
Canvas
มีหลายเวอร์ชัน ในโค้ดนี้ คุณต้องระบุบิตแมป, พิกัด x และ y (เป็นพิกเซล) ที่มุมซ้ายบนและnull
สําหรับPaint
เนื่องจากคุณจะตั้งค่าในภายหลัง
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawBitmap(extraBitmap, 0f, 0f, null)
}
โปรดสังเกตว่าผืนผ้าใบที่ระบบส่งมายัง onDraw()
และใช้โดยระบบเพื่อแสดงบิตแมปนั้นแตกต่างจากภาพที่สร้างด้วยเมธอด 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
ระบุว่ามีการเติมค่าดั้งเดิมที่วาด วาดเส้น หรือทั้งสองอย่าง (สีเดียวกัน) ค่าเริ่มต้นคือเติมวัตถุที่ใช้สี ("Fi&&tt; เปลี่ยนสีด้านในของรูปร่างในขณะที่"str" ทําตามโครงร่าง)strokeJoin
จากPaint.Join
ระบุวิธีที่เส้นและส่วนโค้งรวมอยู่ในเส้นทางที่มีเส้น ค่าเริ่มต้นคือMITER
strokeCap
กําหนดรูปร่างของจุดสิ้นสุดของเส้นให้เป็นความถี่สูงสุดPaint.Cap
ระบุจุดเริ่มต้นและจุดสิ้นสุดของเส้นและเส้นทางที่เป็นเส้น ค่าเริ่มต้นคือBUTT
strokeWidth
ระบุความกว้างของเส้นเป็นพิกเซล ค่าเริ่มต้นคือความกว้างของเส้นผมซึ่งบางมาก จึงตั้งเป็นค่าคงที่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
- สร้างตัวยึดตําแหน่งสําหรับฟังก์ชัน 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()
จะสร้างเส้นที่วาดได้ลื่นไหลโดยไม่มีมุม ดู Bezier Curves - เรียก
invalidate()
ไปยัง (เรียกในที่สุดว่าonDraw()
และ) ดึงมุมมองใหม่
ขั้นตอนที่ 4: ใช้ touchUp()
เมื่อผู้ใช้ยกมือขึ้น สิ่งที่คุณต้องทําก็คือรีเซ็ตเส้นทางเพื่อไม่ให้วาดอีก ไม่มีภาพวาด เราจึงไม่จําเป็นต้องแก้ไข
- ใช้เมธอด
touchUp()
private fun touchUp() {
// Reset the path so it doesn't get drawn again.
path.reset()
}
- เรียกใช้รหัสและใช้นิ้ววาดบนหน้าจอ โปรดทราบว่าหากคุณหมุนอุปกรณ์ ระบบจะล้างหน้าจอออกเพราะไม่ได้บันทึกสถานะภาพวาดไว้ สําหรับแอปตัวอย่างนี้ เราออกแบบขึ้นมาเพื่อให้ผู้ใช้มีวิธีล้างข้อมูลบนหน้าจอได้โดยง่าย
ขั้นตอนที่ 5: วาดเฟรมรอบๆ ภาพสเก็ตช์
เมื่อผู้ใช้วาดบนหน้าจอ แอปของคุณจะสร้างเส้นทางและบันทึกไว้ในบิตแมป extraBitmap
เมธอด onDraw()
จะแสดงบิตแมปเพิ่มเติมในผืนผ้าใบของมุมมอง คุณวาดได้มากขึ้นใน onDraw()
ตัวอย่างเช่น คุณสามารถวาดรูปร่างได้หลังจากที่วาดบิตแมปแล้ว
ในขั้นตอนนี้ คุณจะวาดเฟรมรอบขอบของรูปภาพ
- ใน
MyCanvasView
ให้เพิ่มตัวแปรชื่อframe
ที่มีออบเจ็กต์Rect
private lateinit var frame: Rect
- ในตอนท้ายของ
onSizeChanged()
ให้กําหนดส่วนที่แทรก แล้วเพิ่มโค้ดเพื่อสร้าง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
มีข้อมูลสไตล์และสีเกี่ยวกับวิธีวาดรูปทรงเรขาคณิต (เช่น เส้น สี่เหลี่ยมผืนผ้า วงรี และเส้นทาง) และข้อความ - รูปแบบที่พบบ่อยสําหรับการทํางานกับภาพพิมพ์แคนวาสคือการสร้างมุมมองที่กําหนดเองและลบล้างเมธอด
onDraw()
และonSizeChanged()
- ลบล้างเมธอด
onTouchEvent()
เพื่อดึงดูดและตอบสนองความต้องการของผู้ใช้ด้วยการวาดสิ่งต่างๆ - คุณสามารถใช้บิตแมปเพิ่มเติมเพื่อแคชข้อมูลสําหรับภาพวาดที่มีการเปลี่ยนแปลงเมื่อเวลาผ่านไปได้ หรือคุณจะจัดเก็บรูปร่างหรือเส้นทางก็ได้
หลักสูตร Udacity:
เอกสารประกอบสําหรับนักพัฒนาซอฟต์แวร์ Android
- ชั้นเรียน
Canvas
- ชั้นเรียน
Bitmap
- ชั้นเรียน
View
- ชั้นเรียน
Paint
- การกําหนดค่า
Bitmap.config
- ชั้นเรียน
Path
- เส้นโค้ง Bezier หน้า Wikipedia
- ผืนผ้าใบและภาพวาด
- ชุดบทความ Graphics Architecture (ขั้นสูง)
- ภาพวาด
- onDraw()
- onSizeChanged()
MotionEvent
ViewConfiguration.get(context).scaledTouchSlop
ส่วนนี้จะอธิบายการบ้านและรายงานสําหรับนักเรียนที่ทํางานผ่าน Codelab นี้ซึ่งเป็นส่วนหนึ่งของหลักสูตรที่นําโดยผู้สอน สิ่งที่ผู้สอนต้องทํามีดังนี้
- มอบหมายการบ้านหากจําเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานทําการบ้าน
- ตัดเกรดการบ้าน
ผู้สอนจะใช้คําแนะนําเหล่านี้เท่าใดก็ได้หรือตามที่ต้องการก็ได้ และสามารถกําหนดให้การบ้านอื่นๆ ที่ตนคิดว่าเหมาะสมได้
หากคุณใช้ Codelab ด้วยตัวเอง ก็ให้ใช้การบ้านเพื่อทดสอบความรู้ของคุณได้
ตอบคําถามเหล่านี้
คำถามที่ 1
องค์ประกอบใดต่อไปนี้ที่จําเป็นสําหรับการทํางานกับ Canvas
เลือกได้มากกว่า 1 ข้อ
▢ Bitmap
▢ Paint
▢ Path
▢ View
คำถามที่ 2
การโทรหา invalidate()
ควรทําอย่างไร (โดยทั่วไป)
▢ ระบุว่าไม่ถูกต้องและรีสตาร์ทแอป
▢ ลบภาพวาดออกจากบิตแมป
▢ บ่งบอกว่าไม่ควรเรียกใช้โค้ดก่อนหน้า
▢ บอกระบบว่าต้องวาดหน้าจอใหม่
คำถามที่ 3
ฟังก์ชันของออบเจ็กต์ Canvas
, Bitmap
และ Paint
คืออะไร
▢ พื้นผิวภาพวาด 2 มิติ, บิตแมปที่แสดงบนหน้าจอ, ข้อมูลการจัดรูปแบบสําหรับภาพวาด
▢ พื้นผิวภาพวาด 3 มิติ, บิตแมปสําหรับแคชเส้นทาง, ข้อมูลการจัดรูปแบบสําหรับภาพวาด
▢ พื้นผิวภาพวาด 2 มิติ, บิตแมปที่แสดงบนหน้าจอ, การจัดรูปแบบสําหรับมุมมอง
▢ แคชสําหรับข้อมูลการวาด บิตแมปสําหรับวาด การจัดรูปแบบข้อมูลสําหรับการวาด
สําหรับลิงก์ไปยังหน้า Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page ขั้นสูงสําหรับ Android ใน Kotlin