ConstraintLayout ในการเขียน

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

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

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

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

implementation "androidx.constraintlayout:constraintlayout-compose:$constraintlayout_compose_version"

ConstraintLayout ใน Compose ทำงานในลักษณะต่อไปนี้โดยใช้ DSL:

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

ปุ่มจะปรากฏเหนือข้อความ
รูปที่ 1 Button และคอมโพส Text ที่จำกัดให้สัมพันธ์กันใน 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 มีแนวคิดต่างๆ เช่น เส้นบอกแนว สิ่งกีดขวาง และเชน ซึ่งช่วยในการจัดตำแหน่งองค์ประกอบภายในคอมโพส

เส้นบอกแนว

เส้นบอกแนวเป็นตัวช่วยด้านภาพขนาดเล็กสำหรับการออกแบบเลย์เอาต์ คุณสามารถจำกัดคอมโพสให้สัมพันธ์กับเส้นบอกแนวได้ เส้นบอกแนวมีประโยชน์สำหรับการจัดตำแหน่งองค์ประกอบที่ กำหนดไว้ 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: ระบบจะกระจายพื้นที่ก่อนคอมโพสแรกและหลังคอมโพสสุดท้าย โดยคอมโพสจะอยู่ติดกันโดยไม่มีพื้นที่คั่นระหว่างกัน