ในการพัฒนาแอปบนแอนดรอยด์ก็อาจจะต้องทำให้ TextView สามารถกดเพื่อทำอะไรบางอย่างได้ และในบางครั้งก็อาจจะต้องทำให้ผู้ใช้กดที่บางส่วนของข้อความใน TextView ได้เท่านั้นด้วย
ยกตัวอย่างเช่นข้อความแบบนี้
ดังนั้นการใช้ setOnClickListener
จึงไม่เหมาะกับกรณีนี้ซักเท่าไร เนื่องจากต้องการให้กดได้แค่เฉพาะข้อความที่ต้องการเท่านั้น ไม่ใช่ข้อความทั้งหมด จึงต้องใช้ SpannableString เข้ามาช่วยแก้ปัญหาดังกล่าวแทน
สำหรับข้อความบางส่วนที่กดได้ เจ้าของบล็อกจะเรียกว่า Link Text
เกี่ยวกับ SpannableString
SpannableString เป็นคลาสที่ช่วยให้นักพัฒนาใส่ Markup ให้กับข้อความเพื่อนำไปใช้กับ UI อย่าง TextView ได้ และจะเรียก Markup ใด ๆ ที่กำหนดให้กับข้อความใน SpannableString ว่า Span
ซึ่ง Span ที่มีให้ใช้งานจะมีหลายแบบ แต่ในกรณีนี้เจ้าของบล็อกจะใช้ ClickableSpan เพื่อทำ Link Text นั่นเอง
และเพื่อให้นำไปใช้งานได้ง่าย เจ้าของบล็อกจึงไม่ได้ใช้งาน ClickableSpan โดยตรง แต่จะนำไปสร้างเป็นคลาสขึ้นมาเองเพื่อให้ใช้งานได้ง่ายขึ้น
class TextClickableSpan(
private val url: String?,
val onTextClick: (view: View, url: String?) -> Unit
) : ClickableSpan() {
override fun onClick(view: View) {
onTextClick(view, url)
}
}
จากตัวอย่างของ TextClickableSpan จะเห็นว่ามีการรับ Parameter ที่ชื่อว่า url
เป็น String เข้ามาด้วย เพื่อช่วยให้เขียนโค้ดได้สะดวกขึ้นเท่านั้น
ผู้ที่หลงเข้ามาอ่านสามารถเปลี่ยน URL String ให้เป็นข้อมูลแบบอื่นได้ตามใจชอบ
และจากตัวอย่างคำว่า "All you want is over here" ถ้าใช้กับ TextClickableSpan ที่เจ้าของบล็อกสร้างขึ้นมา ก็จะใช้คำสั่งประมาณนี้
val text = "All you want is over here"
val url = "https://google.com"
val start = 16
val end = 24
val onTextClickSpan = TextClickableSpan(url) { _, url ->
// Do something
}
val spannableString = SpannableString(text).apply {
setSpan(onTextClickSpan, start, end, 0)
}
val textView: TextView = /* ... */
textView.text = spannableString
โดย Index ที่ 16 ถึง 24 คือตำแหน่งของคำว่า "over here" นั่นเอง
อย่าลืมกำหนด Movement Method ให้กับ TextView ด้วย
เดิมที TextView ไม่ได้กำหนดให้รองรับ Link Text จึงต้องเพิ่มคำสั่งให้กับ TextView ก่อนเสมอ
val textView: TextView = /* ... */
textView.movementMethod = LinkMovementMethod()
และถ้าอยากเปลี่ยนสีเฉพาะข้อความดังกล่าว ก็ให้ใช้คำสั่ง setLinkTextColor
ได้เลย
Link Text หลายชุดในข้อความเดียวกัน
เพื่อให้การทำ Link Text นั้นยืดหยุ่นต่อการนำไปใช้งาน นักพัฒนาควรแยกข้อความที่ต้องการทำ Link Text ออกจากข้อความหลัก แล้วแทนที่ด้วย Keyword เพื่อใช้ในการคำนวณในภายหลัง
โดยแนะนำให้สร้าง Data Class เพื่อเก็บข้อมูลสำหรับคำที่ต้องการทำ Link Text
data class LinkText(
val keyword: String,
val text: String,
val url: String
)
สมมติว่าข้อความและ Link Text ที่ต้องการแทรกเข้าไปในข้อความมีลักษณะแบบนี้
val message = "See {link1} and {link2} for more information"
val linkTexts = listOf(
LinkText("{link1}", "Google", "https://google.com"),
LinkText("{link2}", "Akexorcist", "https://akexorcist.dev")
)
เพื่อให้ข้อมูลนำไปคำนวณและใช้งานได้ง่ายขึ้น เจ้าของบล็อกจึงสร้าง Data Class เพิ่มขึ้นมาอีก 2 ตัว
data class MergeSpanValue(
val message: String,
val spanValues: List<SpanValue>
)
data class SpanValue(
val start: Int,
val end: Int,
val url: String
)
และใช้วิธีการคำนวณเพื่อเอาค่าใน Link Text ไปแทนที่ข้อความใน message
แล้วคำนวณหา start
กับ end
เพื่อใช้ตอนกำหนด Span ให้กับ SpannableString แบบนี้
// Initiate
val message: String = /* ... */
val linkTexts: List<LinkText> = /* ... */
// Calculate
val initial = MergeSpanValue(
message = message,
spanValues = listOf()
)
val result = linkTexts
.sortedBy { linkText -> message.indexOf(linkText.keyword) }
.fold(initial) { acc: MergeSpanValue, linkText: LinkText ->
val start = acc.message.indexOf(linkText.keyword)
val end = start + linkText.text.count()
val replacedMessage = acc.message.replace(linkText.keyword, linkText.text)
acc.copy(
message = replacedMessage,
spanValues = acc.spanValues + SpanValue(
start = start,
end = end,
url = linkText.url
)
)
}
val spannableString = SpannableString(result.message).apply {
result.spanValues.forEach { value ->
val onClick = TextClickableSpan(value.url) { _, url ->
// Do something
}
setSpan(onClick, value.start, value.end, 0)
}
}
// Assign
val textView: TextView = /* ... */
textView.movementMethod = LinkMovementMethod()
textView.text = spannableString
เพียงเท่านี้ก็จะได้ Link Text หลายตัวในข้อความเดียวกันแล้ว
แน่นอนว่าวิธีการคำนวณดังกล่าวจะต้องกำหนด Keyword แต่ละตัวให้ไม่เหมือนกัน และ Keyword ที่อยู่ในข้อความก็ต้องห้ามซ้ำกันด้วย ดังนั้นถ้าต้องการนำไปใช้ในลักษณะที่แตกต่างกันออกไป ก็สามารถแก้ไขวิธีการคำนวณได้ตามใจชอบเลย