กราฟิกในการเขียน

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

ภาพวาดพื้นฐานพร้อมตัวปรับแต่งและ DrawScope

วิธีหลักในการวาดสิ่งที่กำหนดเองในเครื่องมือเขียนคือการใช้ตัวแก้ไข เช่น Modifier.drawWithContent, Modifier.drawBehind และ Modifier.drawWithCache

เช่น หากต้องการวาดสิ่งที่อยู่หลังคอมโพสิเบิล ให้ใช้ตัวแก้ไข drawBehind เพื่อเริ่มดำเนินการตามคำสั่งวาดภาพ

Spacer(
    modifier = Modifier
        .fillMaxSize()
        .drawBehind {
            // this = DrawScope
        }
)

หากต้องการแค่ Composable ที่สามารถวาดได้ คุณสามารถใช้ Canvas Composable คอมโพสิเบิล Canvas เป็นไวลด์การ์ดที่สะดวกสำหรับ 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
    )
}

สี่เหลี่ยมผืนผ้าสีชมพูวาดบนพื้นหลังสีขาวกินพื้นที่ 1 ใน 4 ของหน้าจอ
รูปที่ 1 สี่เหลี่ยมผืนผ้าที่วาดโดยใช้ Canvas ใน Compose

ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวปรับแต่งภาพวาดต่างๆ ได้ที่ตัวปรับแต่งกราฟิก เอกสารประกอบ

ระบบพิกัด

หากต้องการวาดสิ่งต่างๆ บนหน้าจอ คุณจะต้องทราบออฟเซต (x และ y) และขนาดของรายการ เมื่อใช้วิธีการวาดหลายวิธีใน DrawScope ตำแหน่งและขนาด ได้มาจากค่าพารามิเตอร์เริ่มต้น พารามิเตอร์เริ่มต้นโดยทั่วไป จัดวางสินค้าที่จุด [0, 0] บนผืนผ้าใบ และระบุค่าเริ่มต้น size ซึ่งแสดงเต็มพื้นที่ภาพวาด ดังตัวอย่างด้านบน คุณจะเห็น ให้สี่เหลี่ยมผืนผ้าอยู่ในตำแหน่งด้านบนซ้าย หากต้องการปรับขนาดและตำแหน่งของ รายการของคุณ คุณจำเป็นต้องเข้าใจระบบพิกัดในการเขียน

จุดเริ่มต้นของระบบพิกัด ([0,0]) อยู่ที่พิกเซลซ้ายสุดใน พื้นที่วาดรูป x เพิ่มขึ้นเมื่อเคลื่อนที่ไปทางขวา และ y เพิ่มขึ้นเมื่อเคลื่อนที่ ลง

ตารางกริดแสดงระบบพิกัดซึ่งแสดงด้านซ้ายบน [0, 0] และมุมขวาล่าง [ความกว้าง, ความสูง]
รูปที่ 2 ระบบพิกัดสำหรับการวาด / ตารางกริดสำหรับการวาดภาพ

เช่น หากต้องการวาดเส้นทแยงมุมจากมุมขวาบนของพื้นที่แคนวาสไปยังมุมซ้ายล่าง คุณสามารถใช้ฟังก์ชัน 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() เพื่อเพิ่มขนาดของการดำเนินการวาดภาพขึ้นอีก 1 ปัจจัย การดำเนินการ เช่น scale() จะใช้กับการดำเนินการวาดทั้งหมดภายใน lambda ที่เกี่ยวข้อง ตัวอย่างเช่น โค้ดต่อไปนี้จะเพิ่ม scaleX 10 ครั้งและ scaleY 15 ครั้ง

Canvas(modifier = Modifier.fillMaxSize()) {
    scale(scaleX = 10f, scaleY = 15f) {
        drawCircle(Color.Blue, radius = 20.dp.toPx())
    }
}

วงกลมมีอัตราส่วนไม่เท่ากัน
รูปที่ 3 กำลังใช้การดำเนินการปรับขนาดกับวงกลมบน Canvas

แปลภาษา

ใช้ DrawScope.translate() เพื่อย้ายการดำเนินการวาดภาพขึ้น ลง ซ้าย หรือขวา ตัวอย่างเช่น พารามิเตอร์ โค้ดต่อไปนี้ย้ายภาพวาดไปทางขวา 100 พิกเซลและขึ้น 300 พิกเซล

Canvas(modifier = Modifier.fillMaxSize()) {
    translate(left = 100f, top = -300f) {
        drawCircle(Color.Blue, radius = 200.dp.toPx())
    }
}

วงกลมที่เลื่อนออกจากกึ่งกลาง
รูปที่ 4 การใช้การดำเนินการแปลกับวงกลมใน Canvas

หมุน

ใช้ DrawScope.rotate() เพื่อหมุนการดำเนินการวาดภาพรอบจุด Pivot ตัวอย่างเช่น พารามิเตอร์ โค้ดต่อไปนี้หมุนสี่เหลี่ยมผืนผ้า 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
        )
    }
}

โทรศัพท์ที่มีสี่เหลี่ยมผืนผ้าหมุน 45 องศาอยู่ตรงกลางหน้าจอ
รูปที่ 5 เราใช้ rotate() เพื่อใช้การหมุนกับขอบเขตการวาดปัจจุบัน ซึ่งจะหมุนสี่เหลี่ยมผืนผ้า 45 องศา

ข้างใน

ใช้ DrawScope.inset() เพื่อปรับพารามิเตอร์เริ่มต้นของปัจจุบัน DrawScope กำลังเปลี่ยนขอบเขตของการวาดและแปลภาพวาด ดังนี้

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    inset(horizontal = 50f, vertical = 30f) {
        drawRect(color = Color.Green, size = canvasQuadrantSize)
    }
}

โค้ดนี้จะเพิ่มระยะห่างจากขอบในคำสั่งการวาดอย่างมีประสิทธิภาพ:

สี่เหลี่ยมผืนผ้าที่มีแผ่นกั้นปิดทั้งหมด
รูปที่ 6 การใช้ส่วนที่แทรกกับคำสั่งการวาด

การเปลี่ยนรูปแบบหลายรายการ

หากต้องการใช้การเปลี่ยนรูปแบบหลายรายการกับภาพ ให้ใช้ฟังก์ชัน 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
        )
    }
}

โทรศัพท์ที่มีสี่เหลี่ยมผืนผ้าที่บิดเบี้ยวไปด้านข้างของหน้าจอ
รูปที่ 7 ใช้ withTransform เพื่อใช้ทั้งการหมุนและการแปล โดยหมุนสี่เหลี่ยมผืนผ้าและเลื่อนไปทางซ้าย

การดำเนินการวาดทั่วไป

วาดข้อความ

หากต้องการวาดข้อความในเครื่องมือเขียน โดยทั่วไปคุณจะใช้คอมโพสิเบิล Text ได้ อย่างไรก็ตาม หากคุณอยู่ใน DrawScope หรือคุณต้องการเขียนข้อความด้วยตนเองด้วย ที่กำหนดเอง คุณสามารถใช้ DrawScope.drawText()

หากต้องการวาดข้อความ ให้สร้าง TextMeasurer โดยใช้ rememberTextMeasurer และเรียกใช้ drawText โดยใช้เครื่องมือวัดดังนี้

val textMeasurer = rememberTextMeasurer()

Canvas(modifier = Modifier.fillMaxSize()) {
    drawText(textMeasurer, "Hello")
}

กำลังแสดงรูป Hello ที่วาดบน Canvas
รูปที่ 8 การวาดข้อความบน Canvas

วัดข้อความ

การวาดข้อความจะทำงานแตกต่างจากคำสั่งวาดอื่นๆ เล็กน้อย โดยปกติแล้ว คุณจะต้องกำหนดขนาด (ความกว้างและความสูง) ให้กับคำสั่งวาดภาพเพื่อวาดรูปทรง/รูปภาพ สำหรับข้อความ จะมีพารามิเตอร์ 2-3 รายการที่ควบคุมขนาดของข้อความที่แสดงผล เช่น ขนาดแบบอักษร แบบอักษร ตัวห้อย และระยะห่างระหว่างตัวอักษร

เมื่อใช้ฟีเจอร์เขียน คุณจะใช้ 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()
)

ข้อมูลโค้ดนี้จะสร้างพื้นหลังสีชมพูบนข้อความ:

ข้อความหลายบรรทัดขนาด ⅔ ของพื้นที่ทั้งหมด โดยมีสี่เหลี่ยมผืนผ้าพื้นหลัง
รูปที่ 9 ข้อความหลายบรรทัดขนาด ⅔ ของพื้นที่ทั้งหมด พร้อมสี่เหลี่ยมผืนผ้าพื้นหลัง

การปรับข้อจำกัด ขนาดแบบอักษร หรือพร็อพเพอร์ตี้ที่มีผลต่อขนาดที่วัดได้ ทำให้มีการรายงานขนาดใหม่ คุณกำหนดขนาดคงที่ได้สำหรับทั้ง 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()
)

ตอนนี้ข้อความจะวาดในข้อจำกัดโดยมีเครื่องหมายจุดไข่ปลาที่ท้ายข้อความ

ข้อความวาดบนพื้นหลังสีชมพูโดยมีจุดไข่ปลาตัดข้อความ
รูปที่ 10 TextOverflow.Ellipsis ที่มีข้อจํากัดแบบคงที่ในการวัดข้อความ

วาดรูปภาพ

หากต้องการวาด ImageBitmap ด้วย DrawScope ให้โหลดรูปภาพโดยใช้ ImageBitmap.imageResource() แล้วเรียกใช้ drawImage ดังนี้

val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)

Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
    drawImage(dogImage)
})

รูปภาพสุนัขที่วาดบนผ้าใบ
รูปที่ 11 กำลังวาดImageBitmapบน Canvas

วาดรูปทรงพื้นฐาน

DrawScope มีฟังก์ชันวาดรูปทรงมากมาย หากต้องการวาดรูปร่าง ให้ใช้รูปร่าง ของฟังก์ชันการวาดที่กำหนดไว้ล่วงหน้า เช่น drawCircle

val purpleColor = Color(0xFFBA68C8)
Canvas(
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp),
    onDraw = {
        drawCircle(purpleColor)
    }
)

API

เอาต์พุต

drawCircle()

วาดวงกลม

drawRect()

วาดรูปสี่เหลี่ยม

drawRoundedRect()

วาดสี่เหลี่ยมผืนผ้ามุมมน

drawLine()

วาดเส้น

drawOval()

วาดวงรี

drawArc()

วาดส่วนโค้ง

drawPoints()

จุดยึด

วาดเส้นทาง

เส้นทางคือชุดคำสั่งทางคณิตศาสตร์ที่จะทำให้มีการวาดภาพเพียงครั้งเดียว ดำเนินการแล้ว 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()
)

สามเหลี่ยมเส้นทางสีม่วงกลับหัวที่วาดบน Compose
รูปที่ 12 การสร้างและวาด Path ใน "เขียน"

เข้าถึงออบเจ็กต์ 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()
)

ShapeDrawable สีดำรูปวงรีที่กินพื้นที่ขนาดเต็ม
รูปที่ 13 การเข้าถึง Canvas เพื่อวาด Drawable

ดูข้อมูลเพิ่มเติม

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการเขียนในการเขียน โปรดดูข้อมูลต่อไปนี้ แหล่งข้อมูล: