ספאנים הם אובייקטי סימון רבי עוצמה שניתן להשתמש בהם כדי לעצב טקסט
ברמת התו או הפסקה. על ידי צירוף גבולות לאובייקטי טקסט, אפשר לשנות
טקסט במגוון דרכים, כולל הוספת צבע, הפיכת הטקסט ללחיץ,
לשנות את גודל הטקסט ולצייר טקסט באופן מותאם אישית. גם ספרדים יכולים
לשנות את המאפיינים של TextPaint
, לצייר על
Canvas
, ושינוי של פריסת הטקסט.
ב-Android יש כמה סוגים של טווחי טקסט שמכסים מגוון של טקסטים נפוצים של עיצובים. אתם יכולים גם ליצור טווחי זמן משלכם כדי להחיל עיצוב בהתאמה אישית.
יצירה והחלה של טווח
כדי ליצור span, אפשר להשתמש באחת מהכיתות שמפורטות בטבלה הבאה. הסיווגים משתנים בהתאם לחוסר היכולת של הטקסט עצמו לשנות, תגי העיצוב ניתנים לשינוי, ומבנה הנתונים הבסיסי מכיל את נתוני span.
דרגה | טקסט שניתן לשנות | תגי עיצוב שניתנים לשינוי | מבנה הנתונים |
---|---|---|---|
SpannedString |
לא | לא | מערך לינארי |
SpannableString |
לא | כן | מערך לינארי |
SpannableStringBuilder |
כן | כן | עץ הפוגות |
כל שלוש המחלקות מרחיבים את Spanned
גרפי. SpannableString
ו-SpannableStringBuilder
מאריכים גם את
הממשק של Spannable
.
כך קובעים באיזה מהם להשתמש:
- אם לא משנים את הטקסט או את תגי העיצוב לאחר היצירה, אפשר להשתמש
SpannedString
- אם אתם צריכים לחבר מספר קטן של מרווחים לאובייקט טקסט אחד
הטקסט עצמו הוא לקריאה בלבד, צריך להשתמש ב-
SpannableString
. - אם אתם צריכים לשנות טקסט לאחר היצירה שלו וצריך לצרף spans כדי
את הטקסט, יש להשתמש ב-
SpannableStringBuilder
. - אם אתם צריכים לצרף מספר גדול של מקטעי טקסט לאובייקט טקסט, ללא קשר
אם הטקסט עצמו הוא לקריאה בלבד, יש להשתמש ב-
SpannableStringBuilder
.
כדי להחיל span, צריך לקרוא ל-setSpan(Object _what_, int _start_, int _end_, int
_flags_)
על אובייקט Spannable
. הפרמטר what מתייחס לטווח
על הטקסט, והפרמטרים start ו-end מציינים את החלק
של הטקסט שעליו רוצים להחיל את הטווח.
אם מוסיפים טקסט בתוך גבולות של טווח, המרווח יתרחב אוטומטית ל-
לכלול את הטקסט שהוזן. כשמוסיפים טקסט ב-span
גבולות – כלומר, באינדקס התחלה או בסוף, הדגלים
קובע אם המרווח מתרחב כדי לכלול את הטקסט שיוכנס. כדאי להשתמש
ה
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
, הטווח
מתרחבת כדי לכלול טקסט שנוסף בגבולות ה-span, כפי שמוצג
בדוגמה הבאה:
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)");
ניתן לצרף מספר רכיבי span לאותו הטקסט. הדוגמה הבאה ממחישה איך כדי ליצור טקסט מודגש ואדום:
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 span
ב-Android יש יותר מ-20 סוגים של טווחים חבילת android.text.style. מערכת Android מסווגת את האזורים בשתי דרכים עיקריות:
- איך ה-span משפיע על הטקסט: טווח יכול להשפיע על המראה או על הטקסט מדדים.
- היקף של היקף: חלק מהטווחים אפשר להחיל על תווים ספציפיים, ואילו אחרים צריך להחיל על פסקה שלמה.
בקטעים הבאים נרחיב על הקטגוריות האלה.
שוליים שמשפיעים על מראה הטקסט
רכיבי span מסוימים שחלים ברמת התווים משפיעים על מראה הטקסט, למשל
לשנות את צבע הטקסט או את צבע הרקע ולהוסיף קו תחתון או קו חוצה. האלה
מרחיב את
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
, מחלקות המשנה משפיעות על מראה הטקסט בתו
ברמה.
שוליים שמשפיעים על פסקאות
מקש span יכול להשפיע גם על הטקסט ברמת הפסקה, למשל שינוי
יישור או את השוליים של קטע טקסט. שוליים שמשפיעים על פסקאות שלמות
להטמיע את 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);
יצירת span בהתאמה אישית
אם אתם זקוקים לפונקציות נוספות מאלו שמסופקות באפליקציית Android הקיימת מתפרשים על פני טווח מותאם אישית. כשאתם מטמיעים את הטווח שלכם, האם הטווח משפיע על הטקסט ברמת התווים או ברמת הפסקה, וגם האם היא משפיעה על הפריסה או המראה של הטקסט. המידע הזה עוזר לך לקבוע אילו מחלקות בסיס אפשר להרחיב ואילו ממשקים ייתכן שיהיה צורך. ליישם. תוכלו להיעזר בטבלה הבאה:
תרחיש | מחלקה או ממשק |
---|---|
הטווח משפיע על הטקסט ברמת התווים. | CharacterStyle |
הטווח משפיע על מראה הטקסט. | UpdateAppearance |
הטווח משפיע על מדדי טקסט. | UpdateLayout |
הטווח משפיע על הטקסט ברמת הפסקה. | ParagraphStyle |
לדוגמה, אם אתם צריכים להטמיע טווח מותאם אישית שמשנה את גודל הטקסט
צבע, מרחיבים RelativeSizeSpan
. באמצעות ירושה, RelativeSizeSpan
מתאר את CharacterStyle
ומיישם את שני הממשקים של Update
. מאחר ש
הכיתה כבר מספקת קריאות חוזרות (callbacks) עבור 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.
שיטות מומלצות לשימוש ב-span
יש כמה דרכים חסכוניות בזיכרון להגדיר טקסט ב-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
יש עכשיו תגי עיצוב שניתנים לשינוי וגם
טקסט שלא ניתן לשינוי. כדי לעדכן את ה-span, צריך לאחזר את הטקסט בתור Spannable
ואז
מעדכנים את המרווחים לפי הצורך.
כשמחברים טווחים, מנתקים אותם או ממקמים אותם מחדש, TextView
באופן אוטומטי
כדי לשקף את השינוי בטקסט. אם אתם משנים מאפיין פנימי
של טווח קיים, קוראים לפונקציה invalidate()
כדי לבצע שינויים הקשורים למראה, או
requestLayout()
כדי לבצע שינויים הקשורים למדדים.
הגדרה של טקסט ב-TextView כמה פעמים
במקרים מסוימים, למשל בזמן שימוש
RecyclerView.ViewHolder
כדאי להשתמש שוב ב-TextView
ולהגדיר את הטקסט מספר פעמים. על ידי
ברירת המחדל, לא משנה אם מגדירים את BufferType
, TextView
יוצר
עותק של האובייקט CharSequence
ושומר אותו בזיכרון. זה הופך את כל
TextView
עדכונים באופן מכוון – אי אפשר לעדכן את המקור
אובייקט CharSequence
כדי לעדכן את הטקסט. המשמעות היא שבכל פעם שמגדירים
טקסט, TextView
יוצר אובייקט חדש.
אם רוצים לקחת יותר שליטה על התהליך הזה ולהימנע מהעצם המיותר
יש לך אפשרות ליישם
Spannable.Factory
ושינוי מברירת המחדל
newSpannable()
במקום ליצור אובייקט טקסט חדש, אפשר להפעיל Cast ולהחזיר את האובייקט הקיים
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
ואי אפשר להפעיל Cast שלו אל Spannable
, מה שגורם ל-newSpannable()
לזרוק
ClassCastException
אחרי שמבטלים את newSpannable()
, מבקשים מ-TextView
להשתמש בפונקציה Factory
החדשה:
Kotlin
textView.setSpannableFactory(spannableFactory)
Java
textView.setSpannableFactory(spannableFactory);
מגדירים את האובייקט Spannable.Factory
פעם אחת, מיד אחרי שמקבלים הפניה
TextView
. אם משתמשים ב-RecyclerView
, צריך להגדיר את האובייקט Factory
קודם ניפוח מספר הצפיות. כך נמנעת יצירת אובייקטים נוספת כאשר
RecyclerView
מקשר פריט חדש אל ViewHolder
.
שינוי מאפייני טווח פנימיים
אם אתם צריכים לשנות רק מאפיין פנימי של טווח שניתן לשינוי, כמו
צבע תבליט בטווח תבליטים מותאם אישית, תוכל להימנע מהתקורה מלהתקשר
setText()
מספר פעמים על ידי שמירה של הפניה ל-span בזמן יצירתו.
אם צריך לשנות את הטווח, אפשר לשנות את ההפניה ואז
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(); } }); } }
שימוש בפונקציות התוסף Android KTX
Android KTX מכיל גם פונקציות תוספים שמאפשרות עבודה עם span יותר קל. לקבלת מידע נוסף, אפשר לעיין במסמכי התיעוד של androidx.core.text חבילה.