ConstraintLayout ในการเขียน

ConstraintLayout คือเลย์เอาต์ที่ช่วยให้คุณวางคอมโพสิเบิลตามสัมพันธ์กับคอมโพสิเบิลอื่นๆ บนหน้าจอได้ ซึ่งเป็นอีกทางเลือกหนึ่งนอกเหนือจากการใช้ Row, Column, Box และองค์ประกอบเลย์เอาต์ที่กำหนดเองอื่นๆ ที่ฝังไว้หลายรายการ ConstraintLayout มีประโยชน์เมื่อใช้เลย์เอาต์ขนาดใหญ่ที่มีข้อกำหนดการจัดตำแหน่งที่ซับซ้อนมากขึ้น

พิจารณาใช้ ConstraintLayout ในสถานการณ์ต่อไปนี้

  • เพื่อหลีกเลี่ยงการฝัง Column และ Row หลายรายการเพื่อวางตำแหน่งองค์ประกอบบนหน้าจอเพื่อปรับปรุงความอ่านง่ายของโค้ด
  • เพื่อวางองค์ประกอบที่คอมโพสิเบิลตามองค์ประกอบที่คอมโพสิเบิลอื่นๆ หรือวางองค์ประกอบที่คอมโพสิเบิลตามเส้นกํากับ สิ่งกีดขวาง หรือเชน

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

เริ่มต้นใช้งาน ConstraintLayout

หากต้องการใช้ ConstraintLayout ใน Compose คุณต้องเพิ่มข้อกำหนดนี้ใน build.gradle (นอกเหนือจากการตั้งค่า Compose)

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

ConstraintLayout ในเครื่องมือเขียนจะทํางานด้วยวิธีต่อไปนี้โดยใช้ DSL

  • สร้างการอ้างอิงสำหรับคอมโพสิเบิลแต่ละรายการใน ConstraintLayout โดยใช้ createRefs() หรือ createRefFor()
  • ระบุข้อจำกัดโดยใช้ตัวแก้ไข constrainAs() ซึ่งจะใช้การอ้างอิงเป็นพารามิเตอร์และให้คุณระบุข้อจำกัดของตัวแปรนั้นในบอดี้ของ Lambda
  • ระบุข้อจำกัดโดยใช้ linkTo() หรือวิธีการอื่นๆ ที่เป็นประโยชน์
  • parent คือข้อมูลอ้างอิงที่มีอยู่ซึ่งสามารถใช้เพื่อระบุข้อจำกัดสำหรับ Composable ConstraintLayout เอง

ตัวอย่างคอมโพสิเบิลที่ใช้ ConstraintLayout

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text(
            "Text",
            Modifier.constrainAs(text) {
                top.linkTo(button.bottom, margin = 16.dp)
            }
        )
    }
}

โค้ดนี้จำกัดด้านบนของ Button ไว้กับองค์ประกอบหลักโดยมีระยะขอบ 16.dp และ Text ไว้ที่ด้านล่างของ Button โดยมีระยะขอบ 16.dp ด้วย

แสดงปุ่มและองค์ประกอบข้อความที่จัดเรียงใน ConstraintLayout

API ที่แยกส่วน

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

ในกรณีเช่นนี้ คุณสามารถใช้ ConstraintLayout ในลักษณะอื่นได้ ดังนี้

  1. ส่ง ConstraintSet เป็นพารามิเตอร์ไปยัง ConstraintLayout
  2. กําหนดการอ้างอิงที่สร้างขึ้นใน ConstraintSet ให้กับคอมโพสิเบิลโดยใช้ตัวแก้ไข layoutId

@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

จากนั้นเมื่อต้องการเปลี่ยนข้อจำกัด คุณก็ส่งConstraintSetอื่นได้

แนวคิด ConstraintLayout

ConstraintLayout มีแนวคิดต่างๆ เช่น เส้นกํากับ สิ่งกีดขวาง และเชน ซึ่งช่วยในการจัดตําแหน่งองค์ประกอบภายใน Composable

หลักเกณฑ์

หลักเกณฑ์คือตัวช่วยเล็กๆ ที่มองเห็นได้ซึ่งใช้ออกแบบเลย์เอาต์ คอมโพสิเบิลสามารถถูกจำกัดตามหลักเกณฑ์ได้ เส้นกํากับมีประโยชน์ในการวางองค์ประกอบที่ dp หรือ percentage ที่เจาะจงภายในคอมโพสิชันหลัก

หลักเกณฑ์มี 2 ประเภท ได้แก่ แนวตั้งและแนวนอน รูปภาพแนวนอน 2 รูปคือ top และ bottom ส่วนรูปภาพแนวตั้ง 2 รูปคือ start และ end

ConstraintLayout {
    // Create guideline from the start of the parent at 10% the width of the Composable
    val startGuideline = createGuidelineFromStart(0.1f)
    // Create guideline from the end of the parent at 10% the width of the Composable
    val endGuideline = createGuidelineFromEnd(0.1f)
    //  Create guideline from 16 dp from the top of the parent
    val topGuideline = createGuidelineFromTop(16.dp)
    //  Create guideline from 16 dp from the bottom of the parent
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

หากต้องการสร้างหลักเกณฑ์ ให้ใช้ createGuidelineFrom* กับประเภทหลักเกณฑ์ที่จําเป็น ซึ่งจะสร้างการอ้างอิงที่ใช้ได้ในบล็อก Modifier.constrainAs()

สิ่งกีดขวาง

สิ่งกีดขวางจะอ้างอิงคอมโพสิเบิลหลายรายการเพื่อสร้างเส้นกํากับเสมือนโดยอิงตามวิดเจ็ตด้านนอกสุดของด้านที่ระบุ

หากต้องการสร้างสิ่งกีดขวาง ให้ใช้ createTopBarrier() (หรือ createBottomBarrier(), createEndBarrier(), createStartBarrier()) แล้วระบุข้อมูลอ้างอิงที่ควรประกอบกันเป็นสิ่งกีดขวาง

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val topBarrier = createTopBarrier(button, text)
    }
}

จากนั้นจึงใช้สิ่งกีดขวางในบล็อก Modifier.constrainAs() ได้

โซ่

เชนจะทำงานแบบกลุ่มในแกนเดียว (แนวนอนหรือแนวตั้ง) ส่วนแกนอื่นๆ จะจำกัดแยกกันได้

หากต้องการสร้างเชน ให้ใช้ createVerticalChain หรือ createHorizontalChain

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
        val horizontalChain = createHorizontalChain(button, text)
    }
}

จากนั้นจึงใช้เชนในบล็อก Modifier.constrainAs() ได้

คุณสามารถกําหนดค่าเชนด้วย ChainStyles ที่แตกต่างกัน ซึ่งจะกําหนดวิธีจัดการกับพื้นที่รอบๆ คอมโพสิเบิล เช่น

  • ChainStyle.Spread: ระบบจะจัดสรรพื้นที่อย่างเท่าๆ กันให้กับคอมโพสิเบิลทั้งหมด ซึ่งรวมถึงพื้นที่ว่างก่อนคอมโพสิเบิลแรกและหลังคอมโพสิเบิลสุดท้าย
  • ChainStyle.SpreadInside: ระบบจะจัดสรรพื้นที่ว่างอย่างเท่าๆ กันในคอมโพสิเบิลทั้งหมด โดยไม่มีเว้นวรรคก่อนคอมโพสิเบิลแรกหรือหลังคอมโพสิเบิลสุดท้าย
  • ChainStyle.Packed: ระบบจะเว้นวรรคก่อนคอมโพสิเบิลแรกและหลังคอมโพสิเบิลสุดท้าย โดยคอมโพสิเบิลจะวางต่อกันโดยไม่มีเว้นวรรค

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับ ConstraintLayout ใน Compose จาก API ที่ใช้งานจริงในตัวอย่างการเขียนที่ใช้ ConstraintLayout