بازه

امتحان کردن روش Compose
Jetpack Compose جعبه‌ابزار واسط کاربر توصیه‌شده برای Android است. با نحوه استفاده از نوشتار در Compose آشنا شوید.

«محدوده‌ها» اشیای نشانه‌گذاری قدرتمندی هستند که می‌توانید از آن‌ها برای سبک‌بندی نوشتار در سطح نویسه یا پاراگراف استفاده کنید. با پیوست کردن بازه‌ها به اشیای نوشتاری، می‌توانید نوشتار را به روش‌های مختلفی تغییر دهید، ازجمله افزودن رنگ، کلیک‌پذیر کردن نوشتار، مقیاس‌بندی اندازه نوشتار، و ترسیم نوشتار به‌صورت سفارشی. Spans همچنین می‌تواند ویژگی‌های TextPaint را تغییر دهد، روی Canvas بکشد، و چیدمان نوشتار را تغییر دهد.

‫Android چندین نوع span ارائه می‌دهد که انواع الگوهای رایج سبک‌بندی نوشتار را پوشش می‌دهند. همچنین می‌توانید برای اعمال سبک سفارشی، بازه‌های خودتان را ایجاد کنید.

ایجاد و اعمال کردن span

برای ایجاد span، می‌توانید از یکی از کلاس‌های فهرست‌شده در جدول زیر استفاده کنید. کلاس‌ها براساس اینکه خود نوشتار قابل‌تغییر است یا نه، نشانه‌گذاری نوشتار قابل‌تغییر است یا نه، و اینکه چه ساختار داده‌ای زیربنایی حاوی داده‌های گستره است متفاوت هستند.

کلاس نوشتار تغییرپذیر نشانه‌گذاری تغییرپذیر ساختار داده‌ها
SpannedString نه نه آرایه خطی
SpannableString نه بله آرایه خطی
SpannableStringBuilder بله بله درخت بازه‌ای

هر سه کلاس میانای Spanned را گسترش می‌دهند. ‫SpannableString و SpannableStringBuilder همچنین میانای Spannable را گسترش می‌دهند.

در اینجا نحوه تصمیم‌گیری درباره اینکه از کدام‌یک استفاده کنید آورده شده است:

  • اگر پس‌از ایجاد، متن یا نشانه‌گذاری را اصلاح نمی‌کنید، از SpannedString استفاده کنید.
  • اگر لازم است تعداد کمی از گستره‌ها را به یک شیء نوشتاری پیوست کنید و خود نوشتار فقط خواندنی است، از SpannableString استفاده کنید.
  • اگر لازم است نوشتار را پس‌از ایجاد اصلاح کنید و باید بازه‌هایی را به نوشتار پیوست کنید، از SpannableStringBuilder استفاده کنید.
  • اگر لازم است تعداد زیادی span را به شیء نوشتاری پیوست کنید، صرف‌نظر از اینکه خود نوشتار فقط خواندنی است یا نه، از SpannableStringBuilder استفاده کنید.

برای اعمال کردن span، setSpan(Object _what_, int _start_, int _end_, int _flags_) را در شیء Spannable فراخوانی کنید. پارامتر what به بازه‌ای اشاره دارد که به نوشتار اعمال می‌کنید، و پارامترهای start و end نشان‌دهنده بخشی از نوشتار است که بازه را به آن اعمال می‌کنید.

اگر نوشتاری را در داخل مرزهای یک محدوده درج کنید، آن محدوده به‌طور خودکار گسترش می‌یابد تا نوشتار درج‌شده را دربرگیرد. هنگام درج نوشتار در مرزهای محدوده—یعنی در نمایه‌های شروع یا پایان—پارامتر پرچم‌ها تعیین می‌کند که آیا محدوده برای گنجاندن نوشتار درج‌شده گسترش می‌یابد یا نه. از پرچم Spannable.SPAN_EXCLUSIVE_INCLUSIVE برای گنجاندن نوشتار درج‌شده استفاده کنید و از Spannable.SPAN_EXCLUSIVE_EXCLUSIVE برای مستثنی کردن نوشتار درج‌شده استفاده کنید.

مثال زیر نشان می‌دهد که چگونه می‌توان یک ForegroundColorSpan را به یک رشته پیوست کرد:

کاتلین

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

جاوا

SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!");
spannable.setSpan(
    new ForegroundColorSpan(Color.RED),
    8, // start
    12, // end
    Spannable.SPAN_EXCLUSIVE_INCLUSIVE
);
تصویری که نوشتاری خاکستری را نشان می‌دهد که بخشی از آن قرمز است.
شکل ۱. نوشتار با ForegroundColorSpan سبک‌بندی شده است.

چون محدوده بااستفاده از Spannable.SPAN_EXCLUSIVE_INCLUSIVE تنظیم شده است، محدوده گسترش می‌یابد تا نوشتار درج‌شده در مرزهای محدوده را دربرگیرد، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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

جاوا

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 استفاده می‌شود، چگونه محدوده متن بیشتری را دربرمی‌گیرد.
شکل ۲. هنگام استفاده از Spannable.SPAN_EXCLUSIVE_INCLUSIVE، محدوده گسترش می‌یابد تا نوشتار اضافی را دربرگیرد.

می‌توانید چندین بازه را به یک نوشتار پیوست کنید. مثال زیر نشان می‌دهد که چگونه نوشتاری پررنگ و قرمز ایجاد کنید:

کاتلین

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
)

جاوا

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)`
شکل ۳. نوشتار با چند بخش: ForegroundColorSpan(Color.RED) و StyleSpan(BOLD).

انواع بازه Android

Android بیش‌از ۲۰ نوع فاصله در بسته android.text.style ارائه می‌دهد. Android بازه‌ها را به دو روش اصلی دسته‌بندی می‌کند:

  • نحوه تأثیر span بر نوشتار: span می‌تواند بر ظاهر نوشتار یا سنجه‌های نوشتار تأثیر بگذارد.
  • محدوده Span: برخی‌از spanها را می‌توان برای نویسه‌های تکی اعمال کرد، درحالی‌که برخی دیگر باید برای کل پاراگراف اعمال شوند.
تصویری که دسته‌های مختلف هرزنامه را نشان می‌دهد
شکل ۴. دسته‌های Android spans.

بخش‌های زیر این دسته‌ها را با جزئیات بیشتری شرح می‌دهند.

بخش‌هایی که بر ظاهر نوشتار تأثیر می‌گذارند

برخی‌از بازه‌هایی که در سطح نویسه اعمال می‌شوند بر ظاهر نوشتار تأثیر می‌گذارند، مثلاً تغییر دادن رنگ نوشتار یا پس‌زمینه و افزودن زیرخط یا خط‌خوردگی. این بخش‌ها کلاس CharacterStyle را گسترش می‌دهند.

نمونه کد زیر نحوه اعمال UnderlineSpan را برای زیرخط‌دار کردن نوشتار نشان می‌دهد:

کاتلین

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

جاوا

SpannableString string = new SpannableString("Text with underline span");
string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
تصویری که نحوه زیرخط‌دار کردن نوشتار را بااستفاده از `UnderlineSpan` نشان می‌دهد
شکل ۵. نوشتار بااستفاده از UnderlineSpan زیرخط‌دار شده است.

فاصله‌هایی که فقط بر ظاهر نوشتار تأثیر می‌گذارند، باعث بازترسیم نوشتار می‌شوند بدون اینکه محاسبه مجدد چیدمان را فعال کنند. این بازه‌ها UpdateAppearance را پیاده‌سازی می‌کنند و CharacterStyle را گسترش می‌دهند. زیرکلاس‌های CharacterStyle با فراهم کردن دسترسی به به‌روزرسانی TextPaint، نحوه ترسیم نوشتار را تعریف می‌کنند.

بخش‌هایی که بر سنجه‌های نوشتار تأثیر می‌گذارند

سایر گستره‌هایی که در سطح نویسه اعمال می‌شوند بر سنجه‌های نوشتار، مانند ارتفاع خط و اندازه نوشتار، تأثیر می‌گذارند. این بازه‌ها کلاس MetricAffectingSpan را گسترش می‌دهند.

نمونه کد زیر یک RelativeSizeSpan ایجاد می‌کند که اندازه نوشتار را ۵۰٪ افزایش می‌دهد:

کاتلین

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

جاوا

SpannableString string = new SpannableString("Text with relative size span");
string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
تصویری که کاربرد RelativeSizeSpan را نشان می‌دهد
شکل ۶. نوشتار بااستفاده از RelativeSizeSpan بزرگ‌تر شده است.

اعمال کردن بازه‌ای که بر سنجه‌های نوشتار تأثیر می‌گذارد باعث می‌شود شیء مشاهده‌کننده نوشتار را برای چیدمان و پرداز درست دوباره اندازه‌گیری کند—برای مثال، تغییر دادن اندازه نوشتار ممکن است باعث شود کلمات در خطوط مختلفی نشان داده شوند. اعمال span قبلی باعث اندازه‌گیری مجدد، محاسبه مجدد چیدمان نوشتار، و بازترسیم نوشتار می‌شود.

بخش‌هایی که بر سنجه‌های نوشتار تأثیر می‌گذارند، کلاس MetricAffectingSpan را گسترش می‌دهند، یک کلاس انتزاعی که به زیرکلاس‌ها اجازه می‌دهد با ارائه دسترسی به TextPaint، نحوه تأثیر بخش بر اندازه‌گیری نوشتار را تعریف کنند. ازآنجایی‌که MetricAffectingSpan CharacterStyle را گسترش می‌دهد، زیرکلاس‌ها بر ظاهر نوشتار در سطح نویسه تأثیر می‌گذارند.

محدوده‌هایی که بر پاراگراف‌ها تأثیر می‌گذارند

یک span همچنین می‌تواند بر متن در سطح پاراگراف تأثیر بگذارد، مانند تغییر دادن تراز یا حاشیه یک بلوک متن. فاصله‌هایی که بر کل پاراگراف‌ها تأثیر می‌گذارند ParagraphStyle را پیاده‌سازی می‌کنند. برای استفاده از این بازه‌ها، آن‌ها را به کل پاراگراف پیوست می‌کنید، به‌جز نویسه خط جدید پایانی. اگر سعی کنید گستره پاراگراف را روی چیزی غیراز کل پاراگراف اعمال کنید، Android اصلاً گستره را اعمال نمی‌کند.

شکل ۸ نشان می‌دهد Android چگونه پاراگراف‌ها را در نوشتار جدا می‌کند.

شکل ۷. در Android، پاراگراف‌ها با نویسه خط جدید (\n) به‌پایان می‌رسند.

نمونه کد زیر QuoteSpan را برای پاراگراف اعمال می‌کند. توجه داشته باشید که اگر span را به هر موقعیتی غیراز ابتدا یا انتهای پاراگراف پیوست کنید، Android اصلاً سبک را اعمال نمی‌کند.

کاتلین

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

جاوا

spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
تصویری که نمونه‌ای از QuoteSpan را نشان می‌دهد
شکل ۸. QuoteSpan روی پاراگراف اعمال شد.

ایجاد بازه‌های سفارشی

اگر به عملکردی بیشتر از آنچه در گستره‌های موجود Android ارائه شده است نیاز دارید، می‌توانید گستره سفارشی پیاده‌سازی کنید. هنگام پیاده‌سازی span خود، تصمیم بگیرید که آیا span شما بر نوشتار در سطح نویسه یا سطح پاراگراف تأثیر می‌گذارد و همچنین آیا بر چیدمان یا ظاهر نوشتار تأثیر می‌گذارد یا نه. این کار به شما کمک می‌کند تعیین کنید کدام کلاس‌های پایه را می‌توانید گسترش دهید و کدام میانارها را ممکن است لازم باشد پیاده‌سازی کنید. از جدول زیر به‌عنوان مرجع استفاده کنید:

سناریو کلاس یا میانای کاربری
محدوده شما بر نوشتار در سطح نویسه تأثیر می‌گذارد. CharacterStyle
محدوده شما بر ظاهر نوشتار تأثیر می‌گذارد. UpdateAppearance
محدوده شما بر سنجه‌های نوشتار تأثیر می‌گذارد. UpdateLayout
محدوده شما بر نوشتار در سطح پاراگراف تأثیر می‌گذارد. ParagraphStyle

برای مثال، اگر لازم است بازه سفارشی‌ای را پیاده‌سازی کنید که اندازه و رنگ نوشتار را اصلاح می‌کند، RelativeSizeSpan را گسترش دهید. ازطریق ارث‌بری، RelativeSizeSpan CharacterStyle را گسترش می‌دهد و دو میانای Update را پیاده‌سازی می‌کند. ازآنجایی‌که این کلاس قبلاً فراخوان‌های برگشتی برای updateDrawState و updateMeasureState ارائه می‌دهد، می‌توانید این فراخوان‌های برگشتی را لغو کنید تا رفتار سفارشی خود را پیاده‌سازی کنید. کد زیر یک span سفارشی ایجاد می‌کند که RelativeSizeSpan را گسترش می‌دهد و callback updateDrawState را لغو می‌کند تا رنگ TextPaint را تنظیم کند:

کاتلین

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

جاوا

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 را پیاده‌سازی کنید تا تأیید کنید که گستره‌های صحیح در مکان‌های صحیح اضافه شده‌اند. برنامه نمونه «سبک‌بندی نوشتار» حاوی یک span است که با پیوست کردن BulletPointSpan به نوشتار، نشانه‌گذاری را روی نقطه‌های گلوله‌ای اعمال می‌کند. نمونه کد زیر نحوه آزمایش اینکه آیا نکات مهم به‌صورت موردانتظار نشان داده می‌شوند یا نه را نشان می‌دهد:

کاتلین

@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())
}

جاوا

@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 آزمایش کنید، و موارد زیر را بررسی کنید:

  • اگر محدوده را به‌درستی اعمال کنید، یک نقطه گلوله‌ای با اندازه و رنگ مشخص‌شده در بوم ظاهر می‌شود و فضای مناسبی بین حاشیه چپ و نقطه گلوله‌ای وجود دارد.
  • اگر span را اعمال نکنید، هیچ‌کدام از رفتارهای سفارشی نشان داده نمی‌شود.

می‌توانید پیاده‌سازی این آزمایش‌ها را در نمونه TextStyling در GitHub ببینید.

می‌توانید تعاملات Canvas را با شبیه‌سازی بوم، ارسال شیء شبیه‌سازی‌شده به روش drawLeadingMargin()، و تأیید اینکه روش‌های صحیح با پارامترهای صحیح فراخوانی می‌شوند، آزمایش کنید.

می‌توانید نمونه‌های بیشتری از آزمایش گستره را در BulletPointSpanTest پیدا کنید.

روال‌های مطلوب برای استفاده از بازه‌ها

بسته به نیازهایتان، چند روش کارآمد ازنظر مصرف حافظه برای تنظیم نوشتار در TextView وجود دارد.

پیوست کردن یا جدا کردن بازه بدون تغییر دادن نوشتار زیرین

TextView.setText() حاوی چندین اضافه‌بار است که با گستره‌ها به‌طور متفاوتی رفتار می‌کند. برای مثال، می‌توانید با کد زیر شیء نوشتاری Spannable را تنظیم کنید:

کاتلین

textView.setText(spannableObject)

جاوا

textView.setText(spannableObject);

هنگام فراخوانی این سربارگذاری setText()،‏ TextView نسخه‌ای از Spannable شما را به‌عنوان SpannedString ایجاد می‌کند و آن را به‌عنوان CharSequence در حافظه نگه می‌دارد. این یعنی نوشتار و بازه‌ها تغییرناپذیر هستند، بنابراین وقتی لازم است نوشتار یا بازه‌ها را به‌روز کنید، شیء Spannable جدیدی ایجاد کنید و setText() را دوباره فراخوانی کنید، که این کار همچنین باعث اندازه‌گیری مجدد و ترسیم مجدد طرح‌بندی می‌شود.

برای نشان دادن اینکه بازه‌ها باید تغییرپذیر باشند، می‌توانید به‌جای آن از setText(CharSequence text, TextView.BufferType type) استفاده کنید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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

جاوا

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 به‌طور خودکار به‌روز می‌شود تا تغییرات نوشتار را منعکس کند. اگر مشخصه داخلی یک span موجود را تغییر می‌دهید، برای ایجاد تغییرات مربوط به ظاهر، invalidate() را فراخوانی کنید یا برای ایجاد تغییرات مربوط به سنجه، requestLayout() را فراخوانی کنید.

تنظیم نوشتار در TextView به‌دفعات

در برخی موارد، مثلاً هنگام استفاده از RecyclerView.ViewHolder، ممکن است بخواهید از TextView مجدداً استفاده کنید و نوشتار را چندین بار تنظیم کنید. به‌طور پیش‌فرض، صرف‌نظر از اینکه BufferType را تنظیم کرده باشید یا نه، TextView نسخه‌ای از شیء CharSequence ایجاد می‌کند و آن را در حافظه نگه می‌دارد. این کار باعث می‌شود همه TextView به‌روزرسانی‌ها عمدی باشد—نمی‌توانید CharSequence شیء اصلی را برای به‌روزرسانی نوشتار به‌روز کنید. این یعنی هربار که نوشتار جدیدی تنظیم می‌کنید، TextView شیء جدیدی ایجاد می‌کند.

اگر می‌خواهید کنترل بیشتری بر این فرایند داشته باشید و از ایجاد شیء اضافی اجتناب کنید، می‌توانید Spannable.Factory خودتان را پیاده‌سازی کنید و newSpannable() را لغو کنید. به‌جای ایجاد شیء نوشتاری جدید، می‌توانید CharSequence موجود را به‌عنوان Spannable ارسال و برگردانید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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 جدید استفاده کند:

کاتلین

textView.setSpannableFactory(spannableFactory)

جاوا

textView.setSpannableFactory(spannableFactory);

شیء Spannable.Factory را یک‌بار، درست پس‌از دریافت ارجاع به TextView خود تنظیم کنید. اگر از RecyclerView استفاده می‌کنید، وقتی برای اولین‌بار نماهایتان را گسترش می‌دهید، شیء Factory را تنظیم کنید. این کار از ایجاد شیء اضافی هنگام اتصال مورد جدیدی به ViewHolder توسط RecyclerView جلوگیری می‌کند.

تغییر دادن مشخصه‌های بازه داخلی

اگر فقط باید یک مشخصه داخلی از یک محدوده قابل‌تغییر را تغییر دهید، مانند رنگ گلوله در یک محدوده گلوله سفارشی، می‌توانید با نگه‌داشتن ارجاع به محدوده در هنگام ایجاد آن، از سربار ناشی از فراخوانی مکرر setText() جلوگیری کنید. وقتی لازم است بازه را اصلاح کنید، می‌توانید مرجع را اصلاح کنید و سپس invalidate() یا requestLayout() را در TextView فراخوانی کنید، بسته به نوع مشخصه‌ای که تغییر داده‌اید.

در نمونه کد زیر، پیاده‌سازی سفارشی علامت نقطه‌ای رنگ پیش‌فرض قرمز دارد که با تک‌ضرب زدن روی دکمه به خاکستری تغییر می‌کند:

کاتلین

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()
        }
    }
}

جاوا

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 مراجعه کنید.