เข้าค่าย Compose Camp ชาว Android Developer กัน (ช่วงเช้า)

Android Oct 19, 2023

และแล้วในที่สุด ในไทยเรามีกิจกรรมนี้แล้วนะทุกคน หลังจากที่เราลอยคอ เอ้ยย รอคอยกันมานาน สำหรับค่าย Compose Camp บล็อกนี้ก็ทำนานกว่าจะเสร็จเช่นกัน

โดย Compose Camp จัดวันอาทิตย์ที่ 11 ธันวาคม 2565 ที่ Skooldio โรงเรียนที่เรารัก และรักเรา 55555 อยู่ที่ MBK กดลิฟต์ไปชั้น 20 ได้เลยจ้า กิจกรรมเริ่มตั้งแต่ 10:00 - 19:30 น. (เพราะตึกเปิดสิบโมง เวลายามถามว่าไปไหน บอกไปเรียนชั้น 20)

สิ่งที่ต้องเตรียม นอกจากเตรียมตัว เตรียมใจ เตรียมพบคนสอน TA และเพื่อนร่วม workshop 30 คนแล้ว เตรียม Laptop ที่ติดตั้ง Android Studio Dolphin 2021.3.1 Patch 1 (Latest Stable) แล้วก็ Emulator หรือเครื่องจริงที่เป็น Android 10 ขึ้นไป เพราะมันมีผลตอนใช้งาน Layout Inspector

แล้วก็อย่าลืม clone repo อันนี้ ComposeCampTH2022 [GitHub] แล้วเปิดทุกโปรเจกต์ ยํ้าว่าทุกโปรเจกต์ เพื่อ Sync Gradle และ Build Project ให้เรียบร้อย เวลาเรียนจริงจะได้ลื่น ๆ

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

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

อันนี้โพสเปิดรับลงทะเบียนเนอะ เนื่องจากทีมมี knowledge sharing โดยน้องอินเทิร์น ทางเราเลยต้องขยันหน่อย เลยส่งใบสมัครพร้อมเขียนเรียงความไปด้วย และก็ได้เข้าร่วมงานนี้ (ถ้าไม่ได้เข้านี่ไม่มีบล็อกนี้ให้อ่านแน่นอน 555555555555555)

.

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

Jetpack Compose for Android Developers
Jetpack Compose for Android Developers
https://developer.android.com/courses/jetpack-compose/course
🗒️
เนื่องจากหลังจากงานนี้เราได้ติดงานบางอย่างเป็นเวลาหลายเดือน ทำให้การสรุปบล็อกนี้ล่าช้า ดังนั้นอาจจะเป็นการ wrap-up เนื้อหาในนั้นแทนเนอะ

เมื่อถึงเวลาสิบโมงเช้าที่นัดกันไว้ มีผู้คนมากมายรอเรียนกันอยู่แล้วแหละเนอะ 5555

เปิดงาน Compose Camp

สักพักก็ถึงพิธีเปิดงาน โดยพี่เอก ซึ่งในตอนนี้ Compose เป็น version 1.3 ที่ stable แล้ว

บางคนอาจจะทำแบบ native หรือ cross platform อย่าง Flutter หรือ React Native ไม่กล้า adopt เป็น Compose งานนี้จัดขึ้นเพื่อให้เขานำไป adopt ใน product ได้ และอยากให้ทีมต่าง ๆ มาใช้ Compose มากข้ึน

แล้วคนที่มาสอนในวันนี้ ทั้งคุณต๊ะ และคุณจู ก็ได้นำเจ้า Compose ไป adopt ใช้จริงบน production แล้วนะ

สาเหตุที่คัดเลือกจำนวน 30 คน เพราะอยากให้ focus ที่คนเรียนเป็นหลัก และมี TA เยอะมาก ถ้าเทียบสัดส่วนต่อคนเรียน เมื่อเราติดปัญหาอะไรสามารถถาม TA หรือคนสอน เพื่อสามารถ focus ในจุดนั้นได้

Compose Overview

เริ่มต้นด้วยเลคเชอร์จากคุณต๊ะก่อนเลย

ก่อนอื่นเลย

Jetpack != Jetpack Compose

โดย Compose เป็นส่วนนึง หรือเป็น subset ก็ได้ ของ Jetpack และ Jetpack Compose เป็น Declarative UI

แล้วมันแตกต่างจากเดิมที่เราใช้ XML ขึ้น layout ยังไงนะ?

Declarative UI & Imperative UI

XML ที่เราใช้กันนั้น เป็น Imperative UI บอกแต่ละ component ว่าตัวเองทำงานยังไง

นอกจาก XML ที่เป็น Imperative UI แล้ว ยังมีพวก class logic ต่าง ๆ รวมไปถึงการ binding XML ที่ผูกกับ logic ต่าง ๆ

ถ้าเรามี stage เยอะ เอาไป handle ไม่ดี แอพเราก็จะบัคได้ เช่น กดปุ่ม + เพื่อเพิ่มตัวเลข แล้วนำไปแสดงใน text box เนอะ ซึ่งมันจะอัพเดตเมื่อเมื่อมีการกดปุ่มพวกนี้ แต่ถ้ากดรัว ๆ ก็อาจจะบัคได้

ส่วน Declarative UI เป็นการบอกว่า เขาต้องทำอะไร มี state ที่แชร์ร่วมกันตรงกลาง อยู่ที่เดียวเท่านั้น ซึ่งการใช้จริงเราจะเก็บค่าพวกนี้ใน ViewModel แทน

State in View: การเก็บ state ผูกไว้กับ view ทำให้เราทำ unit test ได้ยาก จึงต้องใช้ LiveData ทำเป็น State เพื่อให้สามารถ test ได้ง่ายขึ้น

ข้อดี

  • เขียนโค้ดสั้นลง เขียนภาษาเดียว ไม่ต้อง handle state ให้วุ่นวาย
  • ทำงานได้เร็ว และง่ายขึ้น
  • ใช้ภาษา Kotlin ในการเขียน และ support Material Design ให้อยู่แล้ว

Thinking in Compose

simple compose

  • เราใส่ annotation @composable หน้า function ซึ่งเขามี component ต่าง ๆ ให้เราเรียกใช้ได้
@Composable
fun Greeting(name: String) {
    Text("Hello $name")
}
  • composable function สามารถใส่ parameter ได้ และจะไม่ return อะไรกลับมา
  • function ต้องง่าย และเร็ว และ result ควรจะเหมือนเดิม แล้วต้องระวัง side effect จากการเปลี่ยนค่าด้วย

declarative paradigm shift

  • data จะส่งตาม tree ของ view ไปเรื่อย ๆ compose จะ render จุดที่เปลี่ยนแปลงเท่านั้น
  • callback lambda function

dynamic content เช่น list เราสามารถใช้ for loop เพื่อ render ได้เลย สามารถทำได้ง่ายขึ้น

recomposition จะทำการ recompose เมื่อมีการอัพเดตของข้อมูลนั่นเอง

things to be aware of when you program in Compose

  • execute อาจจะไม่ parallel เสมอไป
  • skip frame อื่น ๆ ในการ render และ cancel อันเก่า เพื่อ render อันใหม่
  • หลีกเลี่ยง operation หนัก ๆ ในการ compose ทำให้แอพของเรามี performance ที่ไม่ดีได้ และเกิด memory leak

Basic Compose

  • ขึ้นต้น function ด้วยตัวพิมพ์ใหญ่ ทำให้เราสามารถแยกได้ว่านี่เป็น function เป็น compose นะ และข้างในควรเป็น widget ของ compose เนาะ
  • ใส่ @Preview ในการ support ในการแสดง compose function นั้น ๆ ใน preview
  • เขียน component ซ้อนไปในจุดเดิมได้ ลักษณะการเรียงข้างในจะเหมือน FrameLayout
  • basic layout ของ Compose จะมี column (vertical แนวตั้ง), row (horizontal แนวนอน), box (z axis แนวลึก)
  • ใช้ modifier ในการ set width, height, padding เอ้อ compose ไม่มี margin นะ ใช้กับ padding ไปเลย ว่าให้มันห่างเท่าไหร่ (ปกติถ้าเป็น layout XML จะมี margin และ padding เนอะ) แล้วมี spacer เป็นการเพิ่มพื้นที่ว่างด้วยนะ
  • compose มาพร้อม Material Design ดังนั้นเราสามารถ set darkmode ตาม System UI ได้เลย และ 1 UI มีได้หลาย theme ดังนั้น 1 application สามารถมีได้หลาย theme เช่นกัน และรองรับแบบ custom ได้ด้วยนะ
  • ชนิดของ List จะมี LazyColumn(แนว vertical), LazyRow(แนว horizontal), LazyGrid รองรับทั้งแนว vertical และ horizontal

มาสร้างโปรเจกต์ใหม่ โดยใช้ Compose กัน

หลังจากที่เราเรียนกันไปสักพักแล้ว ถึงเวลาลงมือทำจริงแล้วค้าบผม

ก่อนอื่น สร้างโปรเจกต์ใหม่ เลือก Empty Compose Activity

จากนั้นตั้งชื่อโปรเจกต์ ในที่นี้ใช้ชื่อว่า ComposeSkooldio ก็แล้วกันเนอะ

จากนั้นกดปุ่ม Finish เลยจ้า แล้วก็รอโปรเจกต์ของเราบิ้วสักพักก็จะเป็นประมาณนี้เนอะ

เราจะเห็นว่าตัว function ที่เป็น compose นั้น จะขึ้นต้นชื่อ function ด้วยตัวพิมพ์ใหญ่ เพื่อให้สามารถแยกชื่อจาก function ปกติได้ และมีการใส่ annotation @Composable ไว้ข้างบนด้วย ข้างใน function นี้ จะเป็น widget ของ compose เนอะ

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

ถ้าเราอยากเห็นตัว compose ที่เราเพิ่งเขียนไป ถ้าเราทำ layout XML เราจะต้องรันแอพเพื่อให้เห็นตัว layout ที่เราทำเนอะ แต่ของ compose สามารถทำได้ง่ายกว่านั้น คือ ใส่ annotation @Preview ซึ่งมัน support UI mode

ตอน preview เราจะครอบด้วย theme ที่เราต้องการ ตามด้วย compose function ที่เราต้องการ แบบนี้เลย

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    ComposeSkooldioTheme {
        Greeting("Android")
    }
}

ตัว theme ต่าง ๆ เราสามารถปรับแต่งได้เลย ที่ไฟล์ Theme.kt โดยเราสามารถใส่สีต่าง ๆ ของ theme ของเราได้ เป็นการเพิ่ม color palette เนอะ

โดยตัว Color ใช้ของ androidx.compose.ui.graphics นะ

หน้าตา MyTheme ก็จะมีสีจาก MyThemePalette ที่เพิ่งทำไป สามารถใส่อันอื่น ๆ เข้าไปได้ด้วย เช่น typography เราจะให้แอพเราเป็น font type อะไร, shapes, content

private val MyThemePalette = lightColors(
    primary = Color(0x00FF6600),
    secondary = Color(0x0000FF66),
    surface = Color(0x55000000)
)

@Composable
fun MyTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colors = MyThemePalette,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

เราสามารถใช้หลาย ๆ theme ในแอพของเราได้ รวมถึงการ preview ใน UI mode ด้วยเช่นกัน แต่เราจะเจอ The Preview is out of date เรากด Build & Refresh เพื่ออัพเดต UI mode กันก่อน

จากนั้นก็จะเป็นแบบนี้เนอะ preview อันใหม่ให้เราแล้ว

อธิบายเพิ่มเติม เราสามารถนำ MyTheme ที่เพิ่งสร้าง ไปใช้งานต่อได้เลย โดยเอามาครอบ compose function ที่ต้องการ preview และตอน preview นั้น เราสามารถทำ function preview ได้หลาย ๆ อันเลย

@Composable
fun Greeting(name: String) {
    Surface(color = MaterialTheme.colors.surface) {
        Column {
            Text(text = "Hello $name!")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreviewMyTheme() {
    MyTheme {
        Greeting("Android")
    }
}
  • @Preview(showBackground = true) อันนี้นอกจากบอกให้ preview แล้ว เราบอกให้แสดงพื้นหลังได้ด้วยนะ โดยให้ showBackground = true
  • ตัว Surface เราสามารถใส่สีลงไปได้ด้วยนะ โดยให้มีค่าเป็น MaterialTheme.colors.surface

ถ้าเราเขียน component เพิ่มเข้าไป ในที่นี้คือเพิ่ม Text อีกตัวนึงเนอะ มันจะเอาของใหม่ซ้อนที่จุดเดิม มันจะเหมือน FrameLayout จึงต้องใช้ basic layout ของ Compose ซ้อนอีกชั้นก่อน นั่นคือ Column, Row และ Box นั่นเอง

ในที่นี้ใช้ Column ครอบ widget ลูก ๆ ซึ่งมันจะเรียงลงมาแบบนี้เนอะ

@Composable
fun Greeting(name: String) {
    Surface(color = MaterialTheme.colors.surface) {
        Column{
            Text(text = "Hello,")
            Text(text = name)
        }
    }
}

เราจะเห็นว่า ความกว้างมันตาม content เนอะ แล้วอยากให้มันกว้างตามจอหล่ะ ทำยังไงดี?

เราใส่ modifier เพิ่มเข้าไปใน Surface โดยใส่ค่าของ modifer ไปว่า Modifier.fillMaxWidth() เมื่อนำไป preview เราจะเห็นว่ามันมีความกว้างเต็มจอแล้วอะนะ

เมื่อเราลองเล่นการกับเพิ่ม component อะไรใด ๆ ไปสักพักแล้ว เรามาลองทำเป็น list กันดีกว่า

ตัว item ของ list นี้ ก็คือเจ้า Greeting นั่นแหละ ส่วน parameter name ก็คือเราอยากให้ Text นี้แสดงชื่ออะไรเนอะ ซึ่งมัน dynamic อ่ะเนอะ ดังนั้นเราไปที่ onCreate แล้วสร้าง list ที่มีชื่อต่าง ๆ กันเถอะ

เราให้ list นี้ชื่อ names แล้ว list จะเป็นชื่อของอะไรดีนะ?

ทางนี้ก็เลยเอาชื่อโปรเจกต์ NFT มาใส่เลยจ้า เกร๋ ๆ เปลี่ยนบรรยากาศจากการใส่ชื่อไอดอลที่เรารักอ่ะเนอะ 555

val names = listOf("Stocker DAO", "Bittoon DAO", "Apetimism", "LonelyPop")

จากนั้นเราก็เพิ่ม LazyColumn เพื่อให้มันแสดงผลแบบ list แนวตั้งเนอะ โดยจะใส่ข้างใน Surface โดยครอบตัว Greeting อันเป็น item ของเราอีกทีนึง

วิธีการวนลูปเพื่อแสดงผลมี 2 ท่า

ท่าแรก: วน forEach ตามปกติ แล้วใช้ LazyListScope.item ตามไป

.

ท่าสอง: ใช้ LazyListScope มันจะสั้น และกระชับกว่าแบบแรก

.

ข้อความระวัง: ในส่วน preview และแอพจริง จะแสดงผลไม่ตรงกันนะ

เมื่อรันแอพแล้ว มีการอัพเดตค่าใด ๆ ใน compose แล้ว ตัวแอพจริงจะอัพเดตข้อมูลตามด้วย ไม่ต้อง build ใหม่ ซึ่งตรงนี้เราชอบมาก สะดวกสบายสุด ๆ ไปเล้ยยย

ผลที่ได้จะเป็นดังทวิตนี้เลย มหัศจรรย์พอที่จะขิงเดี๋ยวนั้นเลย 555

Codelab: BasicLayoutCodelab

มาถึงในส่วน codelab จริง ๆ ที่เราต้องเรียนรู้กันหล่ะนะทุกคน คนที่อ่านบล็อกก็สามารถทำตามไปด้วยได้นะ เพราะเป็นหนึ่งใน pathway แหละ ในส่วน Compose essentials จะมี codelab Basic Layout Codelab และ State in Jetpack Compose นะ

Jetpack Compose for Android Developers - Compose essentials
Take your first steps with Jetpack Compose and learn about composable functions, basic layouts and state, Material design, lists and animations.
https://developer.android.com/courses/pathways/jetpack-compose-for-android-developers-1

ซึ่งเราจะทำตามใน codelab นี้เลย

Basic layouts in Compose | Android Developers
In this codelab, you’ll learn how to implement real-world designs with the composables and modifiers that Compose provides out of the box.
https://developer.android.com/codelabs/jetpack-compose-layouts

Search bar - Modifiers

ก่อนอื่น เพิ่ม TextField (ก็คือ EditText นั่นแหละ คนอื่นเขาเรียก TextField กัน) สำหรับ SearchBar กันก่อนเลย

  • modifier เราต้องการความกว้างเต็ม fillMaxWidth() และสูง 56dp
  • color: สีของตัวหนังสือ
  • placeholder: อันนี้คือ hint เดิมแหละ สามารถนำ string ที่มาจากไฟล์ xml มาใช้ได้ และเข้าถึง DSL String Resource ได้โดยใช้ stringResource()
  • leadingIcon: icon ข้างหน้า EditText นี่แหละ

ไฟล์ string xml ยังคงใช้อยู่ และเข้าถึง DSL String Resource ได้โดย

@Composable
fun SearchBar(
    modifier: Modifier = Modifier
) {
    TextField(
        value = "",
        onValueChange = {},
        modifier = modifier
            .fillMaxWidth()
            .heightIn(min = 56.dp),
        colors = TextFieldDefaults.textFieldColors(
            backgroundColor = MaterialTheme.colors.surface
        ),
        placeholder = {
            Text(text = stringResource(id = R.string.placeholder_search))
        },
        leadingIcon = {
            Icon(
                imageVector = Icons.Rounded.Search,
                contentDescription = null
            )
        }
    )
}

ตอนเอาไป preview

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun SearchBarPreview() {
    MySootheTheme { SearchBar(Modifier.padding(8.dp)) }
}

Align your body - Alignment

ใส่ view ที่เป็น item ที่มีรูป Image กับ Text ในที่นี้จะใช้ Column ในการจัดเรียงของ โดยจะให้แสดงกึ่งกลางกัน โดย set horizontalAlignment = Alignment.CenterHorizontally

ในส่วนของ Image เรา set สามารถใส่รูป drawable ได้โดย painterResource() สามารถปรับ scale ได้ที่ contentScale แล้วก็ปรับขนาดและครอปวงกลมผ่าน Modifier โดยใส่ขนาดที่ size และครอปรูปร่างผ่าน clip() แล้วใส่เป็น circleShape

ส่วน Text อันนี้รับ string resource ไป และจัด padding ให้เรียบร้อย

@Composable
fun AlignYourBodyElement(
    @DrawableRes drawable: Int,
    @StringRes text: Int,
    modifier: Modifier = Modifier
) {
    Column(
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(id = drawable),
            contentScale = ContentScale.Crop,
            modifier = Modifier
                .size(88.dp)
                .clip(CircleShape),
            contentDescription = null
        )
        Text(
            text = stringResource(id = text),
            modifier = Modifier.padding(
                top = 24.dp,
                bottom = 8.dp
            )
        )
    }
}

ตอนเอาไป preview

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun AlignYourBodyElementPreview() {
    MySootheTheme {
        AlignYourBodyElement(
            drawable = R.drawable.ab1_inversions,
            text = R.string.ab1_inversions,
            modifier = Modifier.padding(8.dp)
        )
    }
}

Favorite collection card - Material Surface

เพิ่ม item ที่ชื่อว่า favorite collection card ซึ่งจะต้องใช้ Row ในการจัดเรียงของ ข้างในมี Image และ Text แต่มันติดขอบอะเนอะ

ขอบบน ครอบด้วย Surface เพื่อเพิ่มขอบของ Row ที่เราสร้างขึ้นเมื่อกี้

มาดูตัว preview กันก่อน เราใส่ Modifier ข้างในบอกว่าให้ padding 8dp นะ ดังนั้น content ข้างใน row นั้นจะห่างจากขอบ 8dp นั่นเอง ซึ่ง set จากข้างนอกของ rootview นั่นเอง

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun FavoriteCollectionCardPreview() {
    MySootheTheme {
        FavoriteCollectionCard(
            drawable = R.drawable.fc1_short_mantras,
            text = R.string.fc1_short_mantras,
            modifier = Modifier.padding(8.dp)
        )
    }
}

จากนั้นจัดแต่งให้แต่ละ View เรียบร้อย

การจัดกลาง อาจจะมีงง ๆ จากเมื่อกี้ ถ้าเป็นแนวนอน row คู่กับ vertical ส่วนแนวตั้ง column คู่กับ horizontal

อันนี้โค้ดของ View นี้

@Composable
fun FavoriteCollectionCard(
    @DrawableRes drawable: Int,
    @StringRes text: Int,
    modifier: Modifier = Modifier
) {
    Surface(
        shape = MaterialTheme.shapes.small,
        modifier = modifier
    ) {
        Row(
            modifier = Modifier.width(192.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Image(
                painter = painterResource(id = drawable),
                contentDescription = null,
                contentScale = ContentScale.Crop,
                modifier = Modifier.size(56.dp)
            )
            Text(
                text = stringResource(id = text),
                modifier = Modifier.padding(horizontal = 16.dp)
            )
        }
    }
}

Favorite collection card - Material Surface

อันนี้ก็เป็น View อีก item นึงเนอะ เดี๋ยวโค้ดมีเปลี่ยนอีก

โค้ดประมาณนี้

@Composable
fun FavoriteCollectionCard(
   @DrawableRes drawable: Int,
   @StringRes text: Int,
   modifier: Modifier = Modifier
) {
   Surface(
       shape = MaterialTheme.shapes.medium,
       color = MaterialTheme.colorScheme.surfaceVariant,
       modifier = modifier
   ) {
       Row(
           verticalAlignment = Alignment.CenterVertically,
           modifier = Modifier.width(255.dp)
       ) {
           Image(
               painter = painterResource(drawable),
               contentDescription = null,
               contentScale = ContentScale.Crop,
               modifier = Modifier.size(80.dp)
           )
           Text(
               text = stringResource(text),
               style = MaterialTheme.typography.titleMedium,
               modifier = Modifier.padding(horizontal = 16.dp)
           )
       }
   }
}

@Preview(showBackground = true, backgroundColor = 0xFFF5F0EE)
@Composable
fun FavoriteCollectionCardPreview() {
   MySootheTheme {
       FavoriteCollectionCard(
           text = R.string.fc2_nature_meditations,
           drawable = R.drawable.fc2_nature_meditations,
           modifier = Modifier.padding(8.dp)
       )
   }
}

Align your body row - Arrangements

แสดงแต่ละ item ลง list กัน เหมือนเราทำ RecyclerView กับ Adapter อ่ะ แต่ใน Jetpack Compose ไม่ต้องไปทำอะไรให้มันยุ่งยากแบบน้าน โดยใช้ LazyRow นั่นเอง (เพราะในที่นี้คือเป็น RecyclerView แนวนอนเนอะ)

การจัดวาง arrangements ในแนว Row

และในแนว Column

มาดูโค้ดจริงกัน ข้างใน LazyRow ใส่ items ก็คือ data ทั้งหมดที่เราต้องการนำมาแสดง และ item ข้างใน เราอยากเอา field นี้ไปแสดงตรงไหน ผลออกมาเป็น RecyclerView แบบนี้ โดยที่เราไม่ต้องไปทำอะไรยุ่งยาก ๆ แบบเดิมแล้ว สบายสุด ๆ

แต่ดูติด ๆ กันไปหน่อยเนอะ มาเพิ่ม spacing กัน ในส่วน horizontalArrangementให้ข้างในห่างกันสัก 8dp Arrangement.spacedBy(8.dp) (evenly: space/n+1)

เพิ่ม padding ด้านในของ item ได้ โดยให้ contentPadding เป็น PaddingValues(horizontal = 8.dp) ใส่แล้วไม่ถูกตัด ตอน scroll content

ส่วนการ fix height สามารถกำหนดที่ modifier ใหม่ ไม่ใช้ของ surface ได้

โค้ดสุดท้าย

fun AlignYourBodyRow(
    modifier: Modifier = Modifier
) {
    LazyRow(
        modifier = modifier,
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        contentPadding = PaddingValues(horizontal = 8.dp)
    ) {
        items(alignYourBodyData) { item ->
            AlignYourBodyElement(
                drawable = item.drawable,
                text = item.text
            )
        }
    }
}

Favorite collections grid - Lazy grids

สาระสำคัญ คือ เราสามารถแสดงเป็น grid โดยใช้ LazyHorizontalGrid ครอบ item ทั้งหมด และบอกให้แสดงแบบ 2 แถวได้ด้วยนะ GridCells.Fixed(2)

จากนั้นปรับ space โดยใช้ spacedBy นิดหน่อยเพื่อความสวยงาม

@Composable
fun FavoriteCollectionsGrid(
    modifier: Modifier = Modifier
) {
    LazyHorizontalGrid(
        rows = GridCells.Fixed(2),
        modifier = Modifier.height(120.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        contentPadding = PaddingValues(horizontal = 16.dp)
    ) {
        items(favoriteCollectionsData) { item ->
            FavoriteCollectionCard(
                drawable = item.drawable,
                text = item.text,
                modifier = Modifier.height(56.dp)
            )
        }
    }
}

Home section - Slot APIs

เอา text header และ list มารวมร่างกัน โดยการเอามาใส่ใน Column แบบนี้เลย เราไม่ต้องทำ Adapter ซ้อนกันสองอันแบบเมื่อก่อนแล้ว แค่เปลี่ยน list ไส้ในข้างในเอา โดยการ execute @Composable

@Composable
fun HomeSection(
    @StringRes text: Int,
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Column(
        modifier = modifier
    ) {
        Text(
            text = stringResource(id = text).uppercase(Locale.getDefault()),
            style = MaterialTheme.typography.h2,
            modifier = Modifier
                .paddingFromBaseline(top = 40.dp, bottom = 8.dp)
                .padding(horizontal = 16.dp)
        )
        content()
    }
}

ส่วนเส้นล่างสุดของ text โดยใช้ PaddingFromBaseline ที่ Modifier

ส่วนอีกสองอัน เวลาไม่ทัน เลยข้ามไปจ้า เดี๋ยวหิวข้าวเที่ยงกัน

MySoothe App - Scaffold

ประกอบชิ้นส่วนต่าง ๆ เรียงร้อยเข้าด้วยกัน ที่ MySootheApp() ใส่ SearchBar แล้วก็ HomeSection ตาม design แบบนี้เลย

จริง ๆ มีส่วน preview ให้อยู่แล้ว เลยรู้สึกว่าไปปรับแล้ว View อัพเดตตามด้วย

@Preview(widthDp = 360, heightDp = 640)
@Composable
fun MySoothePreview() {
    MySootheApp()
}

แล้ว wrap โดยการใส่ Surface ใส่สีพื้นหลัง แล้วเอาเต็มพื้นที่เลย

SearchBar ข้างบนใส่ padding เพิ่มหน่อย มันติดกันเกินไป ก็จะเป็นประมาณนี้

โค้ดทั้งหมดในส่วนนี้

@Composable
fun MySootheApp() {
    MySootheTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = Color(0xFFF0EAE2)
        ) {
            Column {
                SearchBar(modifier = Modifier.padding(16.dp))
                HomeSection(text = R.string.align_your_body) {
                    AlignYourBodyRow()
                }
                HomeSection(text = R.string.favorite_collections) {
                    FavoriteCollectionsGrid()
                }
            }
        }
    }
}

สิ่งที่อาจจะต้องเพิ่มเติม:

  • ปุ่ม search ให้ text และ icon align กัน
  • มันมีความเอียงอยู่ -> design แก้นํ้าหนักให้ตรง

ช่วงอาหารกลางวัน เม้ามอย มีแต่คนบอกว่า นึกว่าเราไปเป็น TA งานนี้ 55555555 คือตั้งแต่ codelab ปีที่แล้ว ก็ไม่ได้แตะ Compose อีกเลยหล่ะสิ

อาหารกลางวันถ่ายมาแต่ไม่ลงดีกว่า ภาพไม่สวย เพราะแต่กินไป เม้าไป กับน้องโป่ง กับมิ่ง (ใช่ไหมนะ) ที่เป็น TA งานนี้

ตอนกินข้าวเสร็จแล้ว บางคนลงไปซื้อกาแฟ พี่เอกเดินมาเม้า ไป ๆ มา ๆ ได้ตี้กาแฟ แต่นี่พลาดสั่งเย็นมา เพราะแอร์หนาวมาก ถือว่าแก้ง่วงได้บ้างแหละเนอะ แล้วที่ฮาคือได้สั่งร้าน class cafe ซึ่งนี่มี NFT ของ Ape Meta Error Lab ที่สามารถใช้สิทธิ์กาแฟฟรี 1 แก้วอ่า แบบได้กินร้านนี้เฉยเลย ถึงแม้ว่าจะสั่งชาเขียวก็ตาม

แล้วก็มีคำถามเรื่อง ConstraintLayout สามารถไปอ่านเพิ่มเติมได้ที่นี่จ้า

ConstraintLayout in Compose | Jetpack Compose | Android Developers
https://developer.android.com/jetpack/compose/layouts/constraintlayout

บล็อกนี้ก็ได้จบเนื้อหาช่วงเช้ากันไปแล้ว ส่วนเนื้อหาช่วงบ่ายแบบแน่น ๆ จุก ๆ ที่คนสอนและคนเรียนร้องขอชีวิต สามารถอ่านได้ที่นี่

(บล็อกจริงจะอยู่ตรงนี้นะ)


ติดตามข่าวสารตามช่องทางต่าง ๆ และทุกช่องทางโดเนทกันไว้ที่นี่เลย แนะนำให้ใช้ tipme เน้อ ผ่าน promptpay ได้เต็มไม่หักจ้า

ติดตามข่าวสารแบบไว ๆ มาที่ Twitter เลย บางอย่างไม่มีในบล็อก และหน้าเพจนะ

Tags

Minseo Chayabanjonglerd

I am a full-time Android Developer and part-time contributor with developer community and web3 world, who believe people have hard skills and soft skills to up-skill to da moon.