Span

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

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

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

สถานการณ์ คลาสหรืออินเทอร์เฟซ
ช่วงจะส่งผลต่อข้อความในระดับอักขระ 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 ช่วยให้คุณทั้งตั้งค่าช่วงและดึงช่วงจากข้อความได้ เมื่อทดสอบ ให้ใช้การทดสอบ 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 เก็บไว้จะมีมาร์กอัปที่เปลี่ยนแปลงได้และ ข้อความที่เปลี่ยนแปลงไม่ได้ หากต้องการอัปเดตช่วง ให้ดึงข้อความเป็น Spannable แล้ว อัปเดตช่วงตามที่ต้องการ

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

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

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

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

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