บันทึกการ Redesign แอพอ่านบล็อกของตัวเอง ให้เป็น Material Design
เนื่องจากเราเองก็อยากลอง redesign แอพอ่านบล็อกตัวเอง จนย้ายบล็อกมาที่ Ghost แล้ว จึงต้อง Redesign แอพอ่านบล็อก MikkiPastel ใหม่
ความเดิมตอนที่แล้ว
ทำ Color Index ใหม่
สีแดงเดิมทีเป็นสีแดงของ Android นำมาปรับเป็นสีที่เรียกว่า Maguro Red (#B71D25) นั่นเองงงและสีรองลงมาสืบเนื่องจากสีพื้นหลังเดิมของบล็อกนี้เป็นสีคล้ายๆแซลม่อนสุก จึงนำมาเปลี่ยนเป็น Light Pink Salmon (#FF9999) แทน
ปรับโปรเจกในแอพใหม่
ตัวแอพบล็อกเดิมนั้นใช้ blogspot API เขียนด้วยภาษา Java ดังนั้นเราจะเปลี่ยนมาเป็น API ที่เราเขียนเองโดยข้อมูลทั้งหมดจะเก็บไว้ใน Cloud Firestore และเขียนด้วยภาษา Kotlin ซึ่งพยายามปรับไปเรื่อยๆ โดยจะทำความเข้าใจและเริ่มใช้ MVVM แทนของเดิมที่เป็น MVP เพื่อให้สามารถเล่นอะไรใหม่ๆได้ >> ยังไม่เสร็จจ้า เดี๋ยวเล่าแยกถ้าเสร็จแล้ว
เพิ่มลูกเล่นด้วย Lottie
แน่นอนว่าเราเองขี้เกียจทำ view แบบไม่ค่อยสวย เลยไปหา Lottie ต่างๆที่เป็น open source จาก https://lottiefiles.com/ เพื่อเอาไปใช้ในหน้าต่างๆ โดยเริ่มจากในแอพก่อน และตามด้วยในเว็บ
ตอน loading ใช้เจ้าไดโนเสาร์ตัวนี้ซึ่งค่อนข้าง made sense ดี
ตอน error ใช้ตัวนี้ แต่เขียน text กำกับไว้ด้านล่าง พร้อมปุ่ม re-loading
ตอนโหลดก็คิดว่าอันนี้ไว้ด้านบนก็ไม่เลวนะ
ทดลองเรียกใช้ Ghost API เพื่อดึงบล็อกของเราขึ้นมาแสดง
ตอนนี้เลยหาวิธีสักหน่อยว่าเขาทำกันยังไงบ้างนะ ในการเรียก API จาก Ghost ว่ามีอะไรบ้าง
ก่อนอื่นไปที่ Settings > Integration แล้วก็สร้าง CUSTOM INTEGRATIONS ขึ้นมา เราก็จะได้ Content API Key, Admin API Key, และ API URL
การเรียกใช้งานเบื้องต้น เข้าไปดูได้ที่เว็บไซต์นี้เลย
ก่อนอื่น ทดลองเรียกตัวนี้ก่อน ใน Postman ก็ได้เนอะ เป็น method get
"https://demo.ghost.io/ghost/api/v3/content/posts/?key=22444f78447824223cefc48062"
จากนั้นเรามาลองใช้เองบ้างเนอะ มันจะเป็น format ตามนี้
{API_URL}/ghost/api/v3/content/posts?key={CONTENT_API_KEY}
อันนี้เอาไว้เรียก post เนอะ แต่ด้วยความที่มันพ่น field ออกมาเยอะจนเราแบ่บบ เออมันเยอะไปนะ แถมไม่มี tag อีก อ้าววว
ดังนั้นเราใส่ parameter เพิ่ม โดยเพิ่ม tags เข้ามา include=tags
และเอาเฉพาะแค่ไม่กี่ field ที่ใช้ในแอพ คือ เอาแค่ชื่อบล็อก คำโปรย รูป cover เวลาที่ publish แบบนี้ fields=title,url,feature_image,custom_excerpt,published_at
ผลที่ได้คือมันจะมีเฉพาะ field ที่เราต้องการหล่ะ
ใน API จะส่งบล็อกมา 15 blog ต่อครั้ง ดังนั้นถ้าเราจะเรียกหน้าถัดไป หรือหน้าอื่นๆ ให้ใส่อันนี้เข้าไปด้วย page=2
สิ่งหนึ่งที่น่าสนใจก็คือ ตรงท้ายจะมีก้อนนึงที่ชื่อว่า meta
และมี pagination
ซึ่งบอกเลยว่าดีมากๆ เวลาเราทำ lazy load จะได้รู้ว่าเอ้ออออโหลดหมดยังอ่ะ จะได้ไม่ต้องเสีย request ในเรียก API แล้วไม่ได้อะไรกลับมาเนอะ
แต่ละอันบอกอะไรเราบ้างหล่ะ
page
ตอนนี้อยู่ที่หน้าเท่าไหร่limit
หน้านี้มี limit กี่ itempages
ทั้งหมดมีกี่หน้าtotal
ทั้งหมดนี้มีกี่ item กันนะnext
หน้าถัดไปคือหน้าเท่าไหร่prev
หน้าก่อนหน้าคือหน้าเท่าไหร่ ในที่นี้คือ null ก็คือไม่มีหน้าก่อนหน้านี้นั่นเอง
นอกจากเรียกบล็อกแล้ว เรามีการเรียก tag ทั้งหมดในบล็อก จะเป็นประมาณนี้
{API_URL}/ghost/api/v3/content/tags?key={CONTENT_API_KEY}
ตัว field จะเหมือนใน post อ่ะ ไม่มีอะไรมาก
Redesign หน้าแอพหล่ะนะ
ปีที่แล้วเราเองก็ทำการ research เรื่อง Material Design จนเราได้ตัว item ที่เป็น CardView แบบนี้ ที่สามารถกดการ์ดไปอ่านบทความ หรือกดที่ Chip เพื่อดู content ใน tag นั้นๆได้ ซึ่งเรา adapt จาก Material Design มาด้วยหล่ะ
คอนเซปที่พยายามใช้คือ Single Activity ซึ่งก็นึกถึงบล็อกนี้ เจอเป็นอันดับหนึ่งด้วยจ้า
ดังนั้นเราจึงพยายามยุบให้เหลือ Activity เดียว แต่ด้วยความที่ตัว API ของการเรียกบล็อกรวมกับแยก tag ไม่ต่างกันมาก จึงยุบเหลือ Fragment เดียวเลย แล้วเราจะทำยังไงต่อน้าา?
เพิ่ม Dropdown list ด้วย AutoCompleteTextView
แน่นอนกด tag หรือถ้าเราเลือก tag แล้วควรจะ refresh list ใหม่
เลยคิดว่าอาจจะเป็นอะไรสักอย่างกดแล้วเลือกจาก Bottom Sheet แต่พอเราไปเปิดในหน้าเว็บของ Material Design เจอสิ่งที่เรียกว่า dropdown menu นั่นเอง
ใน document บอกวิธีใส่และ implement ไว้แล้วจ้า แต่เรามาอธิบายเพิ่มดีกว่า เดี๋ยวงงๆเนอะ
ก่อนอื่นไปที่ layout ที่เราต้องการ ใส่ไปแบบนี้
อธิบายคร่าวๆ คือ
- ใน
fragment_main.xml
มี parent คือTextInputLayout
อันนี้ก็เป็น layout ด้านบนTextField
ต่างๆ เราใส่ label และ icon ด้านซ้ายเข้าไปด้วย ส่วนAutoCompleteTextView
คือมันจะเป็น auto complete แต่เราไม่อยากให้พิมพ์อะไรลงไป อยากให้เอาไปเลือกว่าอยากอ่าน blog ที่ติด tag ไหน เลยทำให้มันจะไม่เป็นกรอบสีของcolorPrimary
นะ - ส่วน
item_hashtag.xml
เป็น item view ใน adapter ที่เราไม่ต้องสร้าง class adapter เอง เพียงยัด layout เข้าไป เดี๋ยวอธิบายต่อ
จากนั้นในโค้ดของเรา MainFragment.kt
เราทำการ call API ที่เรียก tag ของ Ghost API แล้วเอาชื่อ tag ที่ได้ไปใส่ใน dropdown list มาดูตัวอย่างการใช้กันก่อน
val items = listOf("Material", "Design", "Components", "Android")
val adapter = ArrayAdapter(requireContext(), R.layout.list_item, items)
(textField.editText as? AutoCompleteTextView)?.setAdapter(adapter)
เรามี list ของของสิ่งหนึ่ง จากนั้นสร้าง ArrayAdapter
แล้วเอาเจ้า layout ของ item ใส่ลงไปพร้อม list จากนั้นทำการ setAdapter
ดังนั้นของเราจะเป็นประมาณนี้
ถ้าเรากด Chip
แล้วให้เจ้า AutoCompleteTextView
เปลี่ยนตัวหนังสือเป็น hashtag ตัวนั้น ทำอย่างไรดีนะ ง่ายๆเลยแค่นี้เอง
dropdownList.setText(hashtag, false)
จริงๆถ้าใส่ setText(text)
อย่างเดียว มันจะมีความบัคตรงที่ พอกด dropdown จะเห็นตัวมันเองอย่างเดียว
ดังนั้นจึงใช้ setText(text, filter)
ที่รองรับ API level 17 ขึ้นไปนะ และใส่ค่าเป็น false เพื่อให้แสดง dropdown list ทั้งหมดลงมาแบบนี้
แล้วถ้าตอนกด dropdown หล่ะ เหมือนมันเลือกได้ แต่ตัวหนังสือไม่เปลี่ยน ใช้ setOnItemClickListener
เข้าช่วย แบบนี้
dropdownListsetOnItemClickListener { adapterView, view, position, id ->
dropdownList.setText(yourList[position], false)
//make something happen
}
หลังจากกด dropdown list หรือจิ้มที่ Chip
เราจะให้เรียก API เพื่อ get blog เฉพาะ tag นั้นๆขึ้นมาให้
ต่อมาเรามาลองเพิ่มเจ้า Collapsing Toolbars กันอยู่จ้า จริงๆมันก็แอบมีความซํ้าซ้อนอ่ะ เพราะเราเองก็เห็นว่าบล็อกเราตอนนี้แสดงตาม tag อะไรอยู่ แล้วมี cover ด้านบนมาอีกด้วย ฮ่าๆ
ก่อนอื่น set theme ให้เป็น NoActionBar
ก่อนเลยจ้า แต่ดันมีแต่ Light นี่สิ
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
ในนี้อ่านแล้วจะงงๆนิดนึงเนอะ
เริ่มจาก layout กันก่อนเลย ตัว parent จะเป็น CoordinatorLayout
ที่มีลูกเป็น AppBarLayout
อันคือ app bar ด้านบน และ LinearLayout
ที่เป็นส่วน filter tag และแสดง content นั่นเอง
และใน AppBarLayout
มีอะไรเป็นลูกบ้างหล่ะ? มี CollapsingToolbarLayout
เป็นตัวลูก ที่มี 2 ส่วน ก็คือ ส่วนที่แสดงแบบใหญ่ๆ กับส่วน toolbar
คือหลังจากเลื่อนลงมา ประมาณนี้
และอันนี้คือโค้ด layout เนอะ เดี๋ยวเรามาอธิบายกัน
- ใน layout หน้านี้ มี parent เป็น
CoordinatorLayout
ที่มีลูก 2 ตัวคือAppBarLayout
และLinearLayout
- ตัว
LinearLayout
อันนี้ ก็คือตัวFragment
ที่เราใส่เข้าไป ในการเลือก tag และแสดง content นั่นเอง โดยเราบอกให้มันอยู่ด้ายล่าง appbar นะ โดย set เข้าไปดังนี้
app:layout_behavior="@string/appbar_scrolling_view_behavior"
- ตัว
AppBarLayout
ก็จะมี layout ลูกเป็นCollapsingToolbar
ที่ข้างในจะแบ่งอีก 2 ส่วน ก็คือFrameLayout
ที่เอาไว้แสดง cover และ text ของ tag นั้นๆ และToolbar
ไว้เพื่อ cover ด้านบนนั้นถูก collap ไปแล้ว ก็ให้แสดงขึ้นมา และเราไม่อยากให้มันหายไปตอนเลื่อนดู content ดังนั้นเราจะใส่เพิ่มไปว่า
app:layout_collapseMode="pin"
- ใน
FrameLayout
นั้น เราอยากให้ตัวImageView
มันเป็นฝ่าย collapse มากกว่าตัว layout แม่ เพราะลองไปใส่ที่FrameLayout
พบว่ามันแปลกๆ มันไม่สวย เวลาที่ย่อตัว cover ออกแล้วจะเปลี่ยนเป็นToolbar
ที่มี text อ่ะ โดยใส่ไปดังนี้
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5"
เจ้า layout_collapseParallaxMultiplier
คือเราสามารถ set ให้เจ้า layout ที่ว่านั้น ถูก collapse ไปตอนไหน ซึ่งเราใส่ไป 0.5 ก็หมายถึง ให้มันเริ่มหดๆหายๆ เมื่อเลื่อน layout ชิ้นนี้ไปประมาณ 50% ซึ่งค่าสามารถปรับแต่งตามความสวยงามของเราเองได้เลยจ้า
- เราสามารถ implement พวก style ต่างๆในโค้ดได้ด้วย เช่น เราอยากให้ตอนมันหุบลงมามี title เป็นสีขาว ก็สามารถ set ได้แบบนี้
collapsingToolbar.setCollapsedTitleTextColor(
ContextCompat.getColor(context!!, android.R.color.white)
)
ผลที่ได้จะเป็นแบบนี้จ้า
สำหรับ CoordinatorLayout
สามารถอ่านเพิ่มเติมได้ที่
ปุ่มสามจุดแนวตั้งสียังแปลกๆอยู่ ให้ set style เป็น drawable จุดสามจุดสีขาวใหม่ตามนี้เลย
ส่วนการกดที่ CardView
แล้วไป Chrome Custom Tab จะยกไปที่บล็อกนี้แล้วกัน แบบปรับปรุงบล็อกใหม่ซะเลย
ใน app version beta น่าจะเท่านี้แหละ
ปล. จริงๆ TWA ก็น่าสนใจนะ แค่ของ Ghost เนี่ย ไม่รองรับ PWA อ่ะ ฮืออออออ คือมันต้อง custom เพิ่มอ่า
ตัว github ของโปรเจกนี้จ้า
จบแล้ว ฝากร้านสิ รอไร