การวางเลเยอร์สถาปัตยกรรมของ 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 เท่านั้น
Foundation
โมดูลนี้จัดเตรียมบล็อกการสร้างที่แยกจากระบบการออกแบบสำหรับ UI ของ Compose เช่น Row และ Column, LazyColumn การจดจำท่าทางสัมผัสบางอย่าง เป็นต้น คุณอาจพิจารณาสร้างจากเลเยอร์พื้นฐานเพื่อสร้างระบบการออกแบบของคุณเอง
วัสดุ
โมดูลนี้นำระบบดีไซน์ Material สำหรับ 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