เข้าค่าย Compose Camp ชาว Android Developer กัน (ช่วงเช้า)
และแล้วในที่สุด ในไทยเรามีกิจกรรมนี้แล้วนะทุกคน หลังจากที่เราลอยคอ เอ้ยย รอคอยกันมานาน สำหรับค่าย 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)
.
ตัวคอร์สที่เรียนนั้น ก็จะอยู่ในนี้แหละ สามารถไปทำเองที่บ้านได้ แต่เรากลัวขี้เกียจ เลยไปเรียนจริงดีกว่า เดี๋ยวลืมทำ ขนาดทำสรุปยังดองเลยจ้า เพราะงานแอบเยอะนิดนึง แล้วมันต้องใช้เวลาเนอะ
เมื่อถึงเวลาสิบโมงเช้าที่นัดกันไว้ มีผู้คนมากมายรอเรียนกันอยู่แล้วแหละเนอะ 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 นะ
ซึ่งเราจะทำตามใน codelab นี้เลย
Search bar - Modifiers
ก่อนอื่น เพิ่ม TextField (ก็คือ EditText นั่นแหละ คนอื่นเขาเรียก TextField กัน) สำหรับ SearchBar กันก่อนเลย
modifier
เราต้องการความกว้างเต็มfillMaxWidth()
และสูง 56dpcolor
: สีของตัวหนังสือ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 สามารถไปอ่านเพิ่มเติมได้ที่นี่จ้า
บล็อกนี้ก็ได้จบเนื้อหาช่วงเช้ากันไปแล้ว ส่วนเนื้อหาช่วงบ่ายแบบแน่น ๆ จุก ๆ ที่คนสอนและคนเรียนร้องขอชีวิต สามารถอ่านได้ที่นี่
(บล็อกจริงจะอยู่ตรงนี้นะ)
ติดตามข่าวสารตามช่องทางต่าง ๆ และทุกช่องทางโดเนทกันไว้ที่นี่เลย แนะนำให้ใช้ tipme เน้อ ผ่าน promptpay ได้เต็มไม่หักจ้า
ติดตามข่าวสารแบบไว ๆ มาที่ Twitter เลย บางอย่างไม่มีในบล็อก และหน้าเพจนะ