ตัวปรับแต่งการเขียน

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

  • เปลี่ยนขนาด เลย์เอาต์ ลักษณะการทำงาน และลักษณะที่ปรากฏของคอมโพสิเบิล
  • เพิ่มข้อมูล เช่น ป้ายกำกับการช่วยเหลือพิเศษ
  • ประมวลผลข้อมูลที่ได้จากผู้ใช้
  • เพิ่มการโต้ตอบระดับสูง เช่น ทําให้องค์ประกอบคลิกได้ เลื่อนได้ ลากได้ หรือซูมได้

ตัวแก้ไขคือออบเจ็กต์ Kotlin มาตรฐาน สร้างตัวแก้ไขโดยการเรียกใช้ฟังก์ชันคลาส Modifier รายการใดรายการหนึ่งต่อไปนี้

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

ข้อความ 2 บรรทัดบนพื้นหลังสี โดยมีระยะห่างจากขอบรอบข้อความ

คุณสามารถต่อเชื่อมฟังก์ชันเหล่านี้เข้าด้วยกันเพื่อคอมโพสิชัน

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

ตอนนี้พื้นหลังสีด้านหลังข้อความจะขยายเต็มความกว้างของอุปกรณ์

ในโค้ดด้านบน ให้สังเกตการใช้ฟังก์ชันตัวแก้ไขต่างๆ ร่วมกัน

  • padding เว้นวรรครอบๆ องค์ประกอบ
  • fillMaxWidth ทําให้คอมโพสิเบิลเติมความกว้างสูงสุดที่ได้รับจากส่วนกลาง

แนวทางปฏิบัติแนะนำคือให้คอมโพสิเบิลทั้งหมดยอมรับพารามิเตอร์ modifier และส่งตัวแก้ไขนั้นไปยังคอมโพสิเบิลย่อยรายการแรกที่แสดง UI ซึ่งทำให้โค้ดของคุณนํากลับมาใช้ซ้ำได้มากขึ้น รวมถึงทําให้ลักษณะการทํางานของโค้ดคาดการณ์ได้และใช้งานง่ายขึ้น ดูข้อมูลเพิ่มเติมได้ที่หลักเกณฑ์ของ Compose API หัวข้อองค์ประกอบยอมรับและปฏิบัติตามพารามิเตอร์ตัวแก้ไข

ลําดับของตัวปรับมีความสําคัญ

ลําดับของฟังก์ชันตัวแก้ไขมีความสําคัญ เนื่องจากแต่ละฟังก์ชันทําการเปลี่ยนแปลงModifierที่แสดงผลโดยฟังก์ชันก่อนหน้า ลําดับจึงส่งผลต่อผลลัพธ์สุดท้าย มาดูตัวอย่างกัน

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

พื้นที่ทั้งหมด รวมถึงระยะห่างจากขอบจะตอบสนองต่อการคลิก

ในโค้ดด้านบน ผู้ใช้คลิกได้ทุกพื้นที่ รวมถึงการเว้นวรรครอบๆ เนื่องจากมีการใช้ตัวแก้ไข padding หลังตัวแก้ไข clickable หากลำดับตัวแก้ไขกลับกัน เว้นวรรคที่เพิ่มโดย padding จะไม่ตอบสนองต่ออินพุตของผู้ใช้

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

ระยะห่างจากขอบของเลย์เอาต์ไม่ตอบสนองต่อการคลิกอีกต่อไป

ตัวปรับแต่งในตัว

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

padding และ size

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

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

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

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

รูปภาพย่อยมีขนาดใหญ่กว่าข้อจำกัดที่มาจากรูปภาพหลัก

ในตัวอย่างนี้ แม้ว่าจะมีการตั้งค่า height หลักเป็น 100.dp แต่ความสูงของ Image จะเป็น 150.dp เนื่องจากตัวแก้ไข requiredSize มีความสำคัญเหนือกว่า

หากต้องการให้เลย์เอาต์ย่อยใช้ความสูงทั้งหมดที่เลย์เอาต์หลักอนุญาต ให้เพิ่มตัวแก้ไข fillMaxHeight (คอมโพซยังมี fillMaxSize และ fillMaxWidth ด้วย)

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

ความสูงของรูปภาพเท่ากับองค์ประกอบหลัก

หากต้องการเพิ่มระยะห่างจากขอบรอบๆ องค์ประกอบ ให้ตั้งค่าตัวแก้ไข padding

หากต้องการเพิ่มระยะห่างจากบรรทัดฐานของข้อความเพื่อให้ได้ระยะห่างที่เจาะจงจากด้านบนของเลย์เอาต์ถึงบรรทัดฐาน ให้ใช้ตัวแก้ไข paddingFromBaseline ดังนี้

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

ข้อความที่มีระยะห่างจากด้านบน

ออฟเซ็ต

หากต้องการจัดตําแหน่งเลย์เอาต์ตามตําแหน่งเดิม ให้เพิ่มตัวแก้ไข offset และตั้งค่าการเลื่อนในแกน x และ y ออฟเซตอาจเป็นค่าบวกหรือไม่ใช่ค่าบวกก็ได้ ความแตกต่างระหว่าง padding กับ offset คือการเพิ่ม offset ลงในคอมโพสิเบิลจะไม่เปลี่ยนแปลงการวัดผล

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

ข้อความเลื่อนไปทางด้านขวาของคอนเทนเนอร์หลัก

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

ตัวแก้ไข offset มีการโอเวอร์โหลด 2 รายการ ได้แก่ offset ที่ใช้ออฟเซตเป็นพารามิเตอร์ และ offset ที่ใช้ Lambda ดูข้อมูลเชิงลึกเพิ่มเติมเกี่ยวกับกรณีที่ควรใช้แต่ละรายการและวิธีเพิ่มประสิทธิภาพได้ที่ส่วนประสิทธิภาพการเขียน - เลื่อนการอ่านไว้นานที่สุด

ความปลอดภัยของขอบเขตใน Compose

ใน Compose มีตัวปรับเปลี่ยนที่ใช้ได้เฉพาะกับองค์ประกอบย่อยของคอมโพสิเบิลบางรายการเท่านั้น Compose บังคับใช้สิ่งนี้ด้วยขอบเขตที่กำหนดเอง

เช่น หากต้องการทําให้องค์ประกอบย่อยมีขนาดใหญ่เท่ากับองค์ประกอบหลัก Box โดยไม่ส่งผลต่อขนาด Box ให้ใช้ตัวแก้ไข matchParentSize matchParentSize มีให้บริการใน BoxScope เท่านั้น ดังนั้นจึงใช้ได้กับรายการย่อยภายในรายการหลัก Box เท่านั้น

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

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

matchParentSizeในBox

ดังที่กล่าวไว้ข้างต้น หากต้องการให้เลย์เอาต์ย่อยมีขนาดเท่ากับเลย์เอาต์หลัก Box โดยไม่ส่งผลต่อขนาด Box ให้ใช้ตัวแก้ไข matchParentSize

โปรดทราบว่า matchParentSize ใช้ได้เฉพาะภายในขอบเขต Box ซึ่งหมายความว่าจะใช้ได้กับคอมโพสิเบิล Box ย่อยโดยตรงเท่านั้น

ในตัวอย่างนี้ Spacer ที่เป็นองค์ประกอบย่อยจะรับขนาดมาจาก Box ซึ่งเป็นองค์ประกอบหลัก ซึ่งก็รับขนาดมาจาก ArtistCard ซึ่งเป็นองค์ประกอบย่อยที่ใหญ่ที่สุด

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

พื้นหลังสีเทาที่เติมเต็มคอนเทนเนอร์

หากใช้ fillMaxSize แทน matchParentSize Spacer จะใช้พื้นที่ว่างทั้งหมดที่อนุญาตสำหรับรายการหลัก ซึ่งจะทำให้รายการหลักขยายและเติมเต็มพื้นที่ว่างทั้งหมด

พื้นหลังสีเทาเต็มหน้าจอ

weight ใน Row และ Column

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

มาดู Row ที่มีคอมโพสิเบิล Box 2 รายการ กล่องแรกมี weight เป็น 2 เท่าของกล่องที่ 2 จึงมี 2 เท่าของความกว้าง เนื่องจาก Row กว้าง 210.dp Box แรกจึงกว้าง 140.dp และBox ที่ 2 กว้าง 70.dp

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

ความกว้างของรูปภาพเป็น 2 เท่าของความกว้างของข้อความ

การคลายไฟล์และนำตัวแก้ไขมาใช้ซ้ำ

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

Modifier.Element แต่ละรายการแสดงถึงลักษณะการทํางานแต่ละอย่าง เช่น ลักษณะการทํางานของเลย์เอาต์ การวาด และกราฟิก ลักษณะการทํางานที่เกี่ยวข้องกับท่าทางสัมผัส โฟกัส และความหมายทั้งหมด รวมถึงเหตุการณ์อินพุตของอุปกรณ์ ลําดับขององค์ประกอบเหล่านี้มีความสำคัญ โดยระบบจะใช้องค์ประกอบตัวแก้ไขที่เพิ่มไว้ก่อน

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

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

แนวทางปฏิบัติแนะนำในการใช้ตัวแก้ไขซ้ำ

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

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

ดึงข้อมูลและนําตัวแก้ไขมาใช้ซ้ำเมื่อสังเกตสถานะที่เปลี่ยนแปลงบ่อย

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

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

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

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

การสกัดและนําตัวแก้ไขที่ไม่มีขอบเขตมาใช้ซ้ำ

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

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

ซึ่งจะเป็นประโยชน์อย่างยิ่งเมื่อใช้ร่วมกับเลย์เอาต์แบบ Lazy ในกรณีส่วนใหญ่ คุณควรให้รายการทั้งหมดที่มีจำนวนมาก (ซึ่งอาจมาก) ของคุณมีตัวแก้ไขเดียวกันทุกประการ ดังนี้

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

การสกัดและนําตัวแก้ไขที่มีขอบเขตมาใช้ซ้ำ

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

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

คุณควรส่งเฉพาะตัวแก้ไขที่มีขอบเขตซึ่งดึงข้อมูลแล้วไปยังรายการย่อยโดยตรงที่มีขอบเขตเดียวกัน ดูข้อมูลอ้างอิงเพิ่มเติมเกี่ยวกับเหตุผลที่เรื่องนี้สำคัญได้ที่ส่วนความปลอดภัยของขอบเขตใน Compose

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

การต่อเชื่อมตัวแก้ไขที่ดึงมาเพิ่มเติม

คุณสามารถต่อหรือต่อท้ายเชนตัวแก้ไขที่ดึงมาได้โดยเรียกใช้ฟังก์ชัน .then() ดังนี้

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

โปรดทราบว่าลําดับของตัวแก้ไขนั้นสําคัญ

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

เรามีรายการตัวแก้ไขทั้งหมดพร้อมพารามิเตอร์และขอบเขต

หากต้องการฝึกฝนเพิ่มเติมเกี่ยวกับวิธีใช้ตัวแก้ไข คุณยังดูเลย์เอาต์พื้นฐานใน Codelab ของ Compose หรือดูตอนนี้ในที่เก็บข้อมูล Android ได้ด้วย

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