ข้อมูลเบื้องต้นเกี่ยวกับเลย์เอาต์ของการเขียน

Jetpack Compose ช่วยให้การออกแบบและสร้าง UI ของแอปง่ายขึ้นมาก Compose จะแปลงสถานะเป็นองค์ประกอบ UI ผ่านสิ่งต่อไปนี้

  1. การจัดวางองค์ประกอบ
  2. เลย์เอาต์ขององค์ประกอบ
  3. การวาดองค์ประกอบ

Compose สถานะการเปลี่ยนรูปแบบไปยัง UI ผ่านการจัดองค์ประกอบ เลย์เอาต์ การวาด

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

เป้าหมายของเลย์เอาต์ใน Compose

การใช้งานระบบเลย์เอาต์ของ Jetpack Compose มีเป้าหมายหลัก 2 ประการ ได้แก่

ข้อมูลเบื้องต้นเกี่ยวกับฟังก์ชันที่ประกอบกันได้

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

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

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

หากไม่มีคำแนะนำเกี่ยวกับวิธีจัดเรียง องค์ประกอบ Compose จะซ้อนองค์ประกอบข้อความไว้ด้านบนของกันและกัน ทำให้ข้อความอ่านไม่ได้

องค์ประกอบข้อความ 2 รายการที่วาดซ้อนกัน ทำให้ข้อความอ่านไม่ได้

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

คอมโพเนนต์เลย์เอาต์มาตรฐาน

ในหลายๆ กรณี คุณสามารถใช้เพียง องค์ประกอบเลย์เอาต์มาตรฐาน ของ Compose

ใช้ Column เพื่อวางรายการในแนวตั้งบนหน้าจอ

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

องค์ประกอบข้อความ 2 รายการจัดเรียงในเลย์เอาต์คอลัมน์เพื่อให้ข้อความอ่านได้

ในทำนองเดียวกัน ให้ใช้ Row เพื่อวางรายการในแนวนอนบนหน้าจอ ทั้ง Column และ Row รองรับการกำหนดค่าการจัดแนวขององค์ประกอบที่ประกอบด้วย

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

แสดงเลย์เอาต์ที่ซับซ้อนมากขึ้น โดยมีกราฟิกขนาดเล็กอยู่ข้างคอลัมน์ขององค์ประกอบข้อความ

ใช้ Box เพื่อวางองค์ประกอบไว้ด้านบนขององค์ประกอบอื่น Box ยังรองรับการกำหนดค่าการจัดแนวที่เฉพาะเจาะจงขององค์ประกอบที่ประกอบด้วย

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

แสดงองค์ประกอบ 2 รายการซ้อนกัน

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

เปรียบเทียบ Composable ของเลย์เอาต์ 3 แบบ ได้แก่ คอลัมน์ แถว และกล่อง

หากต้องการกำหนดตำแหน่งขององค์ประกอบย่อยภายใน Row ให้ตั้งค่าอาร์กิวเมนต์ horizontalArrangement และ verticalAlignment สำหรับ Column ให้ตั้งค่าอาร์กิวเมนต์ verticalArrangement และ horizontalAlignment

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

จัดแนวรายการไปทางขวา

โมเดลเลย์เอาต์

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

กล่าวโดยย่อคือ โหนดแม่จะวัดขนาดก่อนโหนดลูก แต่จะกำหนดขนาดและวางหลังจากโหนดลูก

ลองดูฟังก์ชัน SearchResult ต่อไปนี้

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

ฟังก์ชันนี้จะสร้างแผนผัง UI ต่อไปนี้

SearchResult
  Row
    Image
    Column
      Text
      Text

ในตัวอย่าง SearchResult เลย์เอาต์แผนผัง UI จะเป็นไปตามลำดับต่อไปนี้

  1. ระบบจะขอให้โหนดราก Row วัดขนาด
  2. โหนดราก Row ขอให้โหนดลูกแรก Image วัดขนาด
  3. Image เป็นโหนดใบ (ไม่มีโหนดลูก) จึงรายงานขนาดและส่งคำแนะนำในการวางกลับ
  4. โหนดราก Row ขอให้โหนดลูกที่ 2 Column วัดขนาด
  5. โหนด Column ขอให้โหนดลูก Text แรกวัดขนาด
  6. โหนด Text แรกเป็นโหนดใบ จึงรายงานขนาดและส่งคำแนะนำในการวางกลับ
  7. โหนด Column ขอให้โหนดลูก Text ที่ 2 วัดขนาด
  8. โหนด Text ที่ 2 เป็นโหนดใบ จึงรายงานขนาดและส่งคำแนะนำในการวางกลับ
  9. เมื่อโหนด Column วัดขนาด กำหนดขนาด และวางโหนดลูกแล้ว ก็จะกำหนดขนาดและการวางของตัวเองได้
  10. เมื่อโหนดราก Row วัดขนาด กำหนดขนาด และวางโหนดลูกแล้ว ก็จะกำหนดขนาดและการวางของตัวเองได้

ลำดับการวัด การกำหนดขนาด และการวางตำแหน่งในโครงสร้าง UI ของผลการค้นหาใน Search

ประสิทธิภาพ

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

หากเลย์เอาต์ต้องมีการวัดขนาดหลายครั้งด้วยเหตุผลบางประการ Compose มีระบบพิเศษที่เรียกว่า การวัดขนาดโดยธรรมชาติ คุณอ่านเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ได้ที่ การวัดขนาดโดยธรรมชาติในเลย์เอาต์ Compose

เนื่องจากการวัดขนาดและการวางเป็นระยะย่อยที่แยกกันของการส่งผ่านเลย์เอาต์ การเปลี่ยนแปลงใดๆ ที่ส่งผลต่อการวางรายการเท่านั้น ไม่ใช่การวัดขนาด จึงดำเนินการแยกกันได้

การใช้ตัวปรับแต่งในเลย์เอาต์

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

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

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

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

  • clickable ทำให้ฟังก์ชันที่ประกอบกันได้ตอบสนองต่อข้อมูลจากผู้ใช้และแสดง Ripple
  • padding เพิ่มพื้นที่ว่างรอบๆ องค์ประกอบ
  • fillMaxWidth ทำให้ฟังก์ชันที่ประกอบกันได้เติมความกว้างสูงสุดที่กำหนดโดยองค์ประกอบแม่
  • size() ระบุความกว้างและความสูงที่ต้องการขององค์ประกอบ

เลย์เอาต์ที่เลื่อนได้

ดูข้อมูลเพิ่มเติมเกี่ยวกับเลย์เอาต์ที่เลื่อนได้ใน เอกสารประกอบเกี่ยวกับท่าทางสัมผัสของ Compose

สำหรับรายการและรายการแบบ Lazy โปรดดูเอกสารประกอบเกี่ยวกับรายการของ Compose

เลย์เอาต์ที่ปรับเปลี่ยนตามอุปกรณ์

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

ข้อจำกัด

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

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

เลย์เอาต์ที่อิงตามสล็อต

Compose มีฟังก์ชันที่ประกอบกันได้หลากหลายรายการที่อิงตาม Material Design พร้อมทรัพยากร Dependency androidx.compose.material:material (รวมอยู่ด้วยเมื่อสร้าง โปรเจ็กต์ Compose ใน Android Studio) เพื่อให้การสร้าง UI เป็นเรื่องง่าย โดยมีองค์ประกอบต่างๆ เช่น Drawer, FloatingActionButton, และ TopAppBar

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

แผนภาพแสดงช่องที่มีในแถบแอปของคอมโพเนนต์ Material

โดยปกติแล้วฟังก์ชันที่ประกอบกันได้จะใช้แลมดา content ที่ประกอบกันได้ ( content: @Composable () -> Unit) API สล็อตจะเปิดเผยพารามิเตอร์ content หลายรายการสำหรับการใช้งานที่เฉพาะเจาะจง ตัวอย่างเช่น TopAppBar ช่วยให้คุณระบุเนื้อหาสำหรับ title, navigationIcon และ actions ได้

ตัวอย่างเช่น Scaffold ช่วยให้คุณใช้ UI ที่มีโครงสร้างเลย์เอาต์พื้นฐานของ Material Design ได้ Scaffoldมีสล็อตสำหรับคอมโพเนนต์เนื้อหาระดับบนสุดที่ใช้กันมากที่สุด เช่น TopAppBar, BottomAppBar, FloatingActionButton, และ Drawer การใช้ Scaffold จะช่วยให้มั่นใจได้ว่าคอมโพเนนต์เหล่านี้อยู่ในตำแหน่งที่เหมาะสมและทำงานร่วมกันได้อย่างถูกต้อง

แอปตัวอย่าง JetNews ซึ่งใช้ Scaffold เพื่อจัดตำแหน่งองค์ประกอบหลายรายการ

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}