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

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

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

ตัวแก้ไขเป็นออบเจ็กต์ 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 ทำให้ Composable มีความกว้างสูงสุดที่ได้รับจาก องค์ประกอบระดับบน

แนวทางปฏิบัติแนะนำคือให้ Composable ทั้งหมดยอมรับพารามิเตอร์ modifier และส่งตัวแก้ไขนั้นไปยังองค์ประกอบย่อยแรกที่ปล่อย UI การทำเช่นนี้จะทำให้โค้ดของคุณนำกลับมาใช้ซ้ำได้มากขึ้น และทำให้ลักษณะการทำงานของโค้ดคาดการณ์ได้ง่ายขึ้นและใช้งานง่ายขึ้น ดูข้อมูลเพิ่มเติมได้ที่หลักเกณฑ์ของ Compose API, Elements accept and respect a Modifier parameter

ลำดับของตัวแก้ไขมีความสำคัญ

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

padding และ size

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

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

โปรดทราบว่าระบบอาจไม่ใช้ขนาดที่คุณระบุหากไม่เป็นไปตามข้อจำกัดจากองค์ประกอบหลักของเลย์เอาต์ หากต้องการกำหนดขนาดของ Composable ไม่ว่าข้อจำกัดขาเข้าจะเป็นอย่างไร ให้ใช้ตัวแก้ไข 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 (Compose ยังมี 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 จะไม่ เปลี่ยนการวัดค่า

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

ข้อความเลื่อนไปทางด้านขวาของคอนเทนเนอร์ระดับบนสุด

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

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

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

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

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

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

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

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 ที่มี Composable 2 รายการ Box กัน กล่องแรกมี 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 เท่าของความกว้างของข้อความ

การแยกและนำตัวแก้ไขกลับมาใช้ใหม่

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

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

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

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

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

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

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

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

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

@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
    )
}

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

// 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
    )
}

การแยกและนำตัวแก้ไขที่ไม่มีขอบเขตกลับมาใช้ใหม่

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

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

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

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

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,
            )
        }
    }
}

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

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

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 หรือดู ที่เก็บ Now in Android

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