Span

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

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

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

สร้างและใช้ Span

หากต้องการสร้างช่วง คุณสามารถใช้คลาสใดคลาสหนึ่งที่แสดงในตารางต่อไปนี้ คลาสจะแตกต่างกันไปตามว่าข้อความเองเปลี่ยนแปลงได้หรือไม่ มาร์กอัปข้อความเปลี่ยนแปลงได้หรือไม่ และโครงสร้างข้อมูลพื้นฐานใดที่มีข้อมูลช่วง

ชั้น ข้อความที่เปลี่ยนแปลงได้ มาร์กอัปที่เปลี่ยนแปลงได้ โครงสร้างข้อมูล
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 ระบุส่วน ของข้อความที่คุณใช้ช่วง

หากแทรกข้อความภายในขอบเขตของช่วง ช่วงจะขยายโดยอัตโนมัติเพื่อ รวมข้อความที่แทรก เมื่อแทรกข้อความที่ขอบเขตของช่วง กล่าวคือ ที่ดัชนีเริ่มต้นหรือสิ้นสุด พารามิเตอร์flags จะกำหนดว่าช่วงจะขยายเพื่อรวมข้อความที่แทรกหรือไม่ ใช้แฟล็ก 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 มี Span มากกว่า 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 span ที่มีอยู่ คุณสามารถใช้ Custom Span ได้ เมื่อใช้ช่วงของคุณเอง ให้ตัดสินใจว่าช่วงจะส่งผลต่อข้อความที่ระดับอักขระหรือระดับย่อหน้า และจะส่งผลต่อเลย์เอาต์หรือลักษณะที่ปรากฏของข้อความหรือไม่ ซึ่งจะช่วยให้คุณ พิจารณาได้ว่าคลาสฐานใดที่ขยายได้และอินเทอร์เฟซใดที่อาจต้อง นำไปใช้ โปรดใช้ตารางต่อไปนี้เพื่ออ้างอิง

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

เช่น หากต้องการใช้ช่วงที่กำหนดเองซึ่งแก้ไขขนาดและสีของข้อความ ให้ขยาย RelativeSizeSpan ผ่านการรับค่า RelativeSizeSpan จะขยาย CharacterStyle และใช้ 2 Update อินเทอร์เฟซ เนื่องจากคลาสนี้มีฟังก์ชันเรียกกลับสำหรับ updateDrawState และ updateMeasureState อยู่แล้ว คุณจึงลบล้างฟังก์ชันเรียกกลับเหล่านี้เพื่อใช้ลักษณะการทำงานที่กำหนดเองได้ โค้ดต่อไปนี้สร้าง Span ที่กำหนดเองซึ่งขยาย 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 ช่วยให้คุณทั้งตั้งค่าช่วงและดึงช่วงจากข้อความได้ เมื่อทดสอบ ให้ใช้การทดสอบ JUnit ของ Android เพื่อยืนยันว่าได้เพิ่มช่วงที่ถูกต้อง ในตำแหน่งที่ถูกต้อง แอปตัวอย่างการจัดรูปแบบข้อความ มีช่วงที่ใช้มาร์กอัปกับหัวข้อย่อยโดยการแนบ 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 และตรวจสอบสิ่งต่อไปนี้

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

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

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

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

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

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

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

TextView.setText() มีการโอเวอร์โหลดหลายรายการที่จัดการช่วงแตกต่างกัน ตัวอย่างเช่น คุณสามารถ ตั้งค่าออบเจ็กต์ข้อความ 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 เก็บไว้จะมีมาร์กอัปที่เปลี่ยนแปลงได้และข้อความที่เปลี่ยนแปลงไม่ได้ หากต้องการอัปเดต Span ให้ดึงข้อความเป็น Spannable แล้ว อัปเดต Span ตามที่ต้องการ

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

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

ในบางกรณี เช่น เมื่อใช้ RecyclerView.ViewHolder คุณอาจต้องการนำ TextView กลับมาใช้ซ้ำและตั้งค่าข้อความหลายครั้ง โดยค่าเริ่มต้น ไม่ว่าคุณจะตั้งค่า BufferType หรือไม่ก็ตาม TextView จะสร้างสำเนาของออบเจ็กต์ CharSequence และเก็บไว้ในหน่วยความจำ ซึ่งจะทำให้TextViewการอัปเดตCharSequenceทั้งหมดเป็นไปโดยเจตนา คุณจะอัปเดตออบเจ็กต์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 เพียงครั้งเดียวหลังจากได้รับข้อมูลอ้างอิงถึง 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();
            }
        });
    }
}

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

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