SpannableString คือคลาส Android SDK จากแพ็คเกจ android.text ที่ช่วยให้คุณสามารถใช้หลายสไตล์กับส่วนต่างๆ ของสตริงข้อความเดียวกันใน TextView ได้ ซึ่งแตกต่างจากมาร์กอัป HTML SpannableString ทำงานในระดับของอ็อบเจ็กต์ Span ควบคุมการแสดงผลของข้อความ: สี ขนาด รูปแบบตัวอักษร ขีดเส้นใต้ และองค์ประกอบเชิงโต้ตอบ ตามข้อมูลจาก Google Developers SpannableString ถูกใช้ในส่วนประกอบระบบ Android สำหรับการจัดรูปแบบลิงก์ ซึ่งเป็นวิธีหลักในการจัดรูปแบบข้อความโดยไม่ต้องใช้ไลบรารีของบุคคลที่สาม
ประเด็นสำคัญ
SpannableString คือคลาส Android ที่ใช้อินเทอร์เฟซ Spannable เก็บข้อความพร้อมกับชุดอ็อบเจ็กต์ Span ที่ควบคุมการแสดงผล ซึ่งแตกต่างจาก String ทั่วไป SpannableString อนุญาตให้แนบแอตทริบิวต์สไตล์กับช่วงอักขระเฉพาะ: ทำให้ข้อความบางส่วนเป็นสีแดง เพิ่มขนาดตัวอักษรในหัวข้อ หรือเพิ่มลิงก์ที่คลิกได้ภายในย่อหน้า
คลาสนี้อยู่ในแพ็คเกจ android.text และพร้อมใช้งานตั้งแต่ API Level 1 SpannableString ไม่สามารถเปลี่ยนแปลงได้ — เมื่อสร้างแล้ว โครงสร้างจะถูกกำหนด และการแทนที่ข้อความต้องสร้างอ็อบเจ็กต์ใหม่ สำหรับการแก้ไขแบบไดนามิก จะใช้ SpannableStringBuilder ซึ่งรองรับการแทรกและลบอักขระโดยไม่สูญเสียสไตล์
CharSequence คืออินเทอร์เฟซพื้นฐานสำหรับข้อมูลข้อความ ซึ่ง String, StringBuilder และ SpannableString เป็นผู้ใช้งาน ความแตกต่างหลักระหว่าง SpannableString และ String คือการรองรับการแนบอ็อบเจ็กต์ใดๆ ไปยังสตริงย่อย TextView จดจำอินเทอร์เฟซ Spannable และใช้อ็อบเจ็กต์ span กับส่วนข้อความที่เกี่ยวข้องระหว่างการเรนเดอร์ หากคุณส่ง String ทั่วไปไปยัง TextView จะไม่มีสไตล์ใดๆ ถูกนำไปใช้
SpannableString เก็บข้อความเป็นอาร์เรย์ char[] และอาร์เรย์แยกต่างหากของอ็อบเจ็กต์ span พร้อมข้อมูลเมตาเกี่ยวกับตำแหน่งเริ่มต้นและสิ้นสุด เมื่อเรียก setSpan(what, start, end, flags) อ็อบเจ็กต์ what จะถูกบันทึกในรายการพร้อมกับข้อมูลช่วง ระหว่างการเรนเดอร์ TextView จะใช้สแปนทั้งหมดที่อยู่ในช่วงที่แสดงตามลำดับ โดยเรียกเมธอด updateDrawState และ updateMeasureState
CharacterStyle คือคลาสพื้นฐานสำหรับสแปนที่ส่งผลต่ออักขระแต่ละตัวโดยไม่คำนึงถึงตำแหน่งในบรรทัด ซึ่งรวมถึง ForegroundColorSpan (สีข้อความ), RelativeSizeSpan (ขนาดสัมพัทธ์), StyleSpan (ตัวหนาและตัวเอียง), UnderlineSpan (ขีดเส้นใต้) และอื่นๆ สแปนแบบอักขระจะถูกนำไปใช้กับแต่ละอักขระในช่วงที่ระบุทีละตัว
ParagraphStyle คืออินเทอร์เฟซสำหรับสแปนที่ส่งผลต่อทั้งย่อหน้า ตัวแทนที่รู้จักดีที่สุดคือ AlignmentSpan ซึ่งจัดแนวทั้งย่อหน้าไปทางซ้าย กลาง หรือขวา สแปนแบบย่อหน้าต้องครอบคลุมทั้งย่อหน้า มิฉะนั้น Android จะไม่นำไปใช้ ข้อจำกัดนี้เกิดจากการจัดแนวหรือการเยื้องมีความหมายเฉพาะสำหรับบล็อกข้อความทั้งหมดเท่านั้น
แฟลก Span คือค่าคงที่สี่ค่าที่กำหนดพฤติกรรมของสแปนเมื่อแทรกหรือลบข้อความที่ขอบเขตของช่วง SPAN_EXCLUSIVE_EXCLUSIVE เก็บสแปนให้ทำงานเฉพาะภายในขอบเขตเดิม SPAN_INCLUSIVE_INCLUSIVE ขยายมันเมื่อเพิ่มข้อความที่ขอบเขต SPAN_EXCLUSIVE_INCLUSIVE และ SPAN_INCLUSIVE_EXCLUSIVE ให้พฤติกรรมแบบผสมสำหรับจุดเริ่มต้นและจุดสิ้นสุดของช่วง
| แฟลก | แทรกทางซ้าย | แทรกทางขวา |
|---|---|---|
| SPAN_EXCLUSIVE_EXCLUSIVE | ไม่รวม | ไม่รวม |
| SPAN_INCLUSIVE_INCLUSIVE | รวม | รวม |
| SPAN_EXCLUSIVE_INCLUSIVE | ไม่รวม | รวม |
| SPAN_INCLUSIVE_EXCLUSIVE | รวม | ไม่รวม |
การเลือกแฟลกที่ถูกต้องเป็นสิ่งสำคัญสำหรับข้อความ Editable ใน EditText ที่ผู้ใช้สามารถแทรกและลบอักขระได้ สำหรับ TextView แบบอ่านอย่างเดียว มักใช้ SPAN_EXCLUSIVE_EXCLUSIVE — สไตล์จะใช้เฉพาะกับช่วงเดิมและไม่ขยายตามการเปลี่ยนแปลงโดยโปรแกรม
Android SDK มีคลาสสแปนในตัวมากกว่า 25 คลาส ที่ครอบคลุมงานจัดรูปแบบข้อความส่วนใหญ่ แต่ละคลาสใช้อินเทอร์เฟซ CharacterStyle หรือ ParagraphStyle และรับพารามิเตอร์ผ่านคอนสตรัคเตอร์ คลาสทั้งหมดอยู่ในแพ็คเกจ android.text.style และพร้อมใช้งานโดยไม่ต้องเพิ่มการพึ่งพาเพิ่มเติม
ForegroundColorSpan กำหนดสีข้อความสำหรับช่วงที่ระบุ รับสีในรูปแบบ int BackgroundColorSpan ระบายสีพื้นหลังด้านหลังข้อความ มีประโยชน์สำหรับการเน้นผลการค้นหา AbsoluteSizeSpan กำหนดขนาดตัวอักษรที่แน่นอนในพิกเซล RelativeSizeSpan ใช้ตัวคูณสัมพันธ์กับขนาดข้อความพื้นฐานใน TextView
StyleSpan รับค่าคงที่ Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC หรือ BOLD_ITALIC และเปลี่ยนรูปแบบตัวอักษรของอักขระ UnderlineSpan เพิ่มขีดเส้นใต้ StrikethroughSpan เพิ่มขีดทับ SuperscriptSpan และ SubscriptSpan สร้างตัวยกและตัวห้อย TypefaceSpan อนุญาตให้กำหนดฟอนต์ที่กำหนดเองผ่านอ็อบเจ็กต์ Typeface สำหรับช่วงข้อความ
ClickableSpan คือคลาสนามธรรมสำหรับสร้างส่วนข้อความที่คลิกได้ เมื่อแตะ เมธอด onClick() จะถูกเรียก เพื่อให้การคลิกทำงานได้ TextView ต้องมี setMovementMethod(LinkMovementMethod.getInstance()) URLSpan เป็นคลาสย่อยของ ClickableSpan สำหรับไฮเปอร์ลิงก์ที่เปิดเบราว์เซอร์โดยอัตโนมัติ ClickableSpan มักถูกรวมกับ ForegroundColorSpan เพื่อเน้นลิงก์ด้วยสีน้ำเงิน
val spannable = SpannableString("Open developer.android.com")
spannable.setSpan(
URLSpan("https://developer.android.com"),
9, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
spannable.setSpan(
ForegroundColorSpan(Color.BLUE),
9, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
textView.movementMethod = LinkMovementMethod.getInstance()
หากไม่มี LinkMovementMethod การคลิกบน URLSpan จะไม่ถูกประมวลผล MovementMethod ทำหน้าที่สกัดกั้นเหตุการณ์การสัมผัสและค้นหา ClickableSpan ที่ตำแหน่งสัมผัส สแปนสีทำให้ลิงก์มองเห็นได้แก่ผู้ใช้
การทำงานกับ SpannableString เริ่มต้นด้วยการสร้างอินสแตนซ์จากสตริงข้อความและใช้สแปนตามลำดับผ่านเมธอด setSpan() เมธอดรับพารามิเตอร์สี่ตัว: อ็อบเจ็กต์สแปน ตำแหน่งเริ่มต้น ตำแหน่งสิ้นสุด และแฟลก หลังจากตั้งค่าสแปนทั้งหมดแล้ว อ็อบเจ็กต์จะถูกส่งไปยัง TextView ผ่าน setText()
มาสร้างสตริงที่คำแรกเป็นสีแดงและขยายใหญ่ขึ้น โดยเราใช้ ForegroundColorSpan สำหรับสี และ RelativeSizeSpan สำหรับขนาด สแปนทั้งสองถูกนำไปใช้กับช่วงเดียวกันอย่างอิสระ — ลำดับการเรียก setSpan ไม่สำคัญ
val text = "Header: remaining text"
val spannable = SpannableString(text)
val colorSpan = ForegroundColorSpan(Color.RED)
val sizeSpan = RelativeSizeSpan(1.5f)
spannable.setSpan(colorSpan, 0, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(sizeSpan, 0, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannable
อักขระตั้งแต่ตำแหน่ง 0 ถึง 9 ได้รับทั้งสองสไตล์พร้อมกัน TextView ใช้สแปนทั้งหมดโดยอัตโนมัติระหว่างการเรนเดอร์ — ไม่จำเป็นต้องเรียกเพิ่มเติม RelativeSizeSpan ด้วยตัวคูณ 1.5f จะเพิ่มขนาดตัวอักษร 50% เมื่อเทียบกับพื้นฐาน
Html.fromHtml() สร้างอ็อบเจ็กต์ Spanned จากสตริง HTML แต่ชุดแท็กที่รองรับมีจำกัด SpannableString ให้ควบคุมแอตทริบิวต์แต่ละรายการได้อย่างสมบูรณ์โดยไม่มีข้อจำกัดของ HTML หากคุณต้องการแปลง HTML เป็นสแปนแล้วเพิ่มสไตล์ที่กำหนดเอง คุณสามารถใช้ Html.fromHtml() เป็นพื้นฐานแล้วเสริมด้วยสแปนผ่าน setSpan()
val htmlText = Html.fromHtml(
"<b>Important:</b> check the data",
Html.FROM_HTML_MODE_LEGACY
)
val spannable = SpannableString(htmlText)
spannable.setSpan(
ForegroundColorSpan(Color.RED),
0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
ผลลัพธ์ — ข้อความ “สำคัญ:” จะเป็นตัวหนา (จาก HTML) และสีแดง (จากสแปน) วิธีการนี้สะดวกเมื่อทำงานกับเนื้อหาจากเซิร์ฟเวอร์ที่การจัดรูปแบบบางส่วนระบุใน HTML และบางส่วนเพิ่มจากฝั่งไคลเอ็นต์โดยโปรแกรม
SpannableString อนุญาตให้ใช้สแปนจำนวนไม่จำกัดกับช่วงเดียวกันหรือช่วงที่ทับซ้อนกัน การรวมสแปนเป็นข้อได้เปรียบสำคัญเหนือมาร์กอัป HTML ที่แท็กที่ซ้อนกันอาจขัดแย้งกันได้ สแปนเป็นอิสระและถูกนำไปใช้ตามลำดับระหว่างการเรนเดอร์
ตัวอย่างเช่น คุณสามารถทำให้ส่วนข้อความเป็นตัวหนา สีแดง และคลิกได้พร้อมกัน โดยสร้างสแปนสามตัว — StyleSpan, ForegroundColorSpan และ ClickableSpan — และใช้แต่ละตัวกับช่วงเดียวกัน ลำดับการใช้งานไม่ส่งผลต่อผลลัพธ์ เนื่องจากแต่ละสแปนรับผิดชอบแอตทริบิวต์ข้อความของตัวเอง
หากสแปนประเภทต่างๆ ทับซ้อนกันบางส่วน แต่ละตัวทำงานอย่างอิสระภายในขอบเขตของตัวเอง ForegroundColorSpan ที่ช่วง 0–10 และ StyleSpan(BOLD) ที่ช่วง 5–15 จะให้ข้อความสีแดงตัวหนาที่ส่วน 5–10 และตัวหนาอย่างเดียวที่ 10–15 ไม่เกิดความขัดแย้งเนื่องจากแต่ละสแปนปรับเปลี่ยนแอตทริบิวต์ของตัวเองระหว่างการเรนเดอร์
เมธอด getSpans(int start, int end, Class type) คืนค่าอาร์เรย์ของสแปนภายในช่วงที่ระบุ ซึ่งมีประโยชน์สำหรับตรวจสอบว่าสไตล์ใดถูกนำไปใช้แล้ว หรือสำหรับลบสแปนเฉพาะ ด้วย nextSpanTransition() คุณสามารถวนซ้ำขอบเขตการเปลี่ยนแปลงของสแปน — นี่คือพื้นฐานสำหรับการปรับใช้ TextView ที่กำหนดเองซึ่งจำเป็นต้องรู้ว่าสไตล์เปลี่ยนที่ใด
SpannableStringBuilder คือคลาสสำหรับสร้างข้อความที่มีสไตล์ทีละขั้นตอน ด้วยความสามารถในการแทรก แทนที่ และลบส่วนต่างๆ ซึ่งแตกต่างจาก SpannableString ที่สร้างจากสตริงสำเร็จรูปและไม่สามารถเปลี่ยนแปลงได้ Builder อนุญาตให้เพิ่มส่วนข้อความตามลำดับและกำหนดสไตล์ได้ทันที ซึ่งเหมาะสำหรับข้อความผสม: บันทึก การแชท หัวข้อข่าวที่มีป้ายกำกับแบบไดนามิก
Builder ใช้อินเทอร์เฟซ Spannable และ Editable ทำให้เข้ากันได้กับ EditText ผู้ใช้สามารถแก้ไขข้อความ และสไตล์จะถูกเก็บรักษาและเลื่อนอย่างถูกต้องเมื่อแทรกอักขระใหม่ SpannableString ที่ไม่สามารถเปลี่ยนแปลงได้ ไม่เหมาะสำหรับฟิลด์ที่แก้ไขได้
เมธอด append() คืนค่า builder เอง ทำให้สามารถต่อเมธอดได้ หลังจากเพิ่มข้อความแล้ว สแปนจะถูกนำไปใช้ผ่าน setSpan() ตำแหน่งจะถูกระบุสัมพันธ์กับความยาวปัจจุบันของ builder Insert() และ replace() ก็พร้อมใช้งานสำหรับการควบคุมเนื้อหาที่แม่นยำยิ่งขึ้น
val builder = SpannableStringBuilder()
.append("New ")
.append("comment")
val blue = ForegroundColorSpan(Color.BLUE)
val gray = ForegroundColorSpan(Color.GRAY)
builder.setSpan(blue, 0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
builder.setSpan(gray, 6, 17, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = builder
ข้อความ “ใหม่ ” เป็นสีน้ำเงิน และ “ความคิดเห็น” เป็นสีเทา เมื่อแทรกอักขระเพิ่มเติมระหว่างกัน สแปนจะไม่ส่งผลต่อข้อความใหม่เนื่องจากแฟลก EXCLUSIVE Builder ปรับดัชนีภายในโดยอัตโนมัติเมื่อมีการแก้ไข
การใช้สแปนจำนวนมากส่งผลต่อประสิทธิภาพการเรนเดอร์ของ TextView แต่ละสแปนเรียกเมธอด updateDrawState() หรือ updateMeasureState() ทุกครั้งที่วาดใหม่ ขอแนะนำให้จำกัดจำนวนสแปนต่อ TextView ไว้ที่ 50–100 เพื่อประสิทธิภาพที่ราบรื่นบนอุปกรณ์ระดับกลาง สแปนที่เปลี่ยนขนาดข้อความ (RelativeSizeSpan, AbsoluteSizeSpan) ต้องการการคำนวณเลย์เอาต์ใหม่ทุกครั้งที่เปลี่ยนแปลง ซึ่งมีค่าใช้จ่ายสูงกว่าสแปนสีหรือขีดเส้นใต้อย่างมาก
TextAppearanceSpan อนุญาตให้ใช้ชุดสไตล์ทั้งหมดจากทรัพยากร XML android:textAppearance ด้วยการเรียก setSpan() เพียงครั้งเดียว แทนที่จะใช้สแปนสามตัวแยกกัน (สี ขนาด ฟอนต์) จะใช้ TextAppearanceSpan ตัวเดียวพร้อมการอ้างอิงสไตล์ ซึ่งลดจำนวนอ็อบเจ็กต์และทำให้การบำรุงรักษาง่ายขึ้น — การเปลี่ยนสไตล์ในทรัพยากรจะนำไปใช้กับข้อความทั้งหมดที่ใช้สแปนนี้โดยอัตโนมัติ
การสร้างอินสแตนซ์สแปนใหม่ทุกครั้งสำหรับ setSpan() แต่ละครั้งจะเพิ่มภาระให้กับตัวเก็บขยะ วิธีที่เหมาะสมที่สุดคือสร้างอ็อบเจ็กต์สแปนคงที่ หากใช้ซ้ำๆ ตัวอย่างเช่น ForegroundColorSpan(Color.RED) สามารถเก็บไว้ใน companion object และนำกลับมาใช้ใหม่ได้ อย่างไรก็ตาม สแปนที่มีสถานะ (ClickableSpan ที่มีตัวจัดการต่างกัน) ต้องสร้างแยกกันสำหรับแต่ละกรณี
เพื่อวินิจฉัยปัญหาประสิทธิภาพของสแปน ให้ใช้ Layout Inspector ใน Android Studio และโปรไฟล์ GPU หาก TextView ที่มีสแปนจำนวนมากช้าลงอย่างเห็นได้ชัดเมื่อเลื่อน ให้พิจารณาเปลี่ยนสแปนบางส่วนเป็นสไตล์คงที่ผ่าน TextAppearanceSpan หรือลดจำนวนสแปนโดยรวมแอตทริบิวต์ในการปรับใช้ UpdateAppearance ที่กำหนดเอง
คำถามที่พบบ่อย
String คือลำดับอักขระที่ไม่สามารถเปลี่ยนแปลงได้โดยไม่รองรับสไตล์ SpannableString เก็บอักขระเดียวกันแต่เพิ่มเติมมีอาร์เรย์ของอ็อบเจ็กต์ Span พร้อมข้อมูลการจัดรูปแบบ TextView กำหนดประเภทของ CharSequence ที่ส่งและใช้สแปนกับช่วงที่เกี่ยวข้องระหว่างการเรนเดอร์ String ไม่สนใจแอตทริบิวต์สไตล์ใดๆ และแสดงเป็นข้อความธรรมดา
เมธอด removeSpan(Object span) ลบสแปนที่เฉพาะเจาะจง สำหรับการล้างทั้งหมด ให้เรียก getSpans(0, length, Object::class.java) ซึ่งคืนค่าอาร์เรย์ของสแปนทั้งหมด จากนั้นลบแต่ละตัวผ่าน removeSpan หรือสร้าง SpannableString(text.toString()) ใหม่โดยไม่มีสแปน SpannableStringBuilder มีเมธอด clear() ที่ลบทั้งข้อความและสแปน
SpannableString ทำงานใน EditText แต่สำหรับข้อความที่แก้ไขได้ ควรใช้ SpannableStringBuilder ซึ่งใช้อินเทอร์เฟซ Editable EditText ต้องการอินเทอร์เฟซ Editable เพื่อติดตามการเปลี่ยนแปลง หากคุณส่ง SpannableString ไปยัง EditText ข้อความจะแสดงด้วยสไตล์ แต่ระหว่างการแก้ไข Android จะแปลงเป็น Editable ซึ่งอาจรีเซ็ตสแปนบางตัว
ใน Jetpack Compose สแปนของ Android SDK ไม่ได้ใช้โดยตรง แต่ Compose มี AnnotatedString ซึ่งเทียบเท่ากับ SpannableString ที่มีความสามารถคล้ายกัน: SpanStyle สำหรับสไตล์อักขระแต่ละตัวและ ParagraphStyle สำหรับย่อหน้า การแปลง SpannableString เป็น AnnotatedString สามารถทำได้ผ่าน buildAnnotatedString โดยวนซ้ำสแปน
สร้างคลาสที่สืบทอดจาก CharacterStyle และแทนที่เมธอด updateDrawState(TextPaint tp) ภายในเมธอด ปรับเปลี่ยนคุณสมบัติของ TextPaint: สี ความกว้างเส้น เอฟเฟกต์ สำหรับการเปลี่ยนแปลงเมตริก ให้ใช้ UpdateLayout หรือ MetricAffectingSpan สแปนที่กำหนดเองถูกนำไปใช้ผ่าน setSpan() เช่นเดียวกับสแปนในตัว
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ