المساحات هي كائنات ترميزية فعالة يمكنك استخدامها لتصميم النص
على مستوى الحرف أو الفقرة. من خلال إرفاق امتدادات بالكائنات النصية، يمكنك تغيير
والنص بعدة طرق، منها إضافة الألوان وجعل النص قابلاً للنقر
وتغيير حجم النص ورسم النص بطريقة مخصصة. يمكن للامتدادات أيضًا
تغيير خصائص TextPaint
، الرسم على
Canvas
وتغيير تنسيق النص.
يوفر Android عدة أنواع من الفترات التي تغطي مجموعة متنوعة من النصوص الشائعة. أنماط التصميم. يمكنك أيضًا إنشاء فاصلات خاصة بك لتطبيق نمط مخصّص.
إنشاء فترة وتطبيقها
لإنشاء نطاق، يمكنك استخدام إحدى الفئات المدرجة في الجدول التالي. وتختلف هذه الفئات حسب ما إذا كان النص نفسه قابلاً للتغيير أم لا، وما إذا كان النص قابل للتغيير، وما بنية البيانات الأساسية التي تحتوي على بيانات الامتداد.
الفئة | نص قابل للتحويل | الترميز القابل للتغيير | هيكل البيانات |
---|---|---|---|
SpannedString |
لا | لا | مصفوفة خطية |
SpannableString |
لا | نعم | مصفوفة خطية |
SpannableStringBuilder |
نعم | نعم | شجرة الفاصل |
تقوم جميع الفئات الثلاث بتوسيع نطاق Spanned
من واجهة pyplot. يُوسِّع SpannableString
وSpannableStringBuilder
أيضًا
Spannable
.
إليك كيفية تحديد الأداة التي ستستخدمها:
- إذا كنت لا تعدّل النص أو الترميز بعد الإنشاء، استخدِم
SpannedString
- إذا كنت بحاجة إلى إرفاق عدد صغير من الامتدادات بكائن نص واحد
النص نفسه للقراءة فقط، استخدِم
SpannableString
. - إذا كنت بحاجة إلى تعديل النص بعد الإنشاء وكنت بحاجة إلى إرفاق نطاقات
النص، استخدم
SpannableStringBuilder
. - إذا كنت بحاجة إلى إرفاق عدد كبير من الامتدادات بكائن نص، بغض النظر
لتحديد ما إذا كان النص نفسه للقراءة فقط، استخدِم
SpannableStringBuilder
.
لتطبيق مسافة، يمكنك الاتصال بالرقم setSpan(Object _what_, int _start_, int _end_, int
_flags_)
.
على كائن Spannable
. تشير المعلمة what إلى النطاق الذي
تطبيق على النص، وتشير المعلمتان start وend إلى الجزء
من النص الذي تطبق الامتداد عليه.
إذا تم إدراج نص داخل حدود الامتداد، فسيتوسع الامتداد تلقائيًا إلى
تضمين النص المدرج. عند إدراج نص عند المسافة
الحدود، أي عند فهارس البداية أو النهاية، والعلامات
ما إذا كان الامتداد يتسع لتضمين النص المدرج أم لا. استخدام
الـ
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
علامة لتضمين النص المدرج، واستخدام
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
لاستبعاد النص المدرج.
يوضح المثال التالي كيفية إرفاق
ForegroundColorSpan
إلى
string:
Kotlin
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE )
Java
SpannableStringBuilder spannable = new SpannableStringBuilder("Text is spantastic!"); spannable.setSpan( new ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE );
بما أنّه تم ضبط المسافة باستخدام Spannable.SPAN_EXCLUSIVE_INCLUSIVE
، فإنّ النطاق
توسيع ليشمل النص المدرج عند حدود الامتداد، كما هو موضح في علامة
المثال التالي:
Kotlin
val spannable = SpannableStringBuilder("Text is spantastic!") spannable.setSpan( ForegroundColorSpan(Color.RED), 8, // start 12, // end Spannable.SPAN_EXCLUSIVE_INCLUSIVE ) spannable.insert(12, "(& fon)")
Java
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)");
يمكنك إرفاق امتدادات متعددة بالنص نفسه. يوضح المثال التالي كيف لإنشاء نص غامق وأحمر:
Kotlin
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 )
Java
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 );
أنواع نطاقات Android
ويوفّر Android أكثر من 20 نوعًا من النطاقات في android.text.style. يصنّف Android النطاقات بطريقتين أساسيتين:
- كيفية تأثير الامتداد في النص: يمكن أن يؤثر الامتداد في مظهر النص أو النص والمقاييس.
- نطاق المحيط: يمكن تطبيق بعض الامتدادات على أحرف فردية، بينما يمكن تطبيق امتدادات أخرى يجب تطبيقه على فقرة كاملة.
تصف الأقسام التالية هذه الفئات بمزيد من التفصيل.
الفواصل التي تؤثر في مظهر النص
تؤثر بعض المسافات التي تنطبق على مستوى الحرف في مظهر النص، مثل
تغيير لون النص أو الخلفية وإضافة تسطير أو خطوط يتوسطها خط. هذه
تمتد تمتد
صف واحد (CharacterStyle
).
يوضّح مثال الرمز التالي كيفية تطبيق UnderlineSpan
على تسطير.
النص:
Kotlin
val string = SpannableString("Text with underline span") string.setSpan(UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
SpannableString string = new SpannableString("Text with underline span"); string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
تؤدي المسافات التي تؤثر على مظهر النص فقط إلى إعادة رسم النص بدون
مما يؤدي إلى إعادة حساب التخطيط. تنفذ هذه الامتدادات
UpdateAppearance
وتمديد
CharacterStyle
تحدد فئات CharacterStyle
الفرعية كيفية رسم النص من خلال توفير الوصول إلى
تعديل TextPaint
الفواصل التي تؤثر في مقاييس النص
إنّ المسافات الأخرى التي يتم تطبيقها على مستوى الأحرف تؤثّر في مقاييس النص، مثل الأسطر.
الارتفاع وحجم النص. وتمتد هذه الامتدادات
MetricAffectingSpan
الصف.
ينشئ مثال التعليمة البرمجية التالي
RelativeSizeSpan
التي
زيادة حجم النص بنسبة 50٪:
Kotlin
val string = SpannableString("Text with relative size span") string.setSpan(RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
SpannableString string = new SpannableString("Text with relative size span"); string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
يؤدي تطبيق امتداد يؤثر في مقاييس النص إلى جعل كائن الملاحظة إعادة قياس النص لضمان صحة التنسيق والعرض - على سبيل المثال، تغيير قد يؤدي حجم النص إلى ظهور الكلمات في أسطر مختلفة. تطبيق ما سبق الامتداد تؤدي إلى إعادة القياس، وإعادة حساب تخطيط النص، وإعادة رسم النص.
وتؤثِّر المساحات التي تؤثّر في مقاييس النص في توسيع فئة MetricAffectingSpan
،
فئة مجردة تسمح للفئات الفرعية بتحديد كيفية تأثير الامتداد على قياس النص
من خلال توفير إذن بالوصول إلى TextPaint
. نظرًا لتمديد فترة MetricAffectingSpan
CharacterSpan
، تؤثر الفئات الفرعية في مظهر النص عند الحرف
المستوى.
امتدادات تؤثر في الفقرات
يمكن أن يؤثر الامتداد أيضًا على النص على مستوى الفقرة، مثل تغيير
المحاذاة أو هامش كتلة من النص. امتدادات تؤثر في فقرات بأكملها
تنفيذ ParagraphStyle
. إلى
استخدام هذه المسافات، فإنك ترفقها بالفقرة بأكملها، باستثناء النهاية
حرف سطر جديد. إذا حاولت تطبيق مدى فقرة على شيء آخر غير
لفقرة كاملة، فإن Android لا يطبق الامتداد على الإطلاق.
يوضّح الشكل 8 كيف يفصل Android الفقرات في النص.
يطبق مثال التعليمة البرمجية التالي
QuoteSpan
إلى فقرة. لاحظ أن
إذا قمت بتثبيت الامتداد بأي موضع غير بداية أو نهاية العلامة
فإن Android لا يطبق النمط على الإطلاق.
Kotlin
spannable.setSpan(QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
Java
spannable.setSpan(new QuoteSpan(color), 8, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
إنشاء امتدادات مخصصة
إذا كنت بحاجة إلى وظائف أكثر من تلك المتوفرة في نظام Android الحالي يمكنك تنفيذ امتداد مخصص. عند تنفيذ التباعد الخاص بك، حدد سواء كان الامتداد يؤثر على النص على مستوى الحرف أو مستوى الفقرة وكذلك ما إذا كان ذلك يؤثر في تخطيط أو مظهر النص. سيساعدك هذا الإجراء في تحديد الفئات الأساسية التي يمكنك توسيعها والواجهات التي قد تحتاج إليها تنفيذها. استخدِم الجدول التالي كمرجع:
السيناريو | الفئة أو الواجهة |
---|---|
يؤثر الامتداد في النص على مستوى الأحرف. | CharacterStyle |
يؤثر مدى الامتداد في مظهر النص. | UpdateAppearance |
يؤثر مدى اتساعك في مقاييس النص. | UpdateLayout |
يؤثر الامتداد في النص على مستوى الفقرة. | ParagraphStyle |
على سبيل المثال، إذا كنت بحاجة إلى تنفيذ مسافة مخصصة تعدل حجم النص
اللون، تمديد RelativeSizeSpan
. من خلال الاكتساب، RelativeSizeSpan
توسيع CharacterStyle
وتنفيذ واجهتَي Update
نظرًا لأن ذلك
الفئة توفر استدعاءات لـ updateDrawState
وupdateMeasureState
بالفعل،
يمكنك إلغاء عمليات الاستدعاء هذه لتنفيذ سلوكك المخصّص. تشير رسالة الأشكال البيانية
يؤدي الرمز التالي إلى إنشاء مدى مخصص يمتد إلى RelativeSizeSpan
تلغي استدعاء updateDrawState
لضبط لون TextPaint
:
Kotlin
class RelativeSizeColorSpan( size: Float, @ColorInt private val color: Int ) : RelativeSizeSpan(size) { override fun updateDrawState(textPaint: TextPaint) { super.updateDrawState(textPaint) textPaint.color = color } }
Java
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
اختبار للتحقق من إضافة المسافات الصحيحة
في المواقع الصحيحة. نموذج نمط النص
التطبيق
يشتمل على امتداد يطبق الترميز على الرموز النقطية من خلال إرفاق
BulletPointSpan
إلى النص. يوضّح مثال الرمز التالي طريقة اختبار
ما إذا كانت رموز التعداد تظهر على النحو المتوقّع:
Kotlin
@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()) }
Java
@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، تحقَّق ممّا يلي:
- إذا قمت بتطبيق الامتداد بشكل صحيح، فإن نقطة التعداد ذات الحجم المحدد اللون على لوحة الرسم، وتوجد المساحة المناسبة بين اليسار والهامش والنقطة النقطية.
- وفي حال عدم تطبيق النطاق، لن يظهر أي من السلوك المخصّص.
يمكنك الاطلاع على تنفيذ هذه الاختبارات في TextStyling نموذج على GitHub.
يمكنك اختبار تفاعلات "لوحة الرسم" من خلال السخرية من اللوحة واجتياز النموذج الوهمي.
على
drawLeadingMargin()
والتحقق من استدعاء الطرق الصحيحة
المعلَمات.
يمكنك العثور على المزيد من عينات اختبار المسافات في bulletPointSpanTest:
أفضل الممارسات لاستخدام الامتدادات
هناك عدة طرق فعالة في الذاكرة لضبط النص في TextView
، استنادًا إلى
وفقًا لاحتياجاتك.
إرفاق امتداد أو فصله بدون تغيير النص الأساسي
TextView.setText()
يحتوي على العديد من التحميلات الزائدة التي تتعامل مع الامتدادات بشكل مختلف. على سبيل المثال، يمكنك
ضبط كائن نصي Spannable
باستخدام الرمز التالي:
Kotlin
textView.setText(spannableObject)
Java
textView.setText(spannableObject);
عند استدعاء هذا الحمل الزائد لـ setText()
، ينشئ TextView
نسخة من
Spannable
باعتباره SpannedString
ويتم الاحتفاظ به في الذاكرة باعتباره CharSequence
.
هذا يعني أن النص والامتدادات غير قابلة للتغيير، لذلك عندما تحتاج إلى
تعديل النص أو الامتدادات وإنشاء عنصر Spannable
جديد واستدعاء
setText()
مرة أخرى، وهو ما يؤدي أيضًا إلى إعادة قياس وإعادة رسم
التصميم.
للإشارة إلى أن الفترات يجب أن تكون قابلة للتغيير، يمكنك بدلاً من ذلك استخدام
setText(CharSequence text, TextView.BufferType
type)
،
كما هو موضح في المثال التالي:
Kotlin
textView.setText(spannable, BufferType.SPANNABLE) val spannableText = textView.text as Spannable spannableText.setSpan( ForegroundColorSpan(color), 8, spannableText.length, SPAN_INCLUSIVE_INCLUSIVE )
Java
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
" على
التحديثات لتعكس التغيير في النص. في حال تغيير سمة داخلية
لامتداد حالي، يمكنك طلب invalidate()
لإجراء تغييرات متعلّقة بالمظهر أو
requestLayout()
لإجراء تغييرات تتعلّق بالمقياس.
تعيين نص في TextView عدة مرات
في بعض الحالات، مثل عند استخدام
RecyclerView.ViewHolder
،
يمكنك إعادة استخدام TextView
وتعيين النص عدة مرات. من
بغض النظر عمّا إذا تم ضبط BufferType
أم لا، تنشئ TextView
نسخة من كائن CharSequence
وتحتفظ بها في الذاكرة. هذا يجعل كل
تم إجراء تحديثات "TextView
" مقصودة، لا يمكنك تعديل النسخة الأصلية.
عنصر CharSequence
لتعديل النص. يعني ذلك أنه في كل مرة تقوم فيها بتعيين
نصية، تُنشئ TextView
كائنًا جديدًا.
إذا كنت تريد مزيدًا من التحكم في هذه العملية وتجنب العنصر الإضافي
إبداعك، يمكنك تنفيذ ابتكاراتك
Spannable.Factory
وإلغاء
newSpannable()
بدلاً من إنشاء كائن نص جديد، يمكنك إرسال كائن النص الحالي وإرجاعه
CharSequence
باعتباره Spannable
، كما هو موضّح في المثال التالي:
Kotlin
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
الجديدة:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
اضبط الكائن Spannable.Factory
مرة واحدة، مباشرةً بعد الحصول على إشارة إلى
TextView
إذا كنت تستخدم RecyclerView
، يمكنك ضبط الكائن Factory
عند:
إلى زيادة عدد المشاهدات أولاً. يؤدي ذلك إلى تجنُّب إنشاء عناصر إضافية عند
يربط RecyclerView
عنصرًا جديدًا بـ ViewHolder
.
تغيير سمات الامتداد الداخلي
فإذا كنت بحاجة إلى تغيير سمة داخلية فقط لنطاق قابل للتغيير، مثل السمة
لون نقطي في نطاق تعداد نقطي مخصص، يمكنك تجنب النفقات العامة في
setText()
عدة مرات من خلال الإشارة إلى الامتداد عند إنشائه.
عندما تحتاج إلى تعديل الامتداد، يمكنك تعديل المرجع ثم استدعاء
invalidate()
أو requestLayout()
على TextView
، بناءً على نوع
التي غيّرتها.
في مثال الرمز التالي، نجد أن تنفيذ الرموز النقطية المخصصة اللون الافتراضي للأحمر يتغير إلى الرمادي عند النقر على أحد الأزرار:
Kotlin
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() } } }
Java
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 لنظام التشغيل Android
يحتوي Android KTX أيضًا على وظائف امتداد تجعل العمل مع امتدادات كثيرًا. لمزيد من المعلومات، راجع وثائق androidx.core.text طرد.