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

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

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

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

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

เป้าหมายของเลย์เอาต์ในฟีเจอร์เขียน

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

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

ฟังก์ชันที่ประกอบได้เป็นองค์ประกอบพื้นฐานของ Compose ฟังก์ชันที่ใช้ร่วมกันได้ คือฟังก์ชันที่ปล่อย Unit ซึ่งอธิบายส่วนหนึ่งของ UI ฟังก์ชันจะรับอินพุตบางอย่างและสร้างสิ่งที่แสดงบนหน้าจอ ดูข้อมูลเพิ่มเติมเกี่ยวกับ Composable ได้ในเอกสารประกอบโมเดลความคิดของ 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 โหนดแรกเป็นโหนดใบ (Leaf Node) จึงรายงานขนาดและแสดงผล วิธีการจัดวาง
  7. Column โหนดจะขอให้Textโหนดลูกที่ 2 ทำการวัด
  8. โหนด Text ที่ 2 เป็นโหนดใบ (Leaf Node) จึงรายงานขนาดและแสดง วิธีการจัดวาง
  9. ตอนนี้โหนด Column ได้วัดขนาดและวางตำแหน่งขององค์ประกอบย่อยแล้ว โหนดจึง กำหนดขนาดและตำแหน่งของตัวเองได้
  10. ตอนนี้โหนดรูท Row ได้วัดขนาดและวางตำแหน่งของโหนดลูกแล้ว จึงสามารถกำหนดขนาดและตำแหน่งของตัวเองได้

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

ประสิทธิภาพ

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

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

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

การใช้ตัวแก้ไขในเลย์เอาต์

ดังที่ได้กล่าวไว้ในตัวแก้ไข Compose คุณสามารถใช้ตัวแก้ไขเพื่อตกแต่งหรือเพิ่มฟังก์ชันให้กับ Composable ได้ ตัวแก้ไขเป็นสิ่งจำเป็น สำหรับการปรับแต่งเลย์เอาต์ เช่น ในที่นี้เราจะเชื่อมโยงตัวแก้ไขหลายรายการ เพื่อปรับแต่ง 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 ทำให้ Composable ตอบสนองต่ออินพุตของผู้ใช้และแสดงการกระเพื่อม
  • padding จะเว้นที่ว่างรอบองค์ประกอบ
  • fillMaxWidth ทำให้ Composable เติมความกว้างสูงสุดที่ได้รับจาก องค์ประกอบระดับบน
  • size() ระบุความกว้างและความสูงที่ต้องการขององค์ประกอบ
เลย์เอาต์ XML บางครั้งก็ทำให้ค้นหาได้ยากว่าแอตทริบิวต์เลย์เอาต์หนึ่งๆ ใช้ได้กับมุมมองที่กำหนดหรือไม่

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

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

สำหรับรายการและรายการแบบเลื่อน โปรดดูเอกสารประกอบเกี่ยวกับรายการใน Compose

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

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

ข้อจำกัด

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

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

เลย์เอาต์ตามช่อง

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

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

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

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

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

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

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