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

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

@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

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