แก้ปัญหาการแสดงตัวหนังสือบน TextView ให้มีหลากหลายสไตล์ด้วย KTX

Minseo Chayabanjonglerd
Black Lens
Published in
4 min readNov 20, 2019

--

เคยไหมที่รู้สึกเบื่อทุกที ที่ต้องมานั่งทำ style แตกต่างกัน ใน TextView เดียวกัน

แถมโค้ดเก่าๆในโปรเจก ใช้ HTML แถมบอกด้วยว่า deprecated code สำหรับเครื่องที่ตํ่ากว่า Android N ไปแล้วจ้า บั้ยย

เอาเข้าจริงๆก็ไปไม่เป็นเลยนะว่าแก้ยังไงดีนะ ให้ดูดีขึ้นมาหน่อย

เหตุการณ์ก่อนหน้านี้

เมื่อเราจะ handle TextView สักตัวหนึ่ง ที่มีหลากหลาย style ใน TextView หนึ่งตัวนี้ วิธีแบบ basic ที่ทำกันคือ ใช้ HTML ในการ set text style ต่างๆ และนำไป set text ที่ TextView อีกที

ตัวอย่างในที่นี้ คือนึกอะไรไม่ออก อ่ะอันนี้แล้วกัน

https://www.udacity.com/school-of-programming

เราขออนุญาตตัดตอนนิดนึงตรงประกาศตัวแปร 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 นั่นเอง

แน่นอนเราต้องอ่านบล็อกอื่นๆเพื่อทำความเข้าใจกันก่อน

วิธีการทำ ประกาศตัวแปร สร้างเจ้า 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 ได้

ก่อนอื่น ไปเพิ่มใน 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 ขึ้นมา เผื่อเอาไปดูกันยาวๆจ้า

สุดท้ายฝากร้านกันสักนิด ฝากเพจด้วยนะจ๊ะ

--

--

Minseo Chayabanjonglerd
Black Lens

Android Developer | Content Creator AKA. MikkiPastel | Web2 & Web3 Contributor