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

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

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

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

เลเยอร์

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

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

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

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

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

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

ควบคุม

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

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

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 ที่ได้จากเลเยอร์วัสดุ

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

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

  1. วัสดุ Surface ระบุพื้นหลัง รูปร่าง การจัดการการคลิก ฯลฯ

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

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

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

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

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

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

หากไม่ต้องการใช้แนวคิดของ 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