แอปจำนวนมากจำเป็นต้องควบคุมสิ่งที่วาดบนหน้าจอได้อย่างแม่นยำ ซึ่งอาจเป็นการวางกรอบหรือวงกลมบนหน้าจอในตำแหน่งที่เหมาะสม หรืออาจเป็นการจัดเรียงองค์ประกอบกราฟิกอย่างละเอียดในหลายสไตล์
การวาดภาพพื้นฐานด้วยตัวแก้ไขและ DrawScope
วิธีหลักในการวาดอะไรบางอย่างที่กำหนดเองใน Compose คือการใช้ตัวแก้ไข เช่น
Modifier.drawWithContent
Modifier.drawBehind และ
Modifier.drawWithCache
เช่น หากต้องการวาดอะไรบางอย่างไว้ด้านหลัง Composable คุณสามารถใช้ตัวแก้ไข drawBehind เพื่อเริ่มเรียกใช้คำสั่งการวาดได้
Spacer( modifier = Modifier .fillMaxSize() .drawBehind { // this = DrawScope } )
หากต้องการเพียงแค่ Composable ที่วาด คุณสามารถใช้ Composable
Canvas ได้ องค์ประกอบ Canvas เป็น
Wrapper ที่สะดวกสำหรับ Modifier.drawBehind คุณวาง Canvas ในเลย์เอาต์ได้เช่นเดียวกับที่คุณทำกับองค์ประกอบ UI อื่นๆ ของ Compose ภายใน
Canvas คุณสามารถวาดองค์ประกอบพร้อมควบคุมสไตล์และ
ตำแหน่งได้อย่างแม่นยำ
ตัวแก้ไขการวาดทั้งหมดจะแสดง DrawScope ซึ่งเป็นสภาพแวดล้อมการวาดที่กำหนดขอบเขต
ซึ่งรักษาสถานะของตัวเอง ซึ่งช่วยให้คุณตั้งค่าพารามิเตอร์สำหรับกลุ่ม
องค์ประกอบกราฟิกได้ DrawScope มีฟิลด์ที่มีประโยชน์หลายอย่าง เช่น size
ออบเจ็กต์ Size ที่ระบุขนาดปัจจุบันของ DrawScope
หากต้องการวาดอะไรบางอย่าง คุณสามารถใช้ฟังก์ชันการวาดมากมายบน DrawScope ตัวอย่างเช่น โค้ดต่อไปนี้จะวาดสี่เหลี่ยมผืนผ้าที่มุมซ้ายบนของหน้าจอ
Canvas(modifier = Modifier.fillMaxSize()) { val canvasQuadrantSize = size / 2F drawRect( color = Color.Magenta, size = canvasQuadrantSize ) }
ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวแก้ไขการวาดภาพต่างๆ ได้ในเอกสารประกอบตัวแก้ไขกราฟิก
ระบบพิกัด
หากต้องการวาดสิ่งใดสิ่งหนึ่งบนหน้าจอ คุณต้องทราบออฟเซ็ต (x และ y) รวมถึงขนาดของ
รายการ เมื่อใช้เมธอดการวาดหลายๆ เมธอดใน DrawScope ระบบจะระบุตําแหน่งและขนาด
โดยใช้ค่าพารามิเตอร์เริ่มต้น โดยทั่วไป พารามิเตอร์เริ่มต้นจะ
วางตำแหน่งรายการที่จุด [0, 0] บน Canvas และระบุค่าเริ่มต้น
size ที่เติมพื้นที่วาดภาพทั้งหมด ดังตัวอย่างด้านบน คุณจะเห็นว่า
สี่เหลี่ยมผืนผ้าจะวางอยู่ที่ด้านซ้ายบน หากต้องการปรับขนาดและตำแหน่งของ
ไอเทม คุณต้องทำความเข้าใจระบบพิกัดใน Compose
จุดเริ่มต้นของระบบพิกัด ([0,0]) อยู่ที่พิกเซลซ้ายสุดบนสุดใน
พื้นที่วาด x จะเพิ่มขึ้นเมื่อเลื่อนไปทางขวา และ y จะเพิ่มขึ้นเมื่อเลื่อนลง
เช่น หากต้องการวาดเส้นทแยงมุมจากมุมขวาบนของ
พื้นที่ Canvas ไปยังมุมซ้ายล่าง คุณสามารถใช้ฟังก์ชัน
DrawScope.drawLine() และระบุออฟเซ็ตเริ่มต้นและสิ้นสุดด้วย
ตำแหน่ง x และ y ที่สอดคล้องกันได้ดังนี้
Canvas(modifier = Modifier.fillMaxSize()) { val canvasWidth = size.width val canvasHeight = size.height drawLine( start = Offset(x = canvasWidth, y = 0f), end = Offset(x = 0f, y = canvasHeight), color = Color.Blue ) }
การเปลี่ยนรูปแบบพื้นฐาน
DrawScope มีการแปลงเพื่อเปลี่ยนตำแหน่งหรือวิธีเรียกใช้คำสั่งวาด
สเกล
ใช้
DrawScope.scale()
เพื่อเพิ่มขนาดของการดำเนินการวาดภาพตามปัจจัย การดำเนินการ เช่น
scale() มีผลกับการดำเนินการวาดทั้งหมดภายใน Lambda ที่เกี่ยวข้อง ตัวอย่างเช่น โค้ดต่อไปนี้จะเพิ่ม scaleX 10 เท่าและ scaleY 15 เท่า
Canvas(modifier = Modifier.fillMaxSize()) { scale(scaleX = 10f, scaleY = 15f) { drawCircle(Color.Blue, radius = 20.dp.toPx()) } }
แปลภาษา
ใช้
DrawScope.translate()
เพื่อย้ายการวาดขึ้น ลง ซ้าย หรือขวา ตัวอย่างเช่น โค้ดต่อไปนี้จะเลื่อนภาพวาดไปทางขวา 100 พิกเซลและขึ้นไป 300 พิกเซล
Canvas(modifier = Modifier.fillMaxSize()) { translate(left = 100f, top = -300f) { drawCircle(Color.Blue, radius = 200.dp.toPx()) } }
หมุน
ใช้
DrawScope.rotate()
เพื่อหมุนการวาดรอบจุดหมุน ตัวอย่างเช่น โค้ดต่อไปนี้จะหมุนสี่เหลี่ยมผืนผ้า 45 องศา
Canvas(modifier = Modifier.fillMaxSize()) { rotate(degrees = 45F) { drawRect( color = Color.Gray, topLeft = Offset(x = size.width / 3F, y = size.height / 3F), size = size / 3F ) } }
rotate() เพื่อใช้การหมุนกับขอบเขตการวาดปัจจุบัน ซึ่งจะหมุนสี่เหลี่ยมผืนผ้า 45 องศา
ส่วนที่เว้นไว้
ใช้ DrawScope.inset() เพื่อปรับพารามิเตอร์เริ่มต้นของDrawScopeปัจจุบัน โดยเปลี่ยนขอบเขตการวาดและแปลภาพวาดตามนั้น
Canvas(modifier = Modifier.fillMaxSize()) { val canvasQuadrantSize = size / 2F inset(horizontal = 50f, vertical = 30f) { drawRect(color = Color.Green, size = canvasQuadrantSize) } }
โค้ดนี้จะเพิ่มระยะขอบให้กับคำสั่งการวาดได้อย่างมีประสิทธิภาพ
การเปลี่ยนรูปแบบหลายรายการ
หากต้องการใช้การเปลี่ยนรูปแบบหลายรายการกับภาพวาด ให้ใช้ฟังก์ชัน
DrawScope.withTransform() ซึ่งจะสร้างและ
ใช้การเปลี่ยนรูปแบบเดียวที่รวมการเปลี่ยนแปลงที่ต้องการทั้งหมด การใช้
withTransform() มีประสิทธิภาพมากกว่าการเรียกการแปลงที่ซ้อนกันแต่ละรายการ
เนื่องจากระบบจะทำการแปลงทั้งหมดพร้อมกันใน
การดำเนินการเดียว แทนที่ Compose จะต้องคำนวณและบันทึกการแปลงที่ซ้อนกันแต่ละรายการ
ตัวอย่างเช่น โค้ดต่อไปนี้จะใช้ทั้งการแปลและการหมุนกับสี่เหลี่ยมผืนผ้า
Canvas(modifier = Modifier.fillMaxSize()) { withTransform({ translate(left = size.width / 5F) rotate(degrees = 45F) }) { drawRect( color = Color.Gray, topLeft = Offset(x = size.width / 3F, y = size.height / 3F), size = size / 3F ) } }
withTransform เพื่อใช้ทั้งการหมุนและการเลื่อน โดยหมุนสี่เหลี่ยมผืนผ้าและเลื่อนไปทางซ้ายการดำเนินการวาดภาพทั่วไป
วาดข้อความ
โดยปกติแล้ว หากต้องการวาดข้อความใน Compose คุณจะใช้ Composable Text ได้ อย่างไรก็ตาม หากคุณอยู่ในDrawScopeหรือต้องการวาดข้อความด้วยตนเองพร้อมการปรับแต่ง คุณสามารถใช้วิธีDrawScope.drawText()
ได้
หากต้องการวาดข้อความ ให้สร้าง TextMeasurer โดยใช้ rememberTextMeasurer
และเรียกใช้ drawText ด้วยเครื่องมือวัด
val textMeasurer = rememberTextMeasurer() Canvas(modifier = Modifier.fillMaxSize()) { drawText(textMeasurer, "Hello") }
วัดข้อความ
การวาดข้อความจะทำงานแตกต่างจากคำสั่งวาดอื่นๆ เล็กน้อย โดยปกติแล้ว คุณ จะให้คำสั่งวาดภาพพร้อมขนาด (ความกว้างและความสูง) เพื่อวาดรูปร่าง/รูปภาพ สำหรับข้อความ มีพารามิเตอร์บางอย่างที่ควบคุมขนาดของข้อความที่แสดงผล เช่น ขนาดแบบอักษร แบบอักษร อักษรควบ และระยะห่างระหว่างตัวอักษร
Compose ช่วยให้คุณใช้ TextMeasurer เพื่อเข้าถึงขนาดข้อความที่วัดได้โดยขึ้นอยู่กับปัจจัยข้างต้น หากต้องการวาดพื้นหลัง
ด้านหลังข้อความ คุณสามารถใช้ข้อมูลที่วัดได้เพื่อดูขนาดของ
พื้นที่ที่ข้อความใช้
val textMeasurer = rememberTextMeasurer() Spacer( modifier = Modifier .drawWithCache { val measuredText = textMeasurer.measure( AnnotatedString(longTextSample), constraints = Constraints.fixedWidth((size.width * 2f / 3f).toInt()), style = TextStyle(fontSize = 18.sp) ) onDrawBehind { drawRect(pinkColor, size = measuredText.size.toSize()) drawText(measuredText) } } .fillMaxSize() )
ข้อมูลโค้ดนี้จะสร้างพื้นหลังสีชมพูในข้อความ
การปรับข้อจำกัด ขนาดแบบอักษร หรือพร็อพเพอร์ตี้ใดๆ ที่ส่งผลต่อขนาดที่วัดได้
จะทำให้มีการรายงานขนาดใหม่ คุณตั้งค่าขนาดคงที่สำหรับทั้ง width
และ height ได้ จากนั้นข้อความจะใช้TextOverflow ที่ตั้งค่าไว้ ตัวอย่างเช่น โค้ดต่อไปนี้จะแสดงข้อความใน ⅓ ของความสูงและ ⅓ ของความกว้าง
ของพื้นที่ที่ประกอบได้ และตั้งค่า TextOverflow เป็น TextOverflow.Ellipsis
val textMeasurer = rememberTextMeasurer() Spacer( modifier = Modifier .drawWithCache { val measuredText = textMeasurer.measure( AnnotatedString(longTextSample), constraints = Constraints.fixed( width = (size.width / 3f).toInt(), height = (size.height / 3f).toInt() ), overflow = TextOverflow.Ellipsis, style = TextStyle(fontSize = 18.sp) ) onDrawBehind { drawRect(pinkColor, size = measuredText.size.toSize()) drawText(measuredText) } } .fillMaxSize() )
ตอนนี้ข้อความจะแสดงในข้อจำกัดโดยมีจุดไข่ปลาที่ท้ายข้อความ
TextOverflow.Ellipsis โดยมีข้อจำกัดคงที่ในการวัดข้อความวาดรูปภาพ
หากต้องการวาด ImageBitmap ด้วย DrawScope ให้โหลดรูปภาพโดยใช้
ImageBitmap.imageResource() แล้วเรียกใช้ drawImage ดังนี้
val dogImage = ImageBitmap.imageResource(id = R.drawable.dog) Canvas(modifier = Modifier.fillMaxSize(), onDraw = { drawImage(dogImage) })
ImageBitmapใน Canvasวาดรูปร่างพื้นฐาน
DrawScope มีฟังก์ชันวาดรูปร่างมากมาย หากต้องการวาดรูปร่าง ให้ใช้ฟังก์ชันวาดที่กำหนดไว้ล่วงหน้า เช่น drawCircle
val purpleColor = Color(0xFFBA68C8) Canvas( modifier = Modifier .fillMaxSize() .padding(16.dp), onDraw = { drawCircle(purpleColor) } )
API |
เอาต์พุต |
|
|
|
|
|
|
|
|
|
|
|
|
|
วาดเส้นทาง
เส้นทางคือชุดคำสั่งทางคณิตศาสตร์ที่ทำให้เกิดภาพวาดเมื่อ
ดำเนินการ DrawScope สามารถวาดเส้นทางโดยใช้วิธี DrawScope.drawPath()
เช่น สมมติว่าคุณต้องการวาดสามเหลี่ยม คุณสร้างเส้นทางด้วยฟังก์ชันต่างๆ เช่น lineTo() และ moveTo() โดยใช้ขนาดของพื้นที่วาดได้
จากนั้นเรียกใช้ drawPath() ด้วยเส้นทางที่สร้างขึ้นใหม่นี้เพื่อรับสามเหลี่ยม
Spacer( modifier = Modifier .drawWithCache { val path = Path() path.moveTo(0f, 0f) path.lineTo(size.width / 2f, size.height / 2f) path.lineTo(size.width, 0f) path.close() onDrawBehind { drawPath(path, Color.Magenta, style = Stroke(width = 10f)) } } .fillMaxSize() )
Pathใน Composeการเข้าถึงออบเจ็กต์ Canvas
DrawScope จะทำให้คุณไม่มีสิทธิ์เข้าถึงออบเจ็กต์ Canvas โดยตรง คุณสามารถใช้
DrawScope.drawIntoCanvas() เพื่อรับ
สิทธิ์เข้าถึงออบเจ็กต์ Canvas เองซึ่งคุณสามารถเรียกใช้ฟังก์ชันได้
เช่น หากคุณมี Drawable ที่กำหนดเองซึ่งต้องการวาดลงใน
Canvas คุณสามารถเข้าถึง Canvas และเรียกใช้ Drawable#draw() โดยส่งออบเจ็กต์
Canvas ดังนี้
val drawable = ShapeDrawable(OvalShape()) Spacer( modifier = Modifier .drawWithContent { drawIntoCanvas { canvas -> drawable.setBounds(0, 0, size.width.toInt(), size.height.toInt()) drawable.draw(canvas.nativeCanvas) } } .fillMaxSize() )
Drawableดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการวาดใน Compose ได้ที่แหล่งข้อมูลต่อไปนี้
- ตัวแก้ไขกราฟิก - ดูข้อมูลเกี่ยวกับตัวแก้ไขการวาดภาพประเภทต่างๆ
- แปรง - ดูวิธีปรับแต่งการวาดเนื้อหา
- เลย์เอาต์และกราฟิกที่กำหนดเองใน Compose - Android Dev Summit 2022 - ดูวิธีสร้าง UI ที่กำหนดเองใน Compose ด้วยเลย์เอาต์ และกราฟิก
- JetLagged Sample - Compose Sample ที่แสดงวิธีวาดกราฟที่กำหนดเอง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- ตัวปรับแต่งกราฟิก
- กราฟิกใน Compose
- เส้นแนวใน Jetpack Compose