SpannableString کلاسی از Android SDK در بسته android.text است که امکان اعمال چندین سبک به بخشهای مختلف یک رشته متنی در TextView را فراهم میکند. بر خلاف نشانهگذاری HTML، SpannableString در سطح اشیاء Span کار میکند و نمایش بصری متن را مدیریت میکند: رنگ، اندازه، شکل قلم، زیرخط و عناصر تعاملی. به گفته Google Developers، SpannableString در اجزای سیستمی Android برای قالببندی لینکها استفاده میشود. این روش اصلی سبکدهی متن بدون نیاز به کتابخانههای شخص ثالث است.
نکات اصلی
SpannableString کلاسی از Android است که رابط Spannable را پیادهسازی میکند، متنی را همراه با مجموعهای از اشیاء Span که نمایش بصری را مدیریت میکنند ذخیره میکند. بر خلاف String معمولی، SpannableString امکان پیوست کردن ویژگیهای سبک به محدودههای خاصی از کاراکترها را فراهم میکند: بخشی از متن را قرمز کنید، فونت عنوان را بزرگتر کنید یا یک لینک قابل کلیک در داخل پاراگراف اضافه کنید.
این کلاس در بسته android.text قرار دارد و از API Level 1 در دسترس است. SpannableString غیرقابل تغییر است — پس از ایجاد، ساختار آن ثابت میشود و جایگزینی متن نیاز به ایجاد یک شیء جدید دارد. برای ویرایش پویا از SpannableStringBuilder استفاده میشود که از درج و حذف کاراکترها بدون از دست دادن سبکها پشتیبانی میکند.
CharSequence رابط پایه برای دادههای متنی است که توسط String، StringBuilder و SpannableString پیادهسازی میشود. تفاوت اصلی SpannableString با String در پشتیبانی از پیوست کردن اشیاء دلخواه به زیررشته است. TextView رابط Spannable را تشخیص میدهد و در هنگام ترسیم، اشیاء span را به بخشهای مربوطه متن اعمال میکند. اگر یک String معمولی به TextView داده شود، هیچ سبکی اعمال نخواهد شد.
SpannableString متن را به عنوان آرایهای از char[] و یک آرایه جداگانه از اشیاء span با ابرداده درباره موقعیت شروع و پایان ذخیره میکند. هنگام فراخوانی setSpan(what, start, end, flags)، شیء what به همراه اطلاعات محدوده در لیست ذخیره میشود. هنگام ترسیم، TextView به ترتیب همه Spanهایی را که در محدوده نمایش قرار میگیرند اعمال میکند و متدهای updateDrawState و updateMeasureState را فراخوانی میکند.
CharacterStyle کلاس پایه برای Spanهایی است که روی کاراکترهای جداگانه بدون توجه به موقعیت آنها در خطوط تأثیر میگذارند. این شامل ForegroundColorSpan (رنگ متن)، RelativeSizeSpan (اندازه نسبی)، StyleSpan (پررنگ و کج)، UnderlineSpan (زیرخط) و موارد دیگر است. Character-spanها به هر کاراکتر در محدوده مشخص شده به طور جداگانه اعمال میشوند.
ParagraphStyle رابطی برای Spanهایی است که روی پاراگرافهای کامل تأثیر میگذارند. شناختهشدهترین نماینده AlignmentSpan است که کل پاراگراف را به چپ، مرکز یا راست تراز میکند. Paragraph-spanها باید کل پاراگراف را پوشش دهند، در غیر این صورت Android اعمال آنها را نادیده میگیرد. این محدودیت به این دلیل است که تراز یا تورفتگی فقط برای یک بلوک کامل متن معنی دارد.
پرچمهای Span چهار ثابت هستند که رفتار Span را هنگام درج یا حذف متن در مرزهای محدوده آن تعیین میکنند. SPAN_EXCLUSIVE_EXCLUSIVE Span را فقط در داخل مرزهای اصلی فعال نگه میدارد، SPAN_INCLUSIVE_INCLUSIVE آن را هنگام افزودن متن به مرزها گسترش میدهد. SPAN_EXCLUSIVE_INCLUSIVE و SPAN_INCLUSIVE_EXCLUSIVE رفتار ترکیبی برای شروع و پایان محدوده ارائه میدهند.
| پرچم | درج از چپ | درج از راست |
|---|---|---|
| SPAN_EXCLUSIVE_EXCLUSIVE | شامل نمیشود | شامل نمیشود |
| SPAN_INCLUSIVE_INCLUSIVE | شامل میشود | شامل میشود |
| SPAN_EXCLUSIVE_INCLUSIVE | شامل نمیشود | شامل میشود |
| SPAN_INCLUSIVE_EXCLUSIVE | شامل میشود | شامل نمیشود |
انتخاب صحیح پرچم برای متن Editable در EditText حیاتی است، جایی که کاربر میتواند کاراکترها را درج و حذف کند. برای TextView فقط خواندنی معمولاً از SPAN_EXCLUSIVE_EXCLUSIVE استفاده میشود — سبک فقط به محدوده اصلی اعمال میشود و با تغییرات برنامهای گسترش نمییابد.
Android SDK بیش از 25 کلاس داخلی Span ارائه میدهد که بیشتر نیازهای سبکدهی متن را پوشش میدهند. هر کلاس رابط CharacterStyle یا ParagraphStyle را پیادهسازی میکند و پارامترها را از طریق سازنده میپذیرد. همه کلاسها در بسته android.text.style قرار دارند و بدون نیاز به وابستگیهای اضافی در دسترس هستند.
ForegroundColorSpan رنگ متن را برای محدوده مشخص شده تنظیم میکند و رنگ را به صورت int میپذیرد. BackgroundColorSpan پسزمینه زیر متن را رنگ میکند که برای برجستهسازی عبارتهای جستجو مفید است. AbsoluteSizeSpan اندازه دقیق فونت را بر حسب پیکسل تعیین میکند، RelativeSizeSpan ضریبی نسبت به اندازه پایه متن در TextView است.
StyleSpan ثابتهای Typeface.NORMAL، Typeface.BOLD، Typeface.ITALIC یا BOLD_ITALIC را میپذیرد و شکل کاراکترها را تغییر میدهد. UnderlineSpan زیرخط اضافه میکند، StrikethroughSpan خطخورده ایجاد میکند. SuperscriptSpan و SubscriptSpan اندیسهای بالا و پایین ایجاد میکنند. TypefaceSpan امکان تنظیم فونت سفارشی از طریق شیء Typeface را برای محدودهای از متن فراهم میکند.
ClickableSpan کلاس انتزاعی برای ایجاد بخشهای قابل کلیک متن است. هنگام کلیک، متد onClick() فراخوانی میشود. برای کارکرد کلیکها، TextView باید setMovementMethod(LinkMovementMethod.getInstance()) داشته باشد. URLSpan زیرکلاس ClickableSpan برای هایپرلینکها با باز کردن خودکار مرورگر است. ClickableSpan اغلب با ForegroundColorSpan ترکیب میشود تا لینک به صورت بصری با رنگ آبی برجسته شود.
val spannable = SpannableString("Open developer.android.com")
spannable.setSpan(
URLSpan("https://developer.android.com"),
9, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
spannable.setSpan(
ForegroundColorSpan(Color.BLUE),
9, 31, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
textView.movementMethod = LinkMovementMethod.getInstance()
بدون LinkMovementMethod کلیکهای روی URLSpan پردازش نخواهند شد. MovementMethod مسئول رهگیری رویدادهای لمسی و جستجوی ClickableSpan در موقعیت لمس است. Span رنگی لینک را برای کاربر قابل مشاهده میکند.
کار با SpannableString با ایجاد نمونه از یک رشته متنی و اعمال متوالی Spanها از طریق متد setSpan() شروع میشود. این متد چهار پارامتر میپذیرد: شیء Span، موقعیت شروع، موقعیت پایان و پرچمها. پس از تنظیم همه Spanها، شیء از طریق setText() به TextView داده میشود.
رشتهای ایجاد میکنیم که کلمه اول آن قرمز و با اندازه بزرگتر باشد. برای این کار از ForegroundColorSpan برای رنگ و RelativeSizeSpan برای مقیاس استفاده میشود. هر دو Span به یک محدوده مستقل از یکدیگر اعمال میشوند — ترتیب فراخوانی setSpan اهمیتی ندارد.
val text = "Header: remaining text"
val spannable = SpannableString(text)
val colorSpan = ForegroundColorSpan(Color.RED)
val sizeSpan = RelativeSizeSpan(1.5f)
spannable.setSpan(colorSpan, 0, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(sizeSpan, 0, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannable
کاراکترهای موقعیت 0 تا 9 هر دو سبک را همزمان دریافت میکنند. TextView به طور خودکار همه Spanها را هنگام ترسیم اعمال میکند — هیچ فراخوانی اضافی لازم نیست. RelativeSizeSpan با ضریب 1.5f اندازه فونت را 50٪ نسبت به پایه افزایش میدهد.
Html.fromHtml() یک شیء Spanned از رشته HTML ایجاد میکند، اما مجموعه تگهای پشتیبانیشده محدود است. SpannableString کنترل کامل روی هر ویژگی بدون محدودیتهای HTML میدهد. اگر نیاز به تبدیل HTML به Span با افزودن بعدی سبکهای سفارشی دارید، میتوانید از Html.fromHtml() به عنوان پایه استفاده کنید و سپس با setSpan() تکمیل کنید.
val htmlText = Html.fromHtml(
"<b>Important:</b> check the data",
Html.FROM_HTML_MODE_LEGACY
)
val spannable = SpannableString(htmlText)
spannable.setSpan(
ForegroundColorSpan(Color.RED),
0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
textView.text = spannable
نتیجه — متن "مهم:" پررنگ (از HTML) و قرمز (از Span) خواهد بود. این رویکرد هنگام کار با محتوای سرور که بخشی از قالببندی با HTML تنظیم شده و بخشی در سمت مشتری به صورت برنامهای اضافه میشود، راحت است.
SpannableString امکان اعمال تعداد نامحدودی Span روی یک محدوده مشترک یا همپوشان را فراهم میکند. ترکیب Spanها مزیت کلیدی نسبت به نشانهگذاری HTML است، جایی که تگهای تودرتو ممکن است با هم تضاد داشته باشند. Spanها مستقل هستند و به ترتیب هنگام ترسیم اعمال میشوند.
به عنوان مثال، میتوان یک بخش از متن را همزمان پررنگ، قرمز و قابل کلیک کرد. برای این کار سه Span ایجاد میشود — StyleSpan، ForegroundColorSpan و ClickableSpan — و هر کدام به یک محدوده اعمال میشود. ترتیب اعمال تأثیری بر نتیجه ندارد، زیرا هر Span مسئول ویژگی خاص خود از متن است.
اگر Spanهای انواع مختلف به طور جزئی همپوشانی داشته باشند، هر کدام به طور مستقل در مرزهای خود کار میکنند. ForegroundColorSpan در محدوده 0-10 و StyleSpan(BOLD) در محدوده 5-15 متن قرمز پررنگ در بخش 5-10 و فقط پررنگ در بخش 10-15 ایجاد میکنند. هیچ تضادی ایجاد نمیشود، زیرا هر Span ویژگی خود را هنگام ترسیم تغییر میدهد.
متد getSpans(int start, int end, Class type) آرایهای از Spanهای محدوده مشخص شده را برمیگرداند. این برای بررسی اینکه چه سبکهایی اعمال شده یا حذف Spanهای خاص مفید است. با nextSpanTransition() میتوان در مرزهای تغییر Span پیمایش کرد — این اساس کار TextViewهای سفارشی است که باید بدانند سبک کجا تغییر میکند.
SpannableStringBuilder کلاسی برای ساخت گام به گام متن سبکدهی شده با قابلیت درج، جایگزینی و حذف بخشها است. بر خلاف SpannableString که از یک رشته آماده ایجاد میشود و غیرقابل تغییر است، Builder امکان اضافه کردن متوالی بخشهای متن و اختصاص سبک به آنها در حال اجرا را فراهم میکند. این انتخاب ایدهآل برای پیامهای ترکیبی است: لاگها، چتها، عنوانهای خبری با برچسبهای پویا.
Builder رابطهای Spannable و Editable را پیادهسازی میکند که آن را با EditText سازگار میکند. کاربر میتواند متن را ویرایش کند و سبکها حفظ میشوند و در هنگام درج کاراکترهای جدید به درستی جابهجا میشوند. SpannableString به دلیل غیرقابل تغییر بودن برای فیلدهای قابل ویرایش مناسب نیست.
متد append() خود builder را برمیگرداند که امکان زنجیرهای از فراخوانیها را فراهم میکند. پس از افزودن متن، Spanها از طریق setSpan() اعمال میشوند. موقعیتها نسبت به طول فعلی builder مشخص میشوند. Insert() و replace() نیز برای کنترل دقیقتر روی محتوا در دسترس هستند.
val builder = SpannableStringBuilder()
.append("New ")
.append("comment")
val blue = ForegroundColorSpan(Color.BLUE)
val gray = ForegroundColorSpan(Color.GRAY)
builder.setSpan(blue, 0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
builder.setSpan(gray, 6, 17, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = builder
متن "جدید " به رنگ آبی و "نظر" به رنگ خاکستری است. هنگام درج کاراکترهای اضافی بین آنها، Span به لطف پرچمهای EXCLUSIVE متن جدید را تحت تأثیر قرار نمیدهد. Builder به طور خودکار ایندکسهای داخلی را هنگام تغییر تصحیح میکند.
استفاده از تعداد زیادی Span بر عملکرد ترسیم TextView تأثیر میگذارد. هر Span متد updateDrawState() یا updateMeasureState() را در هر بار ترسیم مجدد فراخوانی میکند. توصیه میشود تعداد Spanها در یک TextView به 50-100 برای عملکرد راحت در دستگاههای میانرده محدود شود. Spanهایی که اندازه متن را تغییر میدهند (RelativeSizeSpan، AbsoluteSizeSpan) نیاز به محاسبه مجدد layout در هر تغییر دارند که بسیار گرانتر از Spanهای فقط رنگ یا زیرخط است.
TextAppearanceSpan امکان اعمال مجموعه کاملی از سبکها از منبع xml android:textAppearance را با یک عملیات setSpan() فراهم میکند. به جای سه Span جداگانه (رنگ، اندازه، فونت) از یک TextAppearanceSpan با ارجاع به سبک استفاده میشود. این تعداد اشیاء را کاهش میدهد و نگهداری را ساده میکند — تغییر سبک در منبع به طور خودکار به تمام متنهایی که از این Span استفاده میکنند اعمال میشود.
ایجاد نمونه جدید Span برای هر setSpan() بار اضافی بر جمعآورنده زباله ایجاد میکند. بهینه این است که اشیاء ثابت Span ایجاد کنید اگر مکرراً استفاده میشوند. به عنوان مثال، ForegroundColorSpan(Color.RED) را میتوان در companion object ذخیره و مجدداً استفاده کرد. با این حال Spanهای دارای حالت (ClickableSpan با هندلرهای مختلف) باید برای هر مورد به صورت جداگانه ایجاد شوند.
برای تشخیص مشکلات عملکرد Spanها از Layout Inspector در Android Studio و پروفایلر GPU استفاده کنید. اگر TextView با تعداد زیادی Span در هنگام اسکرول به طور محسوسی کند میشود، جایگزینی بخشی از Spanها با سبکهای ایستا از طریق TextAppearanceSpan یا کاهش تعداد Spanها با ترکیب ویژگیها در پیادهسازیهای سفارشی UpdateAppearance را در نظر بگیرید.
سوالات متداول
String دنبالهای غیرقابل تغییر از کاراکترها بدون پشتیبانی از سبکها است. SpannableString همان کاراکترها را ذخیره میکند اما علاوه بر آن شامل آرایهای از اشیاء Span با اطلاعات قالببندی است. TextView نوع CharSequence دریافتی را تشخیص میدهد و Spanها را به محدودههای مربوطه هنگام ترسیم اعمال میکند. String هر ویژگی سبکی را نادیده میگیرد و به صورت متن ساده نمایش داده میشود.
متد removeSpan(Object span) یک Span خاص را حذف میکند. برای پاکسازی کامل، getSpans(0, length, Object::class.java) را فراخوانی کنید که آرایهای از همه Spanها را برمیگرداند و سپس هر کدام را از طریق removeSpan حذف کنید. همچنین میتوانید یک SpannableString جدید بدون Span ایجاد کنید. SpannableStringBuilder متد clear() دارد که هم متن و هم Spanها را حذف میکند.
SpannableString در EditText کار میکند، اما برای متن قابل ویرایش SpannableStringBuilder که Editable را پیادهسازی میکند ترجیح داده میشود. EditText برای ردیابی تغییرات به رابط Editable نیاز دارد. اگر SpannableString به EditText داده شود، متن با سبکها نمایش داده میشود، اما هنگام ویرایش Android آن را به Editable تبدیل میکند که ممکن است برخی Spanها را بازنشانی کند.
در Jetpack Compose از Spanهای Android SDK مستقیماً استفاده نمیشود. در عوض Compose AnnotatedString را ارائه میدهد — معادل خودش با قابلیتهای مشابه: SpanStyle برای سبکهای کاراکترهای جداگانه و ParagraphStyle برای پاراگرافها. تبدیل SpannableString به AnnotatedString از طریق buildAnnotatedString با پیمایش روی Spanها امکانپذیر است.
کلاسی ایجاد کنید که از CharacterStyle ارثبری کند و متد updateDrawState(TextPaint tp) را بازنویسی کنید. داخل متد، ویژگیهای TextPaint را تغییر دهید: رنگ، ضخامت خط، افکتها. برای تغییرات متریک از UpdateLayout یا MetricAffectingSpan استفاده کنید. Spanهای سفارشی مانند Spanهای داخلی از طریق setSpan() اعمال میشوند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید