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 بالتسلسل جميع السبانات التي تقع ضمن النطاق المعروض، مستدعيًا طرق updateDrawState وupdateMeasureState.
CharacterStyle هي الفئة الأساسية للسبانات التي تؤثر على أحرف فردية بغض النظر عن موضعها في الأسطر. تشمل ForegroundColorSpan (لون النص)، RelativeSizeSpan (الحجم النسبي)، StyleSpan (عريض ومائل)، UnderlineSpan (تسطير)، وغيرها. تُطبق سبانات الأحرف على كل حرف في النطاق المحدد بشكل فردي.
ParagraphStyle هي واجهة للسبانات التي تؤثر على فقرات كاملة. الممثل الأكثر شهرة هو AlignmentSpan، الذي يقوم بمحاذاة الفقرة بأكملها إلى اليسار أو الوسط أو اليمين. يجب أن تغطي سبانات الفقرة الفقرة بأكملها، وإلا يتجاهل Android تطبيقها. يرجع هذا القيد إلى أن المحاذاة أو المسافة البادئة يكون لها معنى فقط لكتلة نص كاملة.
أعلام Span هي أربعة ثوابت تحدد سلوك السبان عند إدراج أو حذف نص على حدود نطاقه. يحتفظ SPAN_EXCLUSIVE_EXCLUSIVE بالسبان نشطًا فقط داخل الحدود الأصلية، يوسعه SPAN_INCLUSIVE_INCLUSIVE عند إضافة نص على الحدود. يوفر SPAN_EXCLUSIVE_INCLUSIVE وSPAN_INCLUSIVE_EXCLUSIVE سلوكًا مختلطًا لبداية ونهاية النطاق.
| العلم | الإدراج على اليسار | الإدراج على اليمين |
|---|---|---|
| SPAN_EXCLUSIVE_EXCLUSIVE | لا يتضمن | لا يتضمن |
| SPAN_INCLUSIVE_INCLUSIVE | يتضمن | يتضمن |
| SPAN_EXCLUSIVE_INCLUSIVE | لا يتضمن | يتضمن |
| SPAN_INCLUSIVE_EXCLUSIVE | يتضمن | لا يتضمن |
اختيار العلامة الصحيح أمر بالغ الأهمية للنص القابل للتحرير في EditText، حيث يمكن للمستخدم إدراج وحذف الأحرف. لـ TextView للقراءة فقط، يُستخدم عادةً SPAN_EXCLUSIVE_EXCLUSIVE — يُطبق النمط فقط على النطاق الأصلي ولا يتوسع مع التغييرات البرمجية.
يوفر Android SDK أكثر من 25 فئة سبان مدمجة تغطي معظم مهام تصميم النص. تنفذ كل فئة واجهة 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 في موضع اللمس. سبان اللون يجعل الرابط مرئيًا للمستخدم.
يبدأ العمل مع SpannableString بإنشاء مثيل من سلسلة نصية وتطبيق السبانات بالتسلسل عبر طريقة setSpan(). تقبل الطريقة أربع معاملات: كائن السبان وموضع البداية وموضع النهاية والأعلام. بعد تعيين جميع السبانات، يتم تمرير الكائن إلى TextView عبر setText().
لننشئ سلسلة حيث تكون الكلمة الأولى حمراء ومكبرة. لهذا، نستخدم ForegroundColorSpan للون وRelativeSizeSpan للمقياس. يتم تطبيق كلا السبان على نفس النطاق بشكل مستقل — ترتيب استدعاء 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 تلقائيًا جميع السبانات أثناء العرض — لا حاجة لاستدعاءات إضافية. RelativeSizeSpan بمضاعف 1.5f يزيد حجم الخط بنسبة 50% بالنسبة للحجم الأساسي.
Html.fromHtml() ينشئ كائن Spanned من سلسلة HTML، لكن مجموعة العلامات المدعومة محدودة. تمنح SpannableString تحكمًا كاملاً في كل سمة دون قيود HTML. إذا كنت بحاجة إلى تحويل HTML إلى سبانات ثم إضافة أنماط مخصصة، يمكنك استخدام 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) وأحمر (من السبان). هذا الأسلوب مفيد عند العمل مع محتوى الخادم حيث يتم تحديد جزء من التنسيق في HTML ويضاف جزء آخر من جانب العميل برمجيًا.
تسمح SpannableString بتطبيق عدد غير محدود من السبانات على نفس النطاق أو نطاق متداخل. يعتبر دمج السبانات ميزة رئيسية مقارنة بترميز HTML، حيث يمكن أن تتعارض العلامات المتداخلة. السبانات مستقلة ويتم تطبيقها بالتسلسل أثناء العرض.
على سبيل المثال، يمكنك جعل جزء من النص عريضًا وأحمر وقابلًا للنقر في نفس الوقت. للقيام بذلك، أنشئ ثلاثة سبانات — StyleSpan وForegroundColorSpan وClickableSpan — وطبق كل منها على نفس النطاق. ترتيب التطبيق لا يؤثر على النتيجة، حيث أن كل سبان مسؤول عن السمة الخاصة به من النص.
إذا تداخلت سبانات من أنواع مختلفة جزئيًا، يعمل كل منها بشكل مستقل ضمن حدوده الخاصة. ForegroundColorSpan على النطاق 0–10 وStyleSpan(BOLD) على النطاق 5–15 سيعطيان نصًا أحمر عريضًا على المقطع 5–10 وعريضًا فقط على 10–15. لا تنشأ أي تعارضات لأن كل سبان يعدل السمة الخاصة به أثناء العرض.
طريقة getSpans(int start, int end, Class type) تُرجع مصفوفة من السبانات ضمن النطاق المحدد. هذا مفيد للتحقق من الأنماط المطبقة بالفعل أو لإزالة سبانات محددة. باستخدام nextSpanTransition()، يمكنك التكرار عبر حدود تغيير السبانات — وهذا هو الأساس لتطبيقات TextView المخصصة التي تحتاج إلى معرفة أين يتغير النمط.
SpannableStringBuilder هي فئة للبناء التدريجي للنص المصمم مع القدرة على إدراج واستبدال وحذف الأجزاء. بخلاف SpannableString التي تُنشأ من سلسلة جاهزة وغير قابلة للتغيير، يسمح Builder بإضافة أجزاء النص بالتسلسل وتعيين الأنماط أثناء التنقل. هذا هو الخيار المثالي للرسائل المركبة: السجلات والدردشات وعناوين الأخبار مع تسميات ديناميكية.
ينفذ Builder واجهتي Spannable وEditable، مما يجعله متوافقًا مع EditText. يمكن للمستخدم تحرير النص، ويتم الحفاظ على الأنماط ونقلها بشكل صحيح عند إدراج أحرف جديدة. SpannableString، كونها غير قابلة للتغيير، غير مناسبة للحقول القابلة للتحرير.
طريقة append() تُرجع builder نفسه، مما يسمح بتسلسل الاستدعاءات. بعد إضافة النص، يتم تطبيق السبانات عبر 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
النص «جديد » ملون بالأزرق، و«تعليق» بالرمادي. عند إدراج أحرف إضافية بينهما، لن يؤثر السبان على النص الجديد بفضل أعلام EXCLUSIVE. يقوم Builder تلقائيًا بضبط المؤشرات الداخلية عند التعديل.
يؤثر استخدام عدد كبير من السبانات على أداء عرض TextView. كل سبان يستدعي طريقة updateDrawState() أو updateMeasureState() عند كل إعادة رسم. يُوصى بتحديد عدد السبانات لكل TextView إلى 50–100 لأداء مريح على الأجهزة المتوسطة. السبانات التي تغير حجم النص (RelativeSizeSpan, AbsoluteSizeSpan) تتطلب إعادة حساب التخطيط عند كل تغيير، وهو أكثر تكلفة بكثير من سبانات اللون أو التسطير فقط.
TextAppearanceSpan يسمح بتطبيق مجموعة كاملة من الأنماط من مورد XML android:textAppearance باستدعاء setSpan() واحد. بدلاً من ثلاثة سبانات منفصلة (لون وحجم وخط)، يُستخدم TextAppearanceSpan واحد مع مرجع للنمط. يقلل ذلك من عدد الكائنات ويبسط الصيانة — تغيير النمط في المورد ينطبق تلقائيًا على جميع النصوص التي تستخدم هذا السبان.
إنشاء مثيل جديد من السبان لكل setSpan() يسبب حملًا إضافيًا على جامع القمامة. من الأمثل إنشاء كائنات سبان ثابتة إذا تم استخدامها بشكل متكرر. على سبيل المثال، يمكن تخزين ForegroundColorSpan(Color.RED) في كائن companion وإعادة استخدامه. ومع ذلك، يجب إنشاء السبانات ذات الحالة (ClickableSpan بمعالجات مختلفة) بشكل فردي لكل حالة.
لتشخيص مشكلات أداء السبانات، استخدم Layout Inspector في Android Studio وملف تعريف GPU. إذا كان TextView مع العديد من السبانات يتباطأ بشكل ملحوظ أثناء التمرير، ففكر في استبدال بعض السبانات بأنماط ثابتة عبر TextAppearanceSpan أو تقليل عدد السبانات من خلال دمج السمات في تطبيقات UpdateAppearance مخصصة.
الأسئلة الشائعة
String هي سلسلة أحرف غير قابلة للتغيير بدون دعم للأنماط. تخزن SpannableString نفس الأحرف ولكنها تحتوي بالإضافة إلى ذلك على مصفوفة من كائنات Span مع معلومات التنسيق. يحدد TextView نوع CharSequence الذي تم تمريره ويطبق السبانات على النطاقات المقابلة أثناء العرض. String يتجاهل أي سمات نمط ويعرض كنص عادي.
طريقة removeSpan(Object span) تزيل سبانًا محددًا. للتنظيف الكامل، استدعِ getSpans(0, length, Object::class.java) التي تُرجع مصفوفة من جميع السبانات، ثم أزل كلًا منها عبر removeSpan. بدلاً من ذلك، أنشئ SpannableString(text.toString()) جديدًا بدون سبانات. SpannableStringBuilder لديه طريقة clear() التي تزيل النص والسبانات معًا.
SpannableString يعمل في EditText، لكن للنص القابل للتحرير يُفضل SpannableStringBuilder الذي ينفذ Editable. يتطلب EditText واجهة Editable لتتبع التغييرات. إذا مررت SpannableString إلى EditText، سيتم عرض النص بالأنماط، لكن أثناء التحرير سيحوله Android إلى Editable، مما قد يعيد تعيين بعض السبانات.
في Jetpack Compose، لا تُستخدم سبانات Android SDK مباشرة. بدلاً من ذلك، يوفر Compose AnnotatedString، المكافئ الخاص به لـ SpannableString بقدرات مماثلة: SpanStyle لأنماط الأحرف الفردية وParagraphStyle للفقرات. التحويل من SpannableString إلى AnnotatedString ممكن عبر buildAnnotatedString مع التكرار على السبانات.
أنشئ فئة ترث من CharacterStyle وتجاوز طريقة updateDrawState(TextPaint tp). داخل الطريقة، قم بتعديل خصائص TextPaint: اللون وسماكة الخط والتأثيرات. للتغييرات المترية، استخدم UpdateLayout أو MetricAffectingSpan. تُطبق السبانات المخصصة عبر setSpan() تمامًا مثل السبانات المدمجة.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.