การวางเลเยอร์สถาปัตยกรรมของ Jetpack Compose

หน้านี้ให้ภาพรวมระดับสูงของเลเยอร์สถาปัตยกรรมที่ประกอบกันเป็น Jetpack Compose และหลักการสำคัญที่ใช้ในการออกแบบนี้

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

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

เลเยอร์

เลเยอร์หลักของ Jetpack Compose มีดังนี้

รูปที่ 1 เลเยอร์หลักของ Jetpack Compose

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

รันไทม์
โมดูลนี้จะอธิบายพื้นฐานของรันไทม์ Compose เช่น remember mutableStateOf คำอธิบายประกอบ @Composable และ SideEffect คุณอาจพิจารณาสร้างเลเยอร์นี้โดยตรงหากต้องการเพียงความสามารถในการจัดการโครงสร้างของ Compose ไม่ใช่ UI
UI
เลเยอร์ UI ประกอบด้วยโมดูลหลายโมดูล ( ui-text, ui-graphics, ui-tooling, ฯลฯ) โมดูลเหล่านี้ใช้พื้นฐานของชุดเครื่องมือ UI เช่น LayoutNode, Modifier, ตัวแฮนเดิลอินพุต เลย์เอาต์ที่กำหนดเอง และการวาด คุณอาจพิจารณาสร้างเลเยอร์นี้หาก ต้องการเพียงแนวคิดพื้นฐานของชุดเครื่องมือ UI
มูลนิธิ
โมดูลนี้มีบล็อกการสร้างที่ไม่ขึ้นกับระบบการออกแบบสำหรับ Compose UI เช่น Row และ Column LazyColumn การจดจำท่าทางสัมผัสที่เฉพาะเจาะจง ฯลฯ คุณอาจพิจารณาต่อยอดจาก เลเยอร์พื้นฐานเพื่อสร้างระบบการออกแบบของคุณเอง
วัสดุ
โมดูลนี้มีการใช้งานระบบ Material Design สำหรับ Compose UI ซึ่งมีระบบการกำหนดธีม คอมโพเนนต์ที่มีสไตล์ การระบุการกระเพื่อม และไอคอน สร้างเลเยอร์นี้เมื่อใช้ Material Design ใน แอป

หลักการออกแบบ

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

ควบคุม

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

เช่น หากต้องการสร้างภาพเคลื่อนไหวสีของคอมโพเนนต์ คุณอาจใช้ animateColorAsState API ดังนี้

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

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

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

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

การปรับแต่ง

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

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

Button ประกอบด้วยคอมโพเนนต์ 4 อย่าง ได้แก่

  1. Material Surface ซึ่งมีพื้นหลัง รูปร่าง การจัดการการคลิก ฯลฯ

  2. A CompositionLocalProvider ซึ่งจะเปลี่ยนอัลฟ่าของเนื้อหาเมื่อเปิดหรือปิดใช้ปุ่ม

  3. A ProvideTextStyle กำหนดรูปแบบข้อความเริ่มต้นที่จะใช้

  4. Row ระบุนโยบายเลย์เอาต์เริ่มต้นสำหรับเนื้อหาของปุ่ม

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

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

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

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

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

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

Jetpack Compose จะสงวนชื่อที่ง่ายที่สุดไว้สำหรับคอมโพเนนต์ระดับสูงสุด ตัวอย่างเช่น androidx.compose.material.Text สร้างขึ้นบน androidx.compose.foundation.text.BasicText ซึ่งจะช่วยให้คุณสามารถใช้การติดตั้งใช้งานของคุณเองด้วยชื่อที่ค้นพบได้ง่ายที่สุด หากต้องการแทนที่ระดับที่สูงกว่า

การเลือกการแยกส่วนที่เหมาะสม

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

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

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

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

ดูตัวอย่างการสร้างระบบการออกแบบที่กำหนดเองได้ที่ ตัวอย่าง Jetsnack