ספרד

רוצה לנסות את שיטת הכתיבה?
'Jetpack פיתוח נייטיב' היא ערכת הכלים המומלצת לממשק המשתמש ל-Android. איך משתמשים בטקסט בניסוח האוטומטי.

ספאנים הם אובייקטי סימון רבי עוצמה שניתן להשתמש בהם כדי לעצב טקסט ברמת התו או הפסקה. על ידי צירוף גבולות לאובייקטי טקסט, אפשר לשנות טקסט במגוון דרכים, כולל הוספת צבע, הפיכת הטקסט ללחיץ, לשנות את גודל הטקסט ולצייר טקסט באופן מותאם אישית. גם ספרדים יכולים לשנות את המאפיינים של TextPaint, לצייר על Canvas, ושינוי של פריסת הטקסט.

ב-Android יש כמה סוגים של טווחי טקסט שמכסים מגוון של טקסטים נפוצים של עיצובים. אתם יכולים גם ליצור טווחי זמן משלכם כדי להחיל עיצוב בהתאמה אישית.

יצירה והחלה של טווח

כדי ליצור span, אפשר להשתמש באחת מהכיתות שמפורטות בטבלה הבאה. הסיווגים משתנים בהתאם לחוסר היכולת של הטקסט עצמו לשנות, תגי העיצוב ניתנים לשינוי, ומבנה הנתונים הבסיסי מכיל את נתוני span.

דרגה טקסט שניתן לשנות תגי עיצוב שניתנים לשינוי מבנה הנתונים
SpannedString לא לא מערך לינארי
SpannableString לא כן מערך לינארי
SpannableStringBuilder כן כן עץ הפוגות

כל שלוש המחלקות מרחיבים את Spanned גרפי. SpannableString ו-SpannableStringBuilder מאריכים גם את הממשק של Spannable.

כך קובעים באיזה מהם להשתמש:

  • אם לא משנים את הטקסט או את תגי העיצוב לאחר היצירה, אפשר להשתמש SpannedString
  • אם אתם צריכים לחבר מספר קטן של מרווחים לאובייקט טקסט אחד הטקסט עצמו הוא לקריאה בלבד, צריך להשתמש ב-SpannableString.
  • אם אתם צריכים לשנות טקסט לאחר היצירה שלו וצריך לצרף spans כדי את הטקסט, יש להשתמש ב-SpannableStringBuilder.
  • אם אתם צריכים לצרף מספר גדול של מקטעי טקסט לאובייקט טקסט, ללא קשר אם הטקסט עצמו הוא לקריאה בלבד, יש להשתמש ב-SpannableStringBuilder.

כדי להחיל span, צריך לקרוא ל-setSpan(Object _what_, int _start_, int _end_, int _flags_) על אובייקט Spannable. הפרמטר what מתייחס לטווח על הטקסט, והפרמטרים start ו-end מציינים את החלק של הטקסט שעליו רוצים להחיל את הטווח.

אם מוסיפים טקסט בתוך גבולות של טווח, המרווח יתרחב אוטומטית ל- לכלול את הטקסט שהוזן. כשמוסיפים טקסט ב-span גבולות – כלומר, באינדקס התחלה או בסוף, הדגלים קובע אם המרווח מתרחב כדי לכלול את הטקסט שיוכנס. כדאי להשתמש ה Spannable.SPAN_EXCLUSIVE_INCLUSIVE סימון להכללה של הטקסט שהוזן, ולהשתמש בסימון Spannable.SPAN_EXCLUSIVE_EXCLUSIVE כדי לא לכלול את הטקסט שהוזן.

הדוגמה הבאה מראה איך לצרף ForegroundColorSpan אל string:

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, הטווח מתרחבת כדי לכלול טקסט שנוסף בגבולות ה-span, כפי שמוצג בדוגמה הבאה:

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

ניתן לצרף מספר רכיבי span לאותו הטקסט. הדוגמה הבאה ממחישה איך כדי ליצור טקסט מודגש ואדום:

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 span

ב-Android יש יותר מ-20 סוגים של טווחים חבילת android.text.style. מערכת Android מסווגת את האזורים בשתי דרכים עיקריות:

  • איך ה-span משפיע על הטקסט: טווח יכול להשפיע על המראה או על הטקסט מדדים.
  • היקף של היקף: חלק מהטווחים אפשר להחיל על תווים ספציפיים, ואילו אחרים צריך להחיל על פסקה שלמה.
תמונה שמוצגים בה קטגוריות שונות של טווח
איור 4. קטגוריות של Android מתפרסות על פני טווח.

בקטעים הבאים נרחיב על הקטגוריות האלה.

שוליים שמשפיעים על מראה הטקסט

רכיבי span מסוימים שחלים ברמת התווים משפיעים על מראה הטקסט, למשל לשנות את צבע הטקסט או את צבע הרקע ולהוסיף קו תחתון או קו חוצה. האלה מרחיב את 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);
תמונה שמציגה את השימוש ב-{/7}SizeSpan
איור 6. הטקסט הוגדל באמצעות RelativeSizeSpan

החלת טווח שמשפיע על מדדי טקסט גורמת לאובייקט צפייה למדוד מחדש את הטקסט כדי לקבל פריסה ורינדור נכונים - לדוגמה, גודל הטקסט עלול לגרום למילים להופיע בשורות שונות. המערכת מחילה את מפעיל מדידה מחדש, חישוב מחדש של פריסת הטקסט ושרטוט מחדש של של הטקסט.

מרווחי זמן שמשפיעים על מדדי טקסט מאריכים את הכיתה MetricAffectingSpan, מחלקה מופשטת שמאפשרת למחלקות משנה להגדיר את ההשפעה של הטווח על מדידת הטקסט באמצעות מתן גישה אל TextPaint. כי MetricAffectingSpan נמשך CharacterSpan, מחלקות המשנה משפיעות על מראה הטקסט בתו ברמה.

שוליים שמשפיעים על פסקאות

מקש span יכול להשפיע גם על הטקסט ברמת הפסקה, למשל שינוי יישור או את השוליים של קטע טקסט. שוליים שמשפיעים על פסקאות שלמות להטמיע את 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 הוחל על פסקה.

יצירת span בהתאמה אישית

אם אתם זקוקים לפונקציות נוספות מאלו שמסופקות באפליקציית Android הקיימת מתפרשים על פני טווח מותאם אישית. כשאתם מטמיעים את הטווח שלכם, האם הטווח משפיע על הטקסט ברמת התווים או ברמת הפסקה, וגם האם היא משפיעה על הפריסה או המראה של הטקסט. המידע הזה עוזר לך לקבוע אילו מחלקות בסיס אפשר להרחיב ואילו ממשקים ייתכן שיהיה צורך. ליישם. תוכלו להיעזר בטבלה הבאה:

תרחיש מחלקה או ממשק
הטווח משפיע על הטקסט ברמת התווים. CharacterStyle
הטווח משפיע על מראה הטקסט. UpdateAppearance
הטווח משפיע על מדדי טקסט. UpdateLayout
הטווח משפיע על הטקסט ברמת הפסקה. ParagraphStyle

לדוגמה, אם אתם צריכים להטמיע טווח מותאם אישית שמשנה את גודל הטקסט צבע, מרחיבים RelativeSizeSpan. באמצעות ירושה, RelativeSizeSpan מתאר את CharacterStyle ומיישם את שני הממשקים של Update. מאחר ש הכיתה כבר מספקת קריאות חוזרות (callbacks) עבור 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.

כדי לבדוק את האינטראקציות של לוח הציור, אפשר ללגלג על הקנבס ולהעביר את הקופסה להתנגד drawLeadingMargin() ואימות שהשיטות הנכונות נקראות עם הקוד הנכון .

אפשר למצוא עוד דגימות בדיקה ב- BulletPointSpanTest.

שיטות מומלצות לשימוש ב-span

יש כמה דרכים חסכוניות בזיכרון להגדיר טקסט ב-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 ואז מעדכנים את המרווחים לפי הצורך.

כשמחברים טווחים, מנתקים אותם או ממקמים אותם מחדש, TextView באופן אוטומטי כדי לשקף את השינוי בטקסט. אם אתם משנים מאפיין פנימי של טווח קיים, קוראים לפונקציה invalidate() כדי לבצע שינויים הקשורים למראה, או requestLayout() כדי לבצע שינויים הקשורים למדדים.

הגדרה של טקסט ב-TextView כמה פעמים

במקרים מסוימים, למשל בזמן שימוש RecyclerView.ViewHolder כדאי להשתמש שוב ב-TextView ולהגדיר את הטקסט מספר פעמים. על ידי ברירת המחדל, לא משנה אם מגדירים את BufferType, TextView יוצר עותק של האובייקט CharSequence ושומר אותו בזיכרון. זה הופך את כל TextView עדכונים באופן מכוון – אי אפשר לעדכן את המקור אובייקט CharSequence כדי לעדכן את הטקסט. המשמעות היא שבכל פעם שמגדירים טקסט, TextView יוצר אובייקט חדש.

אם רוצים לקחת יותר שליטה על התהליך הזה ולהימנע מהעצם המיותר יש לך אפשרות ליישם Spannable.Factory ושינוי מברירת המחדל newSpannable() במקום ליצור אובייקט טקסט חדש, אפשר להפעיל Cast ולהחזיר את האובייקט הקיים 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 ואי אפשר להפעיל Cast שלו אל Spannable, מה שגורם ל-newSpannable() לזרוק ClassCastException

אחרי שמבטלים את newSpannable(), מבקשים מ-TextView להשתמש בפונקציה Factory החדשה:

Kotlin

textView.setSpannableFactory(spannableFactory)

Java

textView.setSpannableFactory(spannableFactory);

מגדירים את האובייקט Spannable.Factory פעם אחת, מיד אחרי שמקבלים הפניה TextView. אם משתמשים ב-RecyclerView, צריך להגדיר את האובייקט Factory קודם ניפוח מספר הצפיות. כך נמנעת יצירת אובייקטים נוספת כאשר RecyclerView מקשר פריט חדש אל ViewHolder.

שינוי מאפייני טווח פנימיים

אם אתם צריכים לשנות רק מאפיין פנימי של טווח שניתן לשינוי, כמו צבע תבליט בטווח תבליטים מותאם אישית, תוכל להימנע מהתקורה מלהתקשר setText() מספר פעמים על ידי שמירה של הפניה ל-span בזמן יצירתו. אם צריך לשנות את הטווח, אפשר לשנות את ההפניה ואז 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 מכיל גם פונקציות תוספים שמאפשרות עבודה עם span יותר קל. לקבלת מידע נוסף, אפשר לעיין במסמכי התיעוד של androidx.core.text חבילה.