دهانه ها

روش نوشتن را امتحان کنید
Jetpack Compose ابزار رابط کاربری پیشنهادی برای اندروید است. یاد بگیرید که چگونه از متن در Compose استفاده کنید.

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

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

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

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

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

هر سه کلاس از رابط Spanned ارث بری می‌کنند. SpannableString و SpannableStringBuilder نیز از رابط Spannable ارث بری می‌کنند.

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

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

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

اگر متنی را درون مرزهای یک span وارد کنید، span به طور خودکار گسترش می‌یابد تا متن درج شده را در بر بگیرد. هنگام درج متن در مرزهای span - یعنی در شاخص‌های شروع یا پایان - پارامتر flags تعیین می‌کند که آیا span برای شامل شدن متن درج شده گسترش می‌یابد یا خیر. از پرچم 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

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

کاتلین

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

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

کاتلین

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

انواع اسپن اندروید

اندروید بیش از 20 نوع span را در پکیج android.text.style ارائه می‌دهد. اندروید spanها را به دو روش اصلی دسته‌بندی می‌کند:

  • چگونه طول فاصله (span) بر متن تأثیر می‌گذارد: یک طول فاصله می‌تواند بر ظاهر متن یا معیارهای متن تأثیر بگذارد.
  • دامنه‌ی طول (span scope): برخی از طول‌ها را می‌توان روی تک تک کاراکترها اعمال کرد، در حالی که برخی دیگر باید روی کل پاراگراف اعمال شوند.
تصویری که دسته بندی های مختلف span را نشان می دهد
شکل 4. دسته بندی های span های اندروید.

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

فاصله‌هایی که بر ظاهر متن تأثیر می‌گذارند

برخی از spanهایی که در سطح کاراکتر اعمال می‌شوند، بر ظاهر متن تأثیر می‌گذارند، مانند تغییر رنگ متن یا پس‌زمینه و اضافه کردن زیرخط یا خط‌خوردگی. این spanها از کلاس 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

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

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

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

مثال کد زیر یک RelativeSizeSpan ایجاد می‌کند که اندازه متن را 50٪ افزایش می‌دهد:

کاتلین

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 که بر معیارهای متن تأثیر می‌گذارد، باعث می‌شود که یک شیء مشاهده‌گر، متن را برای طرح‌بندی و رندر صحیح، مجدداً اندازه‌گیری کند - برای مثال، تغییر اندازه متن ممکن است باعث شود کلمات در خطوط مختلف ظاهر شوند. اعمال span قبلی باعث اندازه‌گیری مجدد، محاسبه مجدد طرح‌بندی متن و ترسیم مجدد متن می‌شود.

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

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

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

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

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

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

کاتلین

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 که روی یک پاراگراف اعمال شده است.

ایجاد دهانه‌های سفارشی

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

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

برای مثال، اگر نیاز به پیاده‌سازی یک span سفارشی دارید که اندازه و رنگ متن را تغییر می‌دهد، RelativeSizeSpan را ارث‌بری کنید. از طریق ارث‌بری، RelativeSizeSpan CharacterStyle ارث‌بری می‌کند و دو رابط Update را پیاده‌سازی می‌کند. از آنجایی که این کلاس از قبل callbackهایی برای updateDrawState و updateMeasureState ارائه می‌دهد، می‌توانید این callbackها را برای پیاده‌سازی رفتار سفارشی خود بازنویسی کنید. کد زیر یک 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);
    }
}

این مثال نحوه ایجاد یک span سفارشی را نشان می‌دهد. شما می‌توانید با اعمال RelativeSizeSpan و ForegroundColorSpan به متن، به همان نتیجه برسید.

استفاده از محدوده تست

رابط کاربری Spanned به شما امکان می‌دهد هم spanها را تنظیم کنید و هم spanها را از متن بازیابی کنید. هنگام آزمایش، یک تست JUnit اندروید پیاده‌سازی کنید تا تأیید کنید که spanهای صحیح در مکان‌های صحیح اضافه شده‌اند. برنامه نمونه Text Styling شامل یک 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 مراجعه کنید.

تست دهانه‌های سفارشی

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

شما می‌توانید رفتار این کلاس را با پیاده‌سازی یک تست AndroidJUnit و بررسی موارد زیر، آزمایش کنید:

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

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

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

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

بهترین شیوه‌ها برای استفاده از دهانه‌ها

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

بدون تغییر متن زیرین، یک span را اضافه یا جدا کنید

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

کاتلین

textView.setText(spannableObject)

جاوا

textView.setText(spannableObject);

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

برای نشان دادن اینکه spanها باید قابل تغییر باشند، می‌توانید از 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ها را در صورت نیاز به‌روزرسانی کنید.

وقتی 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 ایجاد کند.

پس از override کردن newSpannable() ، به TextView بگویید که از Factory جدید استفاده کند:

کاتلین

textView.setSpannableFactory(spannableFactory)

جاوا

textView.setSpannableFactory(spannableFactory);

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

تغییر ویژگی‌های دهانه داخلی

اگر نیاز دارید فقط یک ویژگی داخلی یک span قابل تغییر، مانند رنگ گلوله در یک span سفارشی را تغییر دهید، می‌توانید با نگه داشتن یک ارجاع به span هنگام ایجاد آن، از سربار ناشی از فراخوانی چندین بار setText() جلوگیری کنید. هنگامی که نیاز به تغییر span دارید، می‌توانید ارجاع را تغییر داده و سپس بسته به نوع ویژگی که تغییر داده‌اید، 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();
            }
        });
    }
}

استفاده از توابع افزونه KTX اندروید

اندروید KTX همچنین شامل توابع افزونه‌ای است که کار با spanها را آسان‌تر می‌کند. برای کسب اطلاعات بیشتر، به مستندات بسته androidx.core.text مراجعه کنید.