دهانه ها

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

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

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

یک فاصله ایجاد و اعمال کنید

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

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

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

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

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

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

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

از آنجایی که دامنه با استفاده از Spannable.SPAN_EXCLUSIVE_INCLUSIVE تنظیم می‌شود.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 استفاده می‌شود، دامنه متن بیشتری را شامل می‌شود.
شکل 2. در هنگام استفاده از Spannable.SPAN_EXCLUSIVE_INCLUSIVE دهانه برای گنجاندن متن اضافی گسترش می‌یابد.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)»
شکل 3. متن با دهانه های متعدد: ForegroundColorSpan(Color.RED) و StyleSpan(BOLD) .

انواع دامنه اندروید

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

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

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

گستره هایی که بر ظاهر متن تأثیر می گذارد

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

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

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

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

گستره هایی که بر پاراگراف ها تأثیر می گذارد

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

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

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

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

کاتلین

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

جاوا

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

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

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

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

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

کاتلین

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

می توانید رفتار این کلاس را با اجرای یک تست AndroidJUnit آزمایش کنید و موارد زیر را بررسی کنید:

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

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

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

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

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

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

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

TextView.setText() حاوی بارهای اضافی متعددی است که span ها را متفاوت مدیریت می کند. به عنوان مثال، می توانید یک شی متنی 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 نگهداری می شود اکنون دارای نشانه گذاری قابل تغییر و متن غیرقابل تغییر است. برای به‌روزرسانی دامنه، متن را به‌عنوان Spannable بازیابی کنید و سپس در صورت نیاز، دهانه‌ها را به‌روزرسانی کنید.

هنگامی که گستره ها را ضمیمه، جدا می کنید یا تغییر موقعیت می دهید، 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 را تنظیم کنید. هنگامی که RecyclerView شما یک آیتم جدید را به ViewHolder شما متصل می کند، از ایجاد شی اضافی جلوگیری می کند.

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

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