แก้ปัญหาการแสดงตัวหนังสือบน TextView ให้มีหลากหลายสไตล์ด้วย KTX
เคยไหมที่รู้สึกเบื่อทุกที ที่ต้องมานั่งทำ style แตกต่างกัน ใน TextView เดียวกัน
แถมโค้ดเก่าๆในโปรเจก ใช้ HTML แถมบอกด้วยว่า deprecated code สำหรับเครื่องที่ตํ่ากว่า Android N ไปแล้วจ้า บั้ยย
เอาเข้าจริงๆก็ไปไม่เป็นเลยนะว่าแก้ยังไงดีนะ ให้ดูดีขึ้นมาหน่อย
เหตุการณ์ก่อนหน้านี้
เมื่อเราจะ handle TextView สักตัวหนึ่ง ที่มีหลากหลาย style ใน TextView หนึ่งตัวนี้ วิธีแบบ basic ที่ทำกันคือ ใช้ HTML ในการ set text style ต่างๆ และนำไป set text ที่ TextView อีกที
ตัวอย่างในที่นี้ คือนึกอะไรไม่ออก อ่ะอันนี้แล้วกัน
เราขออนุญาตตัดตอนนิดนึงตรงประกาศตัวแปร sampleText นะ
private val sampleText = "Android Development, Databases, Android Networking Libraries, Crash Analysis, Multilingual Support, Android Libraries, Gradle, APIs"
ถ้าเรา handle ด้วย HTML จะเป็นดังนี้
val text = "<font color='#657482'>Concepts covered:</font> $sampleText"
textViewSample.text = Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY)
แต่ๆๆๆ เจอสิ่งนี้จ้า
แน่นอนว่าเราต้อง implement เผื่อ API level ที่น้อยลงมาด้วย แบบนี้
แน่นอนว่ามี function ที่ deprecated ให้เราดูต่างหน้า ตามที่กล่าวไปข้างต้น ซึ่งผลลัพธ์มันก็คือ
ถ้าอยากใส่ตัวหนาใน HTML ต้องทำยังไง อ่ะข้อดีข้อเสียอย่างนึงคือ เรื่อง ภาษา HTML นี่แหละ อาจจะดูเรียนรู้และทบทวนเกี่ยวกับ HTML tag เนอะ ซึ่งการใส่ tag ตัวหนาคือ <b></b> นั่นเอง
เกี่ยวกับการใส่ HTML tag ขอจบตรงนี้แล้วกันเนอะ ไปวิธีต่อไปดีกว่า
ลองใช้ SpannableString สิตะเอง
ด้วยความที่เห็นโค้ดที่ใช้ HTML แล้วรำคาญโค้ดที่ ต้องแยก API level และ deprecated เราต้องหา solution ที่ handle ครั้งเดียว ได้ทุก API level เลยได้วิธีที่นึกชื่อคร่าวๆแล้วถามอากู๋ นั่นคือ SpannableString นั่นเอง
แน่นอนเราต้องอ่านบล็อกอื่นๆเพื่อทำความเข้าใจกันก่อน
SpannableStringBuilder | Android Developers
AccessibilityService.MagnificationController.OnMagnificationChangedListenerdeveloper.android.com
วิธีการทำ ประกาศตัวแปร สร้างเจ้า SpannableString ขึ้นมาก่อนจ้า ซึ่ง parameter ของมันก็คือ text ที่เราต้องการทำการใส่หลายๆ style ใน TextView เดียวนั่นเอง
val text = "Concepts covered:$sampleText"
val spannable = SpannableString(text)
จากนั้นเราก็ทำการ setSpan จ้า การทำงานของมันก็คือ เลือกก้อน text ที่ต้องการ ว่าให้มันเป็น style แบบไหน เช่น เราต้องการคำว่า “Concepts covered” เป็นสีเทาตัวหนา ซึ่งในตัวอย่างนี้มันเริ่มที่อักษรตัวที่ 0 ไปจนถึงตัวที่ 16 แต่มันเริ่มที่ 0 ดังนั้นจะจบที่ 15 จ้า ในความเป็นจริงเราก็จะไม่นับเองเนอะ ใช้ length ในการช่วยนับให้เราจ้า
spannable.setSpan(StyleSpan(Typeface.BOLD),
0,
“Concepts covered”.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
parameter แรก ให้เราใส่ style ที่เราต้องการลงไป parameter ต่อมาใส่ start และ end สำหรับการ set style นั้นๆ
จากนั้นเรานำเจ้า spannable ไปให้ TextView ของเรา set text เพื่อนำมาแสดงผลให้เราดู
textViewSample.text = spannable
ผลที่ได้คือ
จากนั้นใส่สีให้มันเพิ่มสักหน่อย
spannable.setSpan(ForegroundColorSpan(Color.parseColor("#657482")),
0,
“Concepts covered”.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
ถ้าเพิ่มขนาดตัวให้ใหญ่ขึ้นหล่ะ
spannable.setSpan(RelativeSizeSpan(1.5f),
0,
“Concepts covered”.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
สรุปว่า ถ้ามีหลากหลาย style ใน wording เดียวขนาดนี้ โค้ดนี่เอาจริงๆเยอะพอตัวอ่ะ ไม่ไหวๆ
เฮ้ย ใน Android Jetpack มันก็มีเหมือนกันนี่นา
จนมาระลึกได้เรื่องนึง session ที่ชื่อว่า hello KTX ในงาน Google I/O Extended 2018 พูดถึงเรื่อง Spannable Text นี่นา
แต่เจ้า buildSpannableString ไม่มีใน Document ใน Android แล้วนะเออ
เราลองไปตามหาดูพบว่ามันอยู่ใน Android KTX ซึ่งมันอยู่ใน Android Jetpack อีกที มี function หนึ่งที่ช่วยเราในการทำ Spannable Text ได้
androidx.core.text | Android Developers
MediaSessionCompat.OnActiveChangeListenerdeveloper.android.com
ก่อนอื่น ไปเพิ่มใน dependency ก่อนนะ
implementation 'androidx.core:core-ktx:1.1.0'
สิ่งที่เราสามารถ set ได้เหมือนวิธีที่แล้วเลย
ก่อนอื่นสร้างเจ้า SpannableStringBuilder() เปล่าๆขึ้นมาก่อน
val spannable = SpannableStringBuilder()
พวก Text Style ต่างๆไม่ว่าจะเป็นตัวหนา ตัวเอียง ขีดเส้นใต้ วิธีการใช้งานก็ง่ายๆเลย แบบนี้
spannable.bold {}
โดยใส้ในมันคือ builderAction นั่นเอง ในที่นี้เราอยากให้คำนี้ออกมาเป็นตัวหนา ก็ใส่ไปแบบนี้
spannable.bold { append(“Concepts covered”) }
ส่วนการเพิ่มขนาดตัวอักษรเจ้า parameter แรก คือ จำนวนเท่าที่คูณกับขนาดตัวอักษรเดิม และ parameter ถัดมาคือเจ้า builderAction นั่นเอง คุ้นๆไหม
ถ้าอยากให้ตัวอักษรใหญ่สักนิดนึงก็ใส่ไปแบบนี้
spannable.scale(1.5f) { append(“Concepts covered”) }
การใส่สีตัวอักษร เราจะใช้ color ในการเปลี่ยนสีตัวอักษรของเรา ซึ่ง parameter แรกเขาต้องการสีที่เราจะเปลี่ยน ส่วน parameter ถัดมาคือเจ้า builderAction
ถ้าเราอยากให้คำนี้เป็นสีเทาๆหน่อย ก็ใส่ไปแบบนี้
spannable.color(Color.parseColor("#657482")) {
append("Concepts covered")
}
อยากได้ทั้งสองอย่างหรือหลายอย่างทำไงดี ก็เอามาซ้อนๆกันตามความเหมาะสม จะได้ประมาณนี้
ผลลัพธ์ที่ได้
เท่าที่ลองคือง่ายกว่าวิธีที่แล้วเนอะ ไม่ต้องมานั่งนับ range นับอะไรให้งงตอนอ่านโค้ด แค่ set ไปว่า จะเอา wording นี้เป็นแบบไหน แค่นั้นเอง
และโค้ดดูสะอาดตาเพราะโค้ดสั้นลง และการทำงานหล่ะ จะเป็นยังไงกันนะ 🤔
ทุกตัวเรียก function inSpans หมดเลยแหะ ว่าแต่ function นี้ทำอะไรกันนะ
มิน่าทำไมทำงานเหมือนกัน 555555555 เอาจริงๆคือทำให้ชาวเดฟทำงานกันง่ายขึ้นแหละเนอะ แฮ้ปปี้ๆ
สุดท้ายทั้งหมดทั้งมวล เราสร้าง repo ใน github ขึ้นมา เผื่อเอาไปดูกันยาวๆจ้า
สุดท้ายฝากร้านกันสักนิด ฝากเพจด้วยนะจ๊ะ