Span

ลองใช้วิธีเขียน
Jetpack Compose เป็นชุดเครื่องมือ UI ที่แนะนําสําหรับ Android ดูวิธีใช้ข้อความใน "เขียน"

ช่วงคือออบเจ็กต์มาร์กอัปที่มีประสิทธิภาพซึ่งคุณใช้จัดรูปแบบข้อความได้ในระดับอักขระหรือย่อหน้า คุณสามารถเปลี่ยนข้อความได้หลายวิธีเมื่อแนบช่วงกับออบเจ็กต์ข้อความ ซึ่งรวมถึงการเพิ่มสี การทำให้ข้อความคลิกได้ การปรับขนาดข้อความ และการวาดข้อความในลักษณะที่กำหนดเอง ระยะห่างยังเปลี่ยนแปลงคุณสมบัติของ TextPaint วาดใน Canvas และเปลี่ยนเลย์เอาต์ข้อความได้ด้วย

Android มีช่วงหลายประเภทที่ครอบคลุมรูปแบบการจัดรูปแบบข้อความทั่วไปที่หลากหลาย นอกจากนี้ คุณยังสร้างช่วงของคุณเองเพื่อใช้การจัดรูปแบบที่กำหนดเองได้ด้วย

สร้างและใช้ช่วง

หากต้องการสร้างช่วง ให้ใช้คลาสใดคลาสหนึ่งที่อยู่ในตารางต่อไปนี้ โดยคลาสจะแตกต่างกันไปตามลักษณะของข้อความเอง ลักษณะของมาร์กอัปข้อความ และโครงสร้างข้อมูลที่สําคัญซึ่งมีข้อมูลช่วง

ชั้น ข้อความที่เปลี่ยนแปลงได้ มาร์กอัปที่เปลี่ยนแปลงได้ โครงสร้างข้อมูล
SpannedString ไม่ ไม่ อาร์เรย์เชิงเส้น
SpannableString ไม่ ใช่ อาร์เรย์เชิงเส้น
SpannableStringBuilder ใช่ ใช่ แผนผังช่วงเวลา

คลาสทั้ง 3 คลาสขยายอินเทอร์เฟซ Spanned SpannableString และ SpannableStringBuilder ยังขยายอินเทอร์เฟซ Spannable ด้วย

วิธีเลือกว่าจะใช้รูปแบบใด

  • หากคุณไม่ได้แก้ไขข้อความหรือมาร์กอัปหลังจากสร้าง ให้ใช้ SpannedString
  • หากต้องการแนบช่วงสั้นๆ ไปยังออบเจ็กต์ข้อความรายการเดียวและข้อความนั้นเป็นแบบอ่านอย่างเดียว ให้ใช้ SpannableString
  • หากต้องการแก้ไขข้อความหลังจากสร้างและต้องการแนบช่วงกับข้อความ ให้ใช้ SpannableStringBuilder
  • หากต้องการแนบช่วงจำนวนมากกับออบเจ็กต์ข้อความ ไม่ว่าจะมีสิทธิ์อ่านอย่างเดียวหรือไม่ก็ตาม ให้ใช้ SpannableStringBuilder

หากต้องการใช้ช่วง ให้เรียกใช้ setSpan(Object _what_, int _start_, int _end_, int _flags_) ในออบเจ็กต์ Spannable พารามิเตอร์ what หมายถึงช่วงที่คุณใช้กับข้อความ ส่วนพารามิเตอร์ start และ end ระบุส่วนของข้อความที่คุณใช้ช่วงนั้น

หากคุณแทรกข้อความภายในขอบเขตของช่วง ระบบจะขยายช่วงให้รวมข้อความที่แทรกไว้โดยอัตโนมัติ เมื่อแทรกข้อความที่ขอบเขตของช่วง นั่นคือที่ดัชนีเริ่มต้นหรือสิ้นสุด พารามิเตอร์Flag จะกำหนดว่าช่วงจะขยายเพื่อรวมข้อความที่แทรกหรือไม่ ใช้เครื่องหมาย Spannable.SPAN_EXCLUSIVE_INCLUSIVE เพื่อรวมข้อความที่แทรก และSpannable.SPAN_EXCLUSIVE_EXCLUSIVE เพื่อยกเว้นข้อความที่แทรก

ตัวอย่างต่อไปนี้แสดงวิธีแนบ ForegroundColorSpan กับสตริง

Kotlin

val spannable = SpannableStringBuilder("Text is spantastic!")
spannable.setSpan(
    ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)

Java

SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
);
รูปภาพแสดงข้อความสีเทาและสีแดงบางส่วน
รูปที่ 1 ข้อความที่มีการจัดรูปแบบด้วย ForegroundColorSpan

เนื่องจากมีการตั้งค่าช่วงโดยใช้ Spannable.SPAN_EXCLUSIVE_INCLUSIVE ช่วงจึงขยายเพื่อรวมข้อความที่แทรกไว้ที่ขอบเขตของช่วง ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

val spannable = SpannableStringBuilder("Text is spantastic!")
spannable.setSpan(
    ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
)
spannable.insert(12, "(& fon)")

Java

SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
);
spannable.insert(12, "(& fon)");
รูปภาพแสดงวิธีที่ช่วงรวมข้อความเพิ่มเติมเมื่อใช้ SPAN_EXCLUSIVE_INCLUSIVE
รูปที่ 2 ช่วงที่ขยายจะรวมข้อความเพิ่มเติมเมื่อใช้ Spannable.SPAN_EXCLUSIVE_INCLUSIVE

คุณแนบช่วงหลายรายการลงในข้อความเดียวกันได้ ตัวอย่างต่อไปนี้แสดงวิธีสร้างข้อความตัวหนาสีแดง

Kotlin

val spannable = SpannableString("Text is spantastic!")
spannable.setSpan(ForegroundColorSpan(Color.RED), 8, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(
    StyleSpan(Typeface.BOLD),
    8,
    spannable.length,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

Java

SpannableString spannable = new SpannableString("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, 12,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
spannable.setSpan(
    new StyleSpan(Typeface.BOLD),
    8, spannable.length(),
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
รูปภาพที่แสดงข้อความที่มีหลายช่วง ได้แก่ `ForegroundColorSpan(Color.RED)` และ `StyleSpan(BOLD)`
รูปที่ 3 ข้อความที่มีหลายช่วง ได้แก่ ForegroundColorSpan(Color.RED) และ StyleSpan(BOLD)

ประเภทช่วงของ Android

Android มีประเภทช่วงมากกว่า 20 ประเภทในแพ็กเกจ android.text.style Android จะจัดหมวดหมู่ช่วงออกเป็น 2 วิธีหลัก ดังนี้

  • วิธีที่ช่วงมีผลต่อข้อความ: ช่วงอาจส่งผลต่อลักษณะที่ปรากฏของข้อความหรือเมตริกข้อความ
  • ขอบเขตของช่วง: ช่วงบางช่วงใช้กับอักขระแต่ละตัวได้ ขณะที่บางช่วงต้องใช้กับทั้งย่อหน้า
รูปภาพแสดงหมวดหมู่ช่วงต่างๆ
รูปที่ 4 หมวดหมู่ของช่วงใน Android

ส่วนต่อไปนี้จะอธิบายหมวดหมู่เหล่านี้โดยละเอียด

ช่วงที่มีผลต่อลักษณะที่ปรากฏของข้อความ

ช่วงที่มีผลในระดับอักขระจะส่งผลต่อลักษณะที่ปรากฏของข้อความ เช่น การเปลี่ยนสีข้อความหรือพื้นหลัง และการเพิ่มขีดเส้นใต้หรือขีดทับ ช่วงเหล่านี้จะขยายคลาส CharacterStyle

ตัวอย่างโค้ดต่อไปนี้แสดงวิธีใช้ UnderlineSpan เพื่อขีดเส้นใต้ข้อความ

Kotlin

val string = SpannableString("Text with underline span")
string.setSpan(UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

SpannableString string = new SpannableString("Text with underline span");
string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
รูปภาพแสดงวิธีขีดเส้นใต้ข้อความโดยใช้ `UnderlineSpan`
รูปที่ 5 ข้อความที่ขีดเส้นใต้โดยใช้ UnderlineSpan

ส่วนที่มีผลต่อลักษณะที่ปรากฏของข้อความเท่านั้นจะทริกเกอร์ให้ระบบวาดข้อความอีกครั้งโดยไม่ทริกเกอร์ให้ระบบคํานวณเลย์เอาต์อีกครั้ง ช่วงเหล่านี้จะติดตั้งใช้งาน UpdateAppearance และขยายการทำงาน CharacterStyle CharacterStyle ซับคลาสจะกำหนดวิธีวาดข้อความโดยให้สิทธิ์เข้าถึงเพื่ออัปเดต TextPaint

ช่วงที่มีผลต่อเมตริกข้อความ

ส่วนอื่นๆ ที่ใช้ในระดับอักขระจะส่งผลต่อเมตริกข้อความ เช่น ความสูงของบรรทัดและขนาดข้อความ ช่วงเหล่านี้จะขยายคลาส MetricAffectingSpan

ตัวอย่างโค้ดต่อไปนี้สร้าง RelativeSizeSpan ที่ เพิ่มขนาดข้อความ 50%

Kotlin

val string = SpannableString("Text with relative size span")
string.setSpan(RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

SpannableString string = new SpannableString("Text with relative size span");
string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
รูปภาพแสดงการใช้งาน RelativeSizeSpan
รูปที่ 6 ข้อความที่ขยายใหญ่ขึ้นโดยใช้ RelativeSizeSpan

การใช้ช่วงที่มีผลต่อเมตริกข้อความทําให้ออบเจ็กต์ที่สังเกตการณ์วัดข้อความอีกครั้งเพื่อเลย์เอาต์และการเรนเดอร์ที่ถูกต้อง เช่น การเปลี่ยนขนาดข้อความอาจทําให้คําปรากฏในบรรทัดอื่น การใช้ช่วงก่อนหน้าจะทริกเกอร์การวัดใหม่ การคํานวณเลย์เอาต์ข้อความใหม่ และการวาดข้อความใหม่

ช่วงที่ส่งผลต่อเมตริกข้อความจะขยายคลาส MetricAffectingSpan ซึ่งเป็นคลาสนามธรรมที่ช่วยให้คลาสย่อยกําหนดวิธีที่ช่วงส่งผลต่อวิธีการวัดข้อความได้ด้วยการให้สิทธิ์เข้าถึง TextPaint เนื่องจาก MetricAffectingSpan ขยายจาก CharacterStyle ซับคลาสจึงส่งผลต่อลักษณะที่ปรากฏของข้อความที่ระดับอักขระ

ช่วงที่มีผลต่อย่อหน้า

ระยะห่างยังส่งผลต่อข้อความในระดับย่อหน้าได้ด้วย เช่น การเปลี่ยนการจัดแนวหรือระยะขอบของบล็อกข้อความ ช่วงที่มีผลกับทั้งย่อหน้า ใช้ ParagraphStyle หากต้องการใช้ช่วงเหล่านี้ ให้แนบช่วงนั้นกับทั้งย่อหน้า ยกเว้นอักขระขึ้นบรรทัดใหม่ตอนท้าย หากคุณพยายามใช้ช่วงย่อหน้ากับสิ่งอื่นที่ไม่ใช่ทั้งย่อหน้า Android จะไม่ใช้ช่วงเลย

รูปที่ 8 แสดงวิธีที่ Android แยกย่อหน้าในข้อความ

รูปที่ 7 ใน Android ย่อหน้าจะจบด้วยอักขระบรรทัดใหม่ (\n)

ตัวอย่างโค้ดต่อไปนี้ใช้ QuoteSpan กับย่อหน้า โปรดทราบว่าหากคุณแนบช่วงไว้ที่ตำแหน่งอื่นนอกเหนือจากตอนต้นหรือตอนท้ายของย่อหน้า Android จะไม่ใช้สไตล์นั้นเลย

Kotlin

spannable.setSpan(QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

Java

spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
รูปภาพแสดงตัวอย่าง QuoteSpan
รูปที่ 8 QuoteSpan ใช้กับย่อหน้า

สร้างช่วงที่กำหนดเอง

หากต้องการฟังก์ชันการทำงานมากกว่าที่มีให้ในแท็บ Android ที่มีอยู่ คุณสามารถใช้แท็บที่กําหนดเองได้ เมื่อใช้ช่วงของคุณเอง ให้ตัดสินใจว่าช่วงจะส่งผลต่อข้อความที่ระดับอักขระหรือระดับย่อหน้า และส่งผลต่อเลย์เอาต์หรือลักษณะที่ปรากฏของข้อความหรือไม่ ซึ่งจะช่วยให้คุณระบุคลาสพื้นฐานที่ขยายได้และอินเทอร์เฟซที่อาจต้องใช้ ใช้ตารางต่อไปนี้เป็นข้อมูลอ้างอิง

สถานการณ์ คลาสหรืออินเทอร์เฟซ
ช่วงที่เลือกจะส่งผลต่อข้อความที่ระดับอักขระ CharacterStyle
ระยะมีผลต่อลักษณะของข้อความ UpdateAppearance
ระยะมีผลต่อเมตริกข้อความ UpdateLayout
ช่วงที่เลือกจะมีผลกับข้อความในระดับย่อหน้า ParagraphStyle

เช่น หากต้องการใช้ช่วงที่กำหนดเองซึ่งแก้ไขขนาดและสีข้อความ ให้ขยาย RelativeSizeSpan RelativeSizeSpanสืบทอดCharacterStyleและใช้อินเทอร์เฟซ Update 2 รายการผ่านการรับช่วง เนื่องจากคลาสนี้มีคอลแบ็กสําหรับ updateDrawState และ updateMeasureState อยู่แล้ว คุณจึงลบล้างคอลแบ็กเหล่านี้เพื่อใช้ลักษณะการทํางานที่กำหนดเองได้ โค้ดต่อไปนี้จะสร้างช่วงที่กำหนดเองที่ขยาย RelativeSizeSpan และลบล้างการเรียกกลับ updateDrawState เพื่อกำหนดสีของ TextPaint

Kotlin

class RelativeSizeColorSpan(
    size: Float,
    @ColorInt private val color: Int
) : RelativeSizeSpan(size) {
    override fun updateDrawState(textPaint: TextPaint) {
        super.updateDrawState(textPaint)
        textPaint.color = color
    }
}

Java

public class RelativeSizeColorSpan extends RelativeSizeSpan {
    private int color;
    public RelativeSizeColorSpan(float spanSize, int spanColor) {
        super(spanSize);
        color = spanColor;
    }
    @Override
    public void updateDrawState(TextPaint textPaint) {
        super.updateDrawState(textPaint);
        textPaint.setColor(color);
    }
}

ตัวอย่างนี้แสดงวิธีสร้างช่วงที่กำหนดเอง คุณสร้างเอฟเฟกต์เดียวกันได้โดยใช้ RelativeSizeSpan และ ForegroundColorSpan กับข้อความ

ทดสอบการใช้งานช่วง

อินเทอร์เฟซ Spanned ช่วยให้คุณทั้งตั้งค่าช่วงและดึงข้อมูลช่วงจากข้อความได้ เมื่อทดสอบ ให้ใช้การทดสอบ Android JUnit เพื่อยืนยันว่ามีการเพิ่มช่วงที่เหมาะสมในตําแหน่งที่เหมาะสม ตัวอย่างการจัดรูปแบบข้อความ แอป มีช่วงที่ใช้มาร์กอัปกับสัญลักษณ์หัวข้อย่อยโดยแนบ BulletPointSpan กับข้อความ ตัวอย่างโค้ดต่อไปนี้แสดงวิธีทดสอบว่าหัวข้อย่อยปรากฏตามที่คาดไว้หรือไม่

Kotlin

@Test fun textWithBulletPoints() {
   val result = builder.markdownToSpans("Points\n* one\n+ two")

   // Check whether the markup tags are removed.
   assertEquals("Points\none\ntwo", result.toString())

   // Get all the spans attached to the SpannedString.
   val spans = result.getSpans<Any>(0, result.length, Any::class.java)

   // Check whether the correct number of spans are created.
   assertEquals(2, spans.size.toLong())

   // Check whether the spans are instances of BulletPointSpan.
   val bulletSpan1 = spans[0] as BulletPointSpan
   val bulletSpan2 = spans[1] as BulletPointSpan

   // Check whether the start and end indices are the expected ones.
   assertEquals(7, result.getSpanStart(bulletSpan1).toLong())
   assertEquals(11, result.getSpanEnd(bulletSpan1).toLong())
   assertEquals(11, result.getSpanStart(bulletSpan2).toLong())
   assertEquals(14, result.getSpanEnd(bulletSpan2).toLong())
}

Java

@Test
public void textWithBulletPoints() {
    SpannedString result = builder.markdownToSpans("Points\n* one\n+ two");

    // Check whether the markup tags are removed.
    assertEquals("Points\none\ntwo", result.toString());

    // Get all the spans attached to the SpannedString.
    Object[] spans = result.getSpans(0, result.length(), Object.class);

    // Check whether the correct number of spans are created.
    assertEquals(2, spans.length);

    // Check whether the spans are instances of BulletPointSpan.
    BulletPointSpan bulletSpan1 = (BulletPointSpan) spans[0];
    BulletPointSpan bulletSpan2 = (BulletPointSpan) spans[1];

    // Check whether the start and end indices are the expected ones.
    assertEquals(7, result.getSpanStart(bulletSpan1));
    assertEquals(11, result.getSpanEnd(bulletSpan1));
    assertEquals(11, result.getSpanStart(bulletSpan2));
    assertEquals(14, result.getSpanEnd(bulletSpan2));
}

ดูตัวอย่างการทดสอบเพิ่มเติมได้ในส่วน MarkdownBuilderTest ใน GitHub

ทดสอบช่วงที่กำหนดเอง

เมื่อทดสอบช่วง ให้ตรวจสอบว่า TextPaint มีการแก้ไขตามที่คาดไว้ และมีการแสดงเอลิเมนต์ที่ถูกต้องใน Canvas เช่น ลองใช้การติดตั้งใช้งานช่วงที่กำหนดเองซึ่งเพิ่มเครื่องหมายหัวข้อไว้ที่ด้านหน้าข้อความ มีการกำหนดขนาดและสีของหัวข้อย่อย และมีช่องว่างระหว่างขอบด้านซ้ายของพื้นที่วาดและหัวข้อย่อย

คุณสามารถทดสอบลักษณะการทํางานของคลาสนี้ได้โดยใช้การทดสอบ AndroidJUnit โดยตรวจสอบสิ่งต่อไปนี้

  • หากคุณใช้ช่วงอย่างถูกต้อง จุดเริ่มต้นของย่อหน้าขนาดและสีที่ระบุจะปรากฏบนผืนผ้าใบ และจะมีระยะห่างที่เหมาะสมระหว่างระยะขอบด้านซ้ายกับจุดเริ่มต้นของย่อหน้า
  • หากไม่ใช้ช่วง ลักษณะการทำงานที่กำหนดเองจะไม่ปรากฏ

คุณดูการใช้งานการทดสอบเหล่านี้ได้ในตัวอย่าง TextStyling ใน GitHub

คุณสามารถทดสอบการโต้ตอบของ Canvas โดยการจําลอง Canvas, ส่งออบเจ็กต์ที่จําลองไปยังเมธอด drawLeadingMargin() และยืนยันว่ามีการเรียกใช้เมธอดที่ถูกต้องด้วยพารามิเตอร์ที่ถูกต้อง

ดูตัวอย่างการทดสอบช่วงเพิ่มเติมได้ใน BulletPointSpanTest

แนวทางปฏิบัติแนะนำในการใช้ช่วง

การตั้งค่าข้อความใน TextView นั้นทำได้หลายวิธีโดยขึ้นอยู่กับความต้องการ

แนบหรือเลิกแนบช่วงโดยไม่เปลี่ยนข้อความที่อยู่เบื้องหลัง

TextView.setText() มี Overload หลายรายการที่จัดการช่วงต่างกัน ตัวอย่างเช่น คุณสามารถตั้งค่าออบเจ็กต์ข้อความ Spannable ด้วยโค้ดต่อไปนี้

Kotlin

textView.setText(spannableObject)

Java

textView.setText(spannableObject);

เมื่อเรียกใช้ setText() แบบโอเวอร์โหลดนี้ TextView จะสร้างสำเนา Spannable เป็น SpannedString และเก็บไว้ในหน่วยความจำเป็น CharSequence ซึ่งหมายความว่าข้อความและช่วงจะเปลี่ยนแปลงไม่ได้ ดังนั้นเมื่อคุณต้องการอัปเดตข้อความหรือช่วง ให้สร้างออบเจ็กต์ Spannable ใหม่และเรียกใช้ setText() อีกครั้ง ซึ่งจะทริกเกอร์การวัดและวาดเลย์เอาต์อีกครั้งด้วย

หากต้องการระบุว่าช่วงต้องเปลี่ยนแปลงได้ ให้ใช้ setText(CharSequence text, TextView.BufferType type) แทน ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

textView.setText(spannable, BufferType.SPANNABLE)
val spannableText = textView.text as Spannable
spannableText.setSpan(
     ForegroundColorSpan(color),
     8, spannableText.length,
     SPAN_INCLUSIVE_INCLUSIVE
)

Java

textView.setText(spannable, BufferType.SPANNABLE);
Spannable spannableText = (Spannable) textView.getText();
spannableText.setSpan(
     new ForegroundColorSpan(color),
     8, spannableText.getLength(),
     SPAN_INCLUSIVE_INCLUSIVE);

ในตัวอย่างนี้ พารามิเตอร์ BufferType.SPANNABLE ทําให้ TextView สร้าง SpannableString และตอนนี้ออบเจ็กต์ CharSequence ที่ TextView เก็บไว้จะมีมาร์กอัปที่เปลี่ยนแปลงได้และข้อความที่เปลี่ยนแปลงไม่ได้ หากต้องการอัปเดตช่วง ให้ดึงข้อมูลข้อความเป็น Spannable แล้วอัปเดตช่วงตามต้องการ

เมื่อคุณแนบ เลิกแนบ หรือจัดตำแหน่งใหม่ TextView จะอัปเดตโดยอัตโนมัติเพื่อแสดงการเปลี่ยนแปลงในข้อความ หากคุณเปลี่ยนแอตทริบิวต์ภายในของช่วงที่มีอยู่ ให้เรียกใช้ invalidate() เพื่อทำการเปลี่ยนแปลงที่เกี่ยวข้องกับลักษณะที่ปรากฏ หรือเรียกใช้ requestLayout() เพื่อทำการเปลี่ยนแปลงที่เกี่ยวข้องกับเมตริก

ตั้งค่าข้อความใน TextView หลายครั้ง

ในบางกรณี เช่น เมื่อใช้ RecyclerView.ViewHolder คุณอาจต้องการใช้ TextView ซ้ำและตั้งค่าข้อความหลายครั้ง โดยค่าเริ่มต้น TextView จะสร้างสำเนาออบเจ็กต์ CharSequence และเก็บไว้ในหน่วยความจำ ไม่ว่าคุณจะตั้งค่า BufferType หรือไม่ก็ตาม ซึ่งทำให้การอัปเดตTextViewทั้งหมดเป็นไปอย่างตั้งใจ คุณไม่สามารถอัปเดตออบเจ็กต์CharSequenceต้นฉบับเพื่ออัปเดตข้อความได้ ซึ่งหมายความว่าทุกครั้งที่คุณตั้งค่าข้อความใหม่ TextView จะสร้างออบเจ็กต์ใหม่

หากต้องการควบคุมกระบวนการนี้มากขึ้นและหลีกเลี่ยงการสร้างออบเจ็กต์เพิ่มเติม คุณสามารถใช้ Spannable.Factory ของคุณเองและลบล้าง newSpannable() แทนที่จะสร้างออบเจ็กต์ข้อความใหม่ คุณสามารถแคสต์และแสดงผล CharSequence ที่มีอยู่เป็น Spannable ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

val spannableFactory = object : Spannable.Factory() {
    override fun newSpannable(source: CharSequence?): Spannable {
        return source as Spannable
    }
}

Java

Spannable.Factory spannableFactory = new Spannable.Factory(){
    @Override
    public Spannable newSpannable(CharSequence source) {
        return (Spannable) source;
    }
};

คุณต้องใส่ textView.setText(spannableObject, BufferType.SPANNABLE) เมื่อตั้งค่าข้อความ มิเช่นนั้น ระบบจะสร้างแหล่งที่มา CharSequence เป็นอินสแตนซ์ Spanned และไม่สามารถแคสต์เป็น Spannable ได้ ซึ่งจะทำให้ newSpannable() แสดงClassCastException

หลังจากลบล้าง newSpannable() แล้ว ให้บอก TextView ให้ใช้ Factory ใหม่โดยทำดังนี้

Kotlin

textView.setSpannableFactory(spannableFactory)

Java

textView.setSpannableFactory(spannableFactory);

ตั้งค่าออบเจ็กต์ Spannable.Factory 1 ครั้งทันทีหลังจากที่คุณได้รับข้อมูลอ้างอิงสำหรับ TextView หากคุณใช้ RecyclerView ให้ตั้งค่าออบเจ็กต์ Factory เมื่อคุณเพิ่มยอดดูเป็นครั้งแรก วิธีนี้จะช่วยหลีกเลี่ยงการสร้างออบเจ็กต์เพิ่มเติมเมื่อ RecyclerView ของคุณเชื่อมโยงรายการใหม่กับ ViewHolder

เปลี่ยนแอตทริบิวต์ของช่วงภายใน

หากต้องการเปลี่ยนเฉพาะแอตทริบิวต์ภายในของช่วงที่มีการเปลี่ยนแปลงได้ เช่น สีหัวข้อในช่วงหัวข้อที่กำหนดเอง คุณสามารถหลีกเลี่ยงค่าใช้จ่ายเพิ่มเติมจากการเรียกใช้ setText() หลายครั้งได้โดยเก็บการอ้างอิงไปยังช่วงไว้เมื่อสร้าง เมื่อต้องการแก้ไขช่วง คุณสามารถแก้ไขการอ้างอิงแล้วเรียกใช้ invalidate() หรือ requestLayout() ใน TextView โดยขึ้นอยู่กับประเภทแอตทริบิวต์ที่คุณเปลี่ยนแปลง

ในตัวอย่างโค้ดต่อไปนี้ การใช้หัวข้อย่อยที่กำหนดเองจะมีสีเริ่มต้นเป็นสีแดงซึ่งจะเปลี่ยนเป็นสีเทาเมื่อมีการแตะปุ่ม

Kotlin

class MainActivity : AppCompatActivity() {

    // Keeping the span as a field.
    val bulletSpan = BulletPointSpan(color = Color.RED)

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val spannable = SpannableString("Text is spantastic")
        // Setting the span to the bulletSpan field.
        spannable.setSpan(
            bulletSpan,
            0, 4,
            Spanned.SPAN_INCLUSIVE_INCLUSIVE
        )
        styledText.setText(spannable)
        button.setOnClickListener {
            // Change the color of the mutable span.
            bulletSpan.color = Color.GRAY
            // Color doesn't change until invalidate is called.
            styledText.invalidate()
        }
    }
}

Java

public class MainActivity extends AppCompatActivity {

    private BulletPointSpan bulletSpan = new BulletPointSpan(Color.RED);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        SpannableString spannable = new SpannableString("Text is spantastic");
        // Setting the span to the bulletSpan field.
        spannable.setSpan(bulletSpan, 0, 4, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
        styledText.setText(spannable);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Change the color of the mutable span.
                bulletSpan.setColor(Color.GRAY);
                // Color doesn't change until invalidate is called.
                styledText.invalidate();
            }
        });
    }
}

ใช้ฟังก์ชันส่วนขยาย KTX ของ Android

นอกจากนี้ Android KTX ยังมีฟังก์ชันส่วนขยายที่ทําให้ใช้งานช่วงได้ง่ายขึ้น ดูข้อมูลเพิ่มเติมได้ที่เอกสารประกอบของแพ็กเกจ androidx.core.text