ตัวปรับแต่งกราฟิก

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

ตัวควบคุมการวาด

คำสั่งการวาดทั้งหมดจะดำเนินการด้วยตัวแก้ไขการวาดใน "เขียน" ตัวแก้ไขการวาดหลักๆ ในเครื่องมือเขียนมี 3 รายการดังนี้

ตัวแก้ไขพื้นฐานสำหรับการวาดคือ drawWithContent ซึ่งคุณจะกำหนดลำดับการวาดของ Composable และคำสั่งการวาดที่ออกภายในตัวแก้ไขได้ drawBehind เป็น Wrapper ที่สะดวกสำหรับ drawWithContent ซึ่งตั้งค่าลําดับการวาดไว้ด้านหลังเนื้อหาของคอมโพสิเบิล drawWithCache เรียกใช้ onDrawBehind หรือ onDrawWithContent ภายใน และจัดให้มีกลไกแคชออบเจ็กต์ที่สร้างในนั้น

Modifier.drawWithContent: เลือกลำดับการวาด

Modifier.drawWithContent ช่วยให้คุณดำเนินการ DrawScope ก่อนหรือหลังเนื้อหาของคอมโพสิเบิลได้ อย่าลืมเรียกใช้ drawContent เพื่อแสดงผลเนื้อหาจริงของคอมโพสิเบิล ตัวแก้ไขนี้ช่วยให้คุณกำหนดลําดับการดําเนินการได้ หากต้องการให้วาดเนื้อหาก่อนหรือหลังการดําเนินการวาดที่กําหนดเอง

ตัวอย่างเช่น หากต้องการแสดงผลไล่ระดับสีแบบรัศมีบนเนื้อหาเพื่อสร้างเอฟเฟกต์รูกุญแจของไฟฉายใน UI ให้ทําดังนี้

var pointerOffset by remember {
    mutableStateOf(Offset(0f, 0f))
}
Column(
    modifier = Modifier
        .fillMaxSize()
        .pointerInput("dragging") {
            detectDragGestures { change, dragAmount ->
                pointerOffset += dragAmount
            }
        }
        .onSizeChanged {
            pointerOffset = Offset(it.width / 2f, it.height / 2f)
        }
        .drawWithContent {
            drawContent()
            // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI.
            drawRect(
                Brush.radialGradient(
                    listOf(Color.Transparent, Color.Black),
                    center = pointerOffset,
                    radius = 100.dp.toPx(),
                )
            )
        }
) {
    // Your composables here
}

รูปที่ 1: Modifier.drawWithContent ที่ใช้บน Composable เพื่อสร้างประสบการณ์ UI ประเภทไฟฉาย

Modifier.drawBehind: การวาดด้านหลังคอมโพสิเบิล

Modifier.drawBehind ช่วยให้คุณดำเนินการDrawScopeต่างๆ ที่อยู่เบื้องหลังเนื้อหาแบบคอมโพสิเบิลที่วาดบนหน้าจอได้ หากดูการใช้งาน Canvas คุณอาจเห็นว่าเป็นเพียง Wrapper ที่สะดวกสำหรับ Modifier.drawBehind

วิธีวาดสี่เหลี่ยมผืนผ้ามุมมนด้านหลัง Text

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawBehind {
            drawRoundRect(
                Color(0xFFBBAAEE),
                cornerRadius = CornerRadius(10.dp.toPx())
            )
        }
        .padding(4.dp)
)

ซึ่งให้ผลลัพธ์ดังต่อไปนี้

ข้อความและพื้นหลังที่วาดโดยใช้ Modifier.drawBehind
รูปที่ 2: ข้อความและพื้นหลังที่วาดโดยใช้ Modifier.drawBehind

Modifier.drawWithCache: การวาดและการแคชออบเจ็กต์การวาด

Modifier.drawWithCache จะแคชออบเจ็กต์ที่สร้างขึ้นภายในไว้ ระบบจะแคชออบเจ็กต์ตราบใดที่ขนาดของพื้นที่วาดภาพเท่าเดิม หรือออบเจ็กต์สถานะที่อ่านไม่เปลี่ยนแปลง ตัวแก้ไขนี้มีประโยชน์ในการปรับปรุงประสิทธิภาพของการเรียกใช้การวาด เนื่องจากช่วยหลีกเลี่ยงความจำเป็นในการ reallocate วัตถุ (เช่น Brush, Shader, Path ฯลฯ) ที่สร้างขึ้นในการวาด

หรือจะแคชออบเจ็กต์โดยใช้ remember นอกตัวแก้ไขก็ได้ อย่างไรก็ตาม การดำเนินการนี้อาจไม่สามารถทำได้เสมอไปเนื่องจากคุณอาจไม่มีสิทธิ์เข้าถึงการเรียบเรียง การใช้ drawWithCache อาจมีประสิทธิภาพมากกว่าหากใช้ออบเจ็กต์เพื่อวาดเท่านั้น

ตัวอย่างเช่น หากคุณสร้าง Brush เพื่อวาดเส้นไล่ระดับสีด้านหลัง Text การใช้ drawWithCache จะแคชออบเจ็กต์ Brush จนกว่าขนาดของพื้นที่วาดภาพจะเปลี่ยนแปลง

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawWithCache {
            val brush = Brush.linearGradient(
                listOf(
                    Color(0xFF9E82F0),
                    Color(0xFF42A5F5)
                )
            )
            onDrawBehind {
                drawRoundRect(
                    brush,
                    cornerRadius = CornerRadius(10.dp.toPx())
                )
            }
        }
)

การแคชออบเจ็กต์แปรงด้วย drawWithCache
รูปที่ 3: การแคชออบเจ็กต์แปรงด้วย drawWithCache

ตัวปรับแต่งกราฟิก

Modifier.graphicsLayer: ใช้การเปลี่ยนรูปแบบกับคอมโพสิเบิล

Modifier.graphicsLayer คือตัวแก้ไขที่ทำให้เนื้อหาของการวาดแบบคอมโพสิเบิลกลายเป็นเลเยอร์การวาด เลเยอร์มีฟังก์ชันการทำงานหลายอย่าง เช่น

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

การแปลงโฉม

Modifier.graphicsLayer แยกคำสั่งวาดภาพ เช่น ใช้การเปลี่ยนรูปแบบต่างๆ ได้โดยใช้ Modifier.graphicsLayer คุณสามารถทำให้ภาพเคลื่อนไหวหรือแก้ไขได้โดยไม่ต้องเรียกใช้ Drawing lambda อีกครั้ง

Modifier.graphicsLayer จะไม่เปลี่ยนขนาดหรือตําแหน่งการวัดของคอมโพสิเบิล เนื่องจากจะส่งผลต่อระยะการวาดเท่านั้น ซึ่งหมายความว่าคอมโพสิเบิลของคุณอาจทับซ้อนกับคอมโพสิเบิลอื่นๆ หากวาดนอกขอบเขตเลย์เอาต์

การเปลี่ยนรูปแบบต่อไปนี้ใช้ได้กับตัวแก้ไขนี้

ปรับขนาด - เพิ่มขนาด

scaleX และ scaleY จะขยายหรือย่อเนื้อหาในแนวนอนหรือแนวตั้งตามลำดับ ค่า 1.0f บ่งบอกว่าไม่มีการเปลี่ยนแปลงสเกล ส่วนค่า 0.5f หมายถึงครึ่งหนึ่งของมิติข้อมูล

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.scaleX = 1.2f
            this.scaleY = 0.8f
        }
)

รูปที่ 4: scaleX และ scaleY ที่มีผลกับคอมโพสิชันรูปภาพ
การแปลภาษา

translationX และ translationY จะเปลี่ยนได้ด้วย graphicsLayer translationX จะย้ายคอมโพสิเบิลไปทางซ้ายหรือขวา translationY เลื่อนคอมโพสิเบิลขึ้นหรือลง

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.translationX = 100.dp.toPx()
            this.translationY = 10.dp.toPx()
        }
)

รูปที่ 5: translationX และ translationY ใช้กับรูปภาพด้วย Modifier.graphicsLayer
การหมุน

ตั้งค่า rotationX เพื่อหมุนในแนวนอน rotationY เพื่อหมุนในแนวตั้ง และ rotationZ เพื่อหมุนตามแกน Z (การหมุนมาตรฐาน) ค่านี้ระบุเป็นองศา (0-360)

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

รูปที่ 6: ตั้งค่า rotationX, rotationY และ rotationZ ในรูปภาพโดย Modifier.graphicsLayer
Origin

ระบุ transformOrigin ได้ จากนั้นระบบจะใช้จุดดังกล่าวเป็นจุดเริ่มต้นของการเปลี่ยนรูปแบบ ตัวอย่างทั้งหมดที่ผ่านมาใช้ TransformOrigin.Center ซึ่งอยู่ที่ (0.5f, 0.5f) หากคุณระบุจุดเริ่มต้นที่ (0f, 0f) การเปลี่ยนรูปแบบจะเริ่มจากมุมซ้ายบนของคอมโพสิเบิล

หากเปลี่ยนจุดเริ่มต้นด้วยการเปลี่ยนรูปแบบ rotationZ คุณจะเห็นว่ารายการหมุนรอบด้านบนซ้ายของคอมโพสิเบิล

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.transformOrigin = TransformOrigin(0f, 0f)
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

รูปที่ 7: การหมุนที่ใช้โดยตั้งค่า TransformOrigin เป็น 0f, 0f

คลิปและรูปร่าง

รูปร่างจะระบุขอบที่เนื้อหาจะตัดเมื่อ clip = true ในตัวอย่างนี้ เราได้ตั้งค่ากล่อง 2 กล่องให้มีคลิป 2 รายการที่แตกต่างกัน โดยกล่องหนึ่งใช้ตัวแปรคลิป graphicsLayer และอีกกล่องใช้ตัวห่อที่สะดวก Modifier.clip

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .size(200.dp)
            .graphicsLayer {
                clip = true
                shape = CircleShape
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }
    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(CircleShape)
            .background(Color(0xFF4DB6AC))
    )
}

เนื้อหาของช่องแรก (ข้อความ "Hello Compose") ถูกตัดให้เป็นรูปวงกลม

คลิปที่ใช้กับองค์ประกอบของ Box
รูปที่ 8: คลิปที่ใช้กับองค์ประกอบของ Box

จากนั้นใช้ translationY กับวงกลมสีชมพูด้านบน คุณจะเห็นขอบเขตของ Composable ยังคงเหมือนเดิม แต่วงกลมจะวาดอยู่ใต้วงกลมด้านล่าง (และอยู่นอกขอบเขตของวงกลม)

คลิปที่มีการใช้การแปล Y และเส้นขอบสีแดงสำหรับขอบ
รูปที่ 9: คลิปที่ใช้การแปลค่า Y และเส้นขอบสีแดงสำหรับขอบ

หากต้องการตัดองค์ประกอบที่คอมโพสิเบิลไปยังภูมิภาคที่วาด ให้เพิ่ม Modifier.clip(RectangleShape) อื่นที่จุดเริ่มต้นของเชนตัวแก้ไข จากนั้นเนื้อหาจะยังคงอยู่ในขอบเขตเดิม

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .clip(RectangleShape)
            .size(200.dp)
            .border(2.dp, Color.Black)
            .graphicsLayer {
                clip = true
                shape = CircleShape
                translationY = 50.dp.toPx()
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }

    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(RoundedCornerShape(500.dp))
            .background(Color(0xFF4DB6AC))
    )
}

คลิปที่ใช้อยู่ด้านบนของการเปลี่ยนรูปแบบ graphicsLayer
รูปที่ 10: คลิปที่ใช้กับการเปลี่ยนรูปแบบของกราฟิกเลเยอร์

อัลฟ่า

Modifier.graphicsLayer ใช้เพื่อตั้งค่า alpha (ความทึบแสง) ให้กับทั้งเลเยอร์ได้ 1.0f ทึบแสงทั้งหมดและ 0.0f มองไม่เห็น

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "clock",
    modifier = Modifier
        .graphicsLayer {
            this.alpha = 0.5f
        }
)

รูปภาพที่มีการใช้อัลฟ่า
รูปที่ 11: รูปภาพที่มีการใช้อัลฟ่า

กลยุทธ์การคอมโพส

การใช้อัลฟ่าและความโปร่งใสอาจไม่ง่ายเหมือนการเปลี่ยนค่าอัลฟ่าค่าเดียว นอกจากการเปลี่ยนค่าอัลฟ่าแล้ว คุณยังมีตัวเลือกในการตั้งค่า CompositingStrategy ใน graphicsLayer ด้วย CompositingStrategy จะกำหนดวิธีคอมโพส (รวม) เนื้อหาของคอมโพสิเบิลเข้ากับเนื้อหาอื่นๆ ที่วาดบนหน้าจอแล้ว

กลยุทธ์ต่างๆ มีดังนี้

อัตโนมัติ (ค่าเริ่มต้น)

กลยุทธ์การคอมโพสจะกำหนดโดยพารามิเตอร์ graphicsLayerที่เหลือ โดยจะแสดงผลเลเยอร์เป็นบัฟเฟอร์นอกหน้าจอหากค่าอัลฟาน้อยกว่า 1.0f หรือมีการตั้งค่า RenderEffect เมื่อใดก็ตามที่แอลฟามีค่าน้อยกว่า 1f ระบบจะสร้างเลเยอร์การคอมโพสโดยอัตโนมัติเพื่อแสดงผลเนื้อหา จากนั้นจะวาดบัฟเฟอร์นอกหน้าจอนี้ไปยังปลายทางด้วยค่าอัลฟาที่เกี่ยวข้อง การตั้งค่า RenderEffect หรือการเลื่อนผ่านเนื้อหาจะแสดงผลเนื้อหาเป็นบัฟเฟอร์นอกหน้าจอเสมอ ไม่ว่าCompositingStrategyที่ตั้งไว้จะเป็นอย่างไรก็ตาม

นอกหน้าจอ

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

ตัวอย่างการใช้ CompositingStrategy.Offscreen คือ BlendModes จากตัวอย่างด้านล่าง สมมติว่าคุณต้องการนำบางส่วนของคอมโพสิชัน Image ออกโดยออกคำสั่งวาดที่ใช้ BlendMode.Clear หากคุณไม่ได้ตั้งค่า compositingStrategy เป็น CompositingStrategy.Offscreen BlendMode จะโต้ตอบกับเนื้อหาทั้งหมดที่อยู่ด้านล่าง

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = "Dog",
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(120.dp)
        .aspectRatio(1f)
        .background(
            Brush.linearGradient(
                listOf(
                    Color(0xFFC5E1A5),
                    Color(0xFF80DEEA)
                )
            )
        )
        .padding(8.dp)
        .graphicsLayer {
            compositingStrategy = CompositingStrategy.Offscreen
        }
        .drawWithCache {
            val path = Path()
            path.addOval(
                Rect(
                    topLeft = Offset.Zero,
                    bottomRight = Offset(size.width, size.height)
                )
            )
            onDrawWithContent {
                clipPath(path) {
                    // this draws the actual image - if you don't call drawContent, it wont
                    // render anything
                    this@onDrawWithContent.drawContent()
                }
                val dotSize = size.width / 8f
                // Clip a white border for the content
                drawCircle(
                    Color.Black,
                    radius = dotSize,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    ),
                    blendMode = BlendMode.Clear
                )
                // draw the red circle indication
                drawCircle(
                    Color(0xFFEF5350), radius = dotSize * 0.8f,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    )
                )
            }
        }
)

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

Modifier.drawWithContent ในรูปภาพที่แสดงเครื่องหมายวงกลม โดยมี BlendMode.Clear ในแอป
รูปที่ 12: Modifier.drawWithContent ในรูปภาพที่แสดงเครื่องหมายวงกลม โดยมี BlendMode.Clear และ CompositingStrategy.Offscreen ในแอป

หากคุณไม่ได้ใช้ CompositingStrategy.Offscreen ผลลัพธ์ของการใช้ BlendMode.Clear จะล้างพิกเซลทั้งหมดในปลายทาง ไม่ว่าจะมีการตั้งค่าอะไรไว้แล้วก็ตาม ซึ่งจะทำให้บัฟเฟอร์การแสดงผลของหน้าต่าง (สีดํา) มองเห็นได้ BlendModes จำนวนมากที่เกี่ยวข้องกับอัลฟ่าจะไม่ทำงานตามที่คาดไว้หากไม่มีบัฟเฟอร์นอกหน้าจอ สังเกตวงแหวนสีดำรอบตัวบ่งชี้วงกลมสีแดง

Modifier.drawWithContent ในรูปภาพที่แสดงเครื่องหมายวงกลม โดยมี BlendMode.Clear และไม่มีการตั้งค่า CompositingStrategy
รูปที่ 13: Modifier.drawWithContent ในรูปภาพที่แสดงการบ่งชี้วงกลม โดยมี BlendMode.Clear และไม่มีการตั้งค่า CompositingStrategy

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

ไม่มีการตั้งค่า CompositingStrategy และใช้ BlendMode.Clear กับแอปที่มีพื้นหลังหน้าต่างโปร่งแสง วอลเปเปอร์สีชมพูจะแสดงผ่านบริเวณรอบๆ วงกลมสถานะสีแดง
รูปที่ 14: ไม่มีการตั้งค่า CompositingStrategy และใช้ BlendMode.Clear กับแอปที่มีพื้นหลังหน้าต่างโปร่งแสง สังเกตว่าวอลเปเปอร์สีชมพูแสดงผ่านบริเวณรอบๆ วงกลมสถานะสีแดงอย่างไร

โปรดทราบว่าเมื่อใช้ CompositingStrategy.Offscreen ระบบจะสร้างพื้นผิวนอกหน้าจอขนาดเท่ากับพื้นที่วาดและแสดงผลกลับบนหน้าจอ โดยค่าเริ่มต้นแล้ว คำสั่งวาดที่ทำด้วยกลยุทธ์นี้จะตัดให้อยู่ภายในภูมิภาคนี้ ข้อมูลโค้ดด้านล่างแสดงความแตกต่างเมื่อเปลี่ยนไปใช้พื้นผิวนอกหน้าจอ

@Composable
fun CompositingStrategyExamples() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.Center)
    ) {
        // Does not clip content even with a graphics layer usage here. By default, graphicsLayer
        // does not allocate + rasterize content into a separate layer but instead is used
        // for isolation. That is draw invalidations made outside of this graphicsLayer will not
        // re-record the drawing instructions in this composable as they have not changed
        Canvas(
            modifier = Modifier
                .graphicsLayer()
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            // ... and drawing a size of 200 dp here outside the bounds
            drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }

        Spacer(modifier = Modifier.size(300.dp))

        /* Clips content as alpha usage here creates an offscreen buffer to rasterize content
        into first then draws to the original destination */
        Canvas(
            modifier = Modifier
                // force to an offscreen buffer
                .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the
            content gets clipped */
            drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }
    }
}

CompositingStrategy.Auto เทียบกับ CompositingStrategy.Offscreen - คลิปนอกเฟรมไปยังภูมิภาค ซึ่ง Auto จะไม่ทำ
รูปที่ 15: CompositingStrategy.Auto เทียบกับ CompositingStrategy.Offscreen - คลิปนอกเฟรมไปยังภูมิภาคที่ Auto ไม่ได้ทำ
ModulateAlpha

กลยุทธ์การเขียนนี้จะปรับค่าอัลฟ่าสำหรับคำสั่งวาดแต่ละรายการที่บันทึกไว้ใน graphicsLayer จะไม่สร้างบัฟเฟอร์นอกหน้าจอสำหรับค่าอัลฟ่าที่ต่ำกว่า 1.0f เว้นแต่จะมีการตั้งค่า RenderEffect เพื่อให้การแสดงผลอัลฟ่ามีประสิทธิภาพมากขึ้น อย่างไรก็ตาม ฟีเจอร์นี้อาจให้ผลลัพธ์ที่แตกต่างกันสำหรับเนื้อหาที่ทับซ้อนกัน สําหรับกรณีการใช้งานที่ทราบล่วงหน้าว่าเนื้อหาไม่ทับซ้อนกัน รูปแบบนี้จะมีประสิทธิภาพดีกว่าCompositingStrategy.Autoที่มีค่าอัลฟ่าน้อยกว่า 1

ตัวอย่างกลยุทธ์การคอมโพสอื่นๆ อีกตัวอย่างหนึ่งมีดังนี้ การใช้อัลฟ่าที่แตกต่างกันกับส่วนต่างๆ ขององค์ประกอบที่คอมโพสได้ และการใช้Modulateกลยุทธ์ต่อไปนี้

@Preview
@Composable
fun CompositingStrategy_ModulateAlpha() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp)
    ) {
        // Base drawing, no alpha applied
        Canvas(
            modifier = Modifier.size(200.dp)
        ) {
            drawSquares()
        }

        Spacer(modifier = Modifier.size(36.dp))

        // Alpha 0.5f applied to whole composable
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    alpha = 0.5f
                }
        ) {
            drawSquares()
        }
        Spacer(modifier = Modifier.size(36.dp))

        // 0.75f alpha applied to each draw call when using ModulateAlpha
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.ModulateAlpha
                    alpha = 0.75f
                }
        ) {
            drawSquares()
        }
    }
}

private fun DrawScope.drawSquares() {

    val size = Size(100.dp.toPx(), 100.dp.toPx())
    drawRect(color = Red, size = size)
    drawRect(
        color = Purple, size = size,
        topLeft = Offset(size.width / 4f, size.height / 4f)
    )
    drawRect(
        color = Yellow, size = size,
        topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f)
    )
}

val Purple = Color(0xFF7E57C2)
val Yellow = Color(0xFFFFCA28)
val Red = Color(0xFFEF5350)

ModulateAlpha ใช้ค่าอัลฟ่าที่กำหนดกับคำสั่งวาดแต่ละรายการ
รูปที่ 16: ModulateAlpha ใช้ค่าอัลฟ่าที่กำหนดกับคำสั่งวาดแต่ละรายการ

เขียนเนื้อหาของคอมโพสิเบิลลงในบิตแมป

กรณีการใช้งานทั่วไปคือการสร้าง Bitmap จากคอมโพสิเบิล หากต้องการคัดลอกเนื้อหาของคอมโพสิเบิลไปยัง Bitmap ให้สร้าง GraphicsLayer โดยใช้ rememberGraphicsLayer()

เปลี่ยนเส้นทางคำสั่งวาดไปยังเลเยอร์ใหม่โดยใช้ drawWithContent() และ graphicsLayer.record{} จากนั้นวาดเลเยอร์ในผืนผ้าใบที่มองเห็นได้โดยใช้ drawLayer ดังนี้

val coroutineScope = rememberCoroutineScope()
val graphicsLayer = rememberGraphicsLayer()
Box(
    modifier = Modifier
        .drawWithContent {
            // call record to capture the content in the graphics layer
            graphicsLayer.record {
                // draw the contents of the composable into the graphics layer
                this@drawWithContent.drawContent()
            }
            // draw the graphics layer on the visible canvas
            drawLayer(graphicsLayer)
        }
        .clickable {
            coroutineScope.launch {
                val bitmap = graphicsLayer.toImageBitmap()
                // do something with the newly acquired bitmap
            }
        }
        .background(Color.White)
) {
    Text("Hello Android", fontSize = 26.sp)
}

คุณสามารถบันทึกบิตแมปลงในดิสก์และแชร์ได้ ดูรายละเอียดเพิ่มเติมได้ที่ตัวอย่างข้อมูลโค้ดแบบเต็ม โปรดตรวจสอบสิทธิ์ในอุปกรณ์ก่อนพยายามบันทึกลงในดิสก์

ตัวแก้ไขการวาดที่กำหนดเอง

หากต้องการสร้างตัวแก้ไขที่กําหนดเอง ให้ใช้อินเทอร์เฟซ DrawModifier ซึ่งจะช่วยให้คุณเข้าถึง ContentDrawScope ได้ ซึ่งเหมือนกับข้อมูลที่แสดงเมื่อใช้ Modifier.drawWithContent() จากนั้นคุณสามารถดึงข้อมูลการดำเนินการวาดทั่วไปไปยังตัวแก้ไขการวาดที่กำหนดเองเพื่อล้างโค้ดและจัดเตรียมตัวห่อที่สะดวก เช่น Modifier.background() เป็นDrawModifierที่สะดวก

ตัวอย่างเช่น หากต้องการใช้ Modifier ที่พลิกเนื้อหาในแนวตั้ง คุณสามารถสร้าง Modifier ดังกล่าวได้ดังนี้

class FlippedModifier : DrawModifier {
    override fun ContentDrawScope.draw() {
        scale(1f, -1f) {
            this@draw.drawContent()
        }
    }
}

fun Modifier.flipped() = this.then(FlippedModifier())

จากนั้นใช้ตัวหมุนกลับนี้กับ Text

Text(
    "Hello Compose!",
    modifier = Modifier
        .flipped()
)

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

แหล่งข้อมูลเพิ่มเติม

ดูตัวอย่างเพิ่มเติมในการใช้ graphicsLayer และการวาดที่กำหนดเองได้ที่แหล่งข้อมูลต่อไปนี้