ConstraintLayout ในการเขียน

ConstraintLayout คือเลย์เอาต์ที่ช่วยให้คุณวาง Composable โดยสัมพันธ์กับ Composable อื่นๆ บนหน้าจอได้ ซึ่งเป็นทางเลือกแทนการใช้ 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

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

ตัวอย่าง Composable ที่ใช้ 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 Composable ที่จำกัดซึ่งกันและกันใน ConstraintLayout

Decoupled API

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

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

  1. ส่ง ConstraintSet เป็นพารามิเตอร์ไปยัง ConstraintLayout
  2. กำหนดข้อมูลอ้างอิงที่สร้างใน ConstraintSet ให้กับ Composable โดยใช้ตัวแก้ไข 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

หลักเกณฑ์

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

เส้นบอกแนวมี 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() ได้

อุปสรรค

Barrier จะอ้างอิง Composable หลายรายการเพื่อสร้างไกด์ไลน์เสมือนโดยอิงตามวิดเจ็ตที่อยู่สุดขอบด้านที่ระบุ

หากต้องการสร้างสิ่งกีดขวาง ให้ใช้ 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 ที่แตกต่างกัน ซึ่งจะกำหนดวิธีจัดการกับพื้นที่รอบๆ Composable เช่น

  • ChainStyle.Spread: พื้นที่จะกระจายอย่างสม่ำเสมอใน Composable ทั้งหมด รวมถึงพื้นที่ว่างก่อน Composable แรกและหลัง Composable สุดท้าย
  • ChainStyle.SpreadInside: พื้นที่จะกระจายอย่างสม่ำเสมอใน Composable ทั้งหมด โดยไม่มีพื้นที่ว่างก่อน Composable แรกหรือหลัง Composable สุดท้าย
  • ChainStyle.Packed: พื้นที่จะกระจายก่อน Composable แรกและหลัง Composable สุดท้าย โดย Composable จะรวมเข้าด้วยกันโดยไม่มีช่องว่างระหว่างกัน