תנאי הצירוף של הכתיבה

מודификаторים מאפשרים לקשט או להוסיף רכיבים ל-composable. מגבילי התאמה מאפשרים לכם לבצע את הפעולות הבאות:

  • שינוי הגודל, הפריסה, ההתנהגות והמראה של התוכן הקומפוזבילי
  • הוספת מידע, כמו תוויות נגישות
  • עיבוד קלט של משתמשים
  • הוספת אינטראקציות ברמה גבוהה, כמו הפיכת אלמנט לקליקבילי, לגלילה, לגרירה או להגדלת התצוגה שלו

משתני פונקציה הם אובייקטים רגילים של Kotlin. כדי ליצור פונקציית שינוי, קוראים לאחת מפונקציות הכיתה Modifier:

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

שתי שורות טקסט על רקע צבעוני, עם מרווח פנימי סביב הטקסט.

אפשר לשרשר את הפונקציות האלה כדי ליצור מהן קומפוזיציה:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

הרקע הצבעוני שמאחורי הטקסט נמתח עכשיו לרוחב המלא של המכשיר.

בקוד שלמעלה, שימו לב לפונקציות צירוף שונות שנעשה בהן שימוש יחד.

  • padding תופסת רווח מסביב לרכיב.
  • fillMaxWidth גורם לרכיב ה-Composable למלא את הרוחב המקסימלי שהוגדר לו על ידי ההורה שלו.

השיטה המומלצת היא שכל התכנים הקומפוזביליים יקבלו את הפרמטר modifier, ויעבירו את הצירוף הזה לתת-הצאצא הראשון שלו שפולט ממשק משתמש. כך תוכלו לעשות שימוש חוזר בקוד בקלות רבה יותר, וההתנהגות שלו תהיה צפויה ואינטואיטיבית יותר. מידע נוסף זמין בהנחיות של Compose API במאמר Elements מקבלים ומכבדים פרמטר Modifier.

סדר המשתנים המשתנים חשוב

הסדר של פונקציות המשתנה המשנה הוא משמעותי. מכיוון שכל פונקציה מבצעת שינויים ב-Modifier שהפונקציה הקודמת החזירה, הסדר משפיע על התוצאה הסופית. בואו נראה דוגמה לכך:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

כל האזור, כולל הריפוד מסביב לקצוות, מגיב לקליקים

בקוד שמעל כל האזור ניתן ללחוץ על, כולל המרווח הפנימי שמסביב, כי מקש הצירוף padding הוחל אחרי המגביל clickable. אם מגדירים את סדר המשתנים המשתנים הפוך, המרחב המשותף שנוסף באמצעות padding לא מגיב לקלט של המשתמש:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

הרווח מסביב לקצה הפריסה לא מגיב יותר לקליקים

גורמי שינוי מובנים

ב-Jetpack פיתוח נייטיב יש רשימה של רכיבי שינוי מובנים שיעזרו לכם לקשט או לשפר תוכן קומפוזבילי. ריכזנו כאן כמה משתני אופן פעולה נפוצים שיעזרו לכם לשנות את הפריסות.

padding וגם size

כברירת מחדל, פריסות שסופקו ב-Compose עוטפות את הצאצאים שלהן. אבל אפשר להגדיר את הגודל באמצעות מקש הצירוף size:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

שימו לב: יכול להיות שהגודל שציינתם לא יתקבל אם הוא לא עומד באילוצים שמגיעים מהרכיב ההורה של הפריסה. אם אתם רוצים שהגודל של הרכיב הניתן ליצירה יהיה קבוע ללא קשר למגבלות הנכנסות, צריך להשתמש במערך requiredSize:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

קובץ האימג' הצאצא גדול מהמגבלות שמגיעות מההורה שלו

בדוגמה הזו, גם אם ההורה height מוגדר כ-100.dp, הגובה של Image יהיה 150.dp, כי המשתנה המשנה requiredSize מקבל עדיפות.

אם רוצים שפריסת צאצא תמלא את כל הגובה הזמין שמותר על ידי ההורה, מוסיפים את מקש הצירוף fillMaxHeight (כתיבה כוללת גם את fillMaxSize ואת fillMaxWidth):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

גובה התמונה גדול כמו ההורה שלו

כדי להוסיף ריפוד מסביב לאלמנט, מגדירים את המאפיין padding.

אם רוצים להוסיף רווח מעל קו הטקסט כך שיהיה מרחק ספציפי בין החלק העליון של הפריסה לקו הטקסט, משתמשים במשתנה paddingFromBaseline:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

טקסט עם רווח מעל

היסט

כדי למקם פריסה ביחס למיקום המקורי שלה, מוסיפים את המשתנה offset ומגדירים את ההיסט בצייר x ו-y. התנודות יכולות להיות חיוביות וגם שליליות. ההבדל בין padding לבין offset הוא שהוספת offset ל-composable לא משנה את המדידות שלו:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

טקסט שנדחק לצד ימין של מאגר האב שלו

המשתנה offset מיושם אופקית בהתאם לכיוון הפריסה. בהקשר של שמאל לימין, ערך חיובי של offset מעביר את הרכיב ימינה, ואילו בהקשר של ימין לשמאל, הוא מעביר את הרכיב שמאלה. אם צריך להגדיר היסט בלי להביא בחשבון את כיוון הפריסה, כדאי לעיין במשתנה absoluteOffset, שבו ערך היסט חיובי תמיד מעביר את הרכיב ימינה.

המשתנה המשנה offset מספק שתי עומסי יתר – offset שמקבל את הזזות האופק כפרמטרים ו-offset שמקבל פונקציית lambda. למידע מפורט יותר מתי כדאי להשתמש בכל אחת מהאפשרויות האלה ואיך לבצע אופטימיזציה לביצועים, קראו את הקטע כתיבת ביצועים – דחיית קריאה ככל האפשר.

היקף הבטיחות של הכתיבה

ב-Compose יש משתני אופן (modifiers) שאפשר להשתמש בהם רק כשהם חלים על צאצאים של רכיבים מסוימים ליצירה. אוכפים את זה באמצעות היקפים מותאמים אישית.

לדוגמה, אם רוצים להגדיל רכיב צאצא לגודל של הרכיב ההורה Box בלי להשפיע על הגודל של Box, משתמשים במודификатор matchParentSize. matchParentSize זמין רק ב-BoxScope. לכן, אפשר להשתמש בו רק בנכס צאצא בתוך נכס הורה מסוג Box.

בזכות הבטיחות ברמת ההיקף, לא תוכלו להוסיף משתני אופן פעולה שלא יפעלו ברכיבים אחרים ובהיקפים אחרים, וכך לחסוך זמן בניסיון וטעייה.

התאמות בהיקף שהוגדר מיידעות את ההורה לגבי מידע מסוים שההורים צריכים לדעת על הילד או הילדה. הם נקראים גם התאמות של נתוני הורה. המאפיינים הפנימיים שלהם שונים מאלה של המשתנים למטרות כלליות, אבל מבחינת השימוש, ההבדלים האלה לא חשובים.

matchParentSize בעוד Box

כפי שצוין למעלה, אם רוצים שהפריסה של הצאצא תהיה באותו גודל כמו הפריסה של ההורה Box בלי להשפיע על הגודל של Box, צריך להשתמש במשתנה המשנה matchParentSize.

הערה: המאפיין matchParentSize זמין רק בהיקף Box, כלומר הוא חל רק על צאצאים ישירים של רכיבים מורכבים מסוג Box.

בדוגמה הבאה, הצאצא Spacer מקבל את הגודל שלו מיחידת ההורה Box, שבתוכה הוא מקבל את הגודל שלו מהילדים הגדולים ביותר, ArtistCard במקרה הזה.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

רקע אפור שממלא את הקונטיינר שלו

אם נעשה שימוש ב-fillMaxSize במקום ב-matchParentSize, הפרמטר Spacer יתייחס לכל נפח האחסון הזמין שמותר להורה, וכך ההורה יתרחב ותמלא את כל השטח הזמין.

רקע אפור שממלא את המסך

weight ב-Row וב-Column

כפי שראיתם בקטע הקודם בנושא הגדרת שוליים וגודל, כברירת מחדל, הגודל של רכיב שאפשר ליצור ממנו קומפוזיציה מוגדר לפי התוכן שהוא עוטף. אפשר להגדיר גודל קומפוזבילי שיהיה גמיש בתוך ההורה שלו באמצעות המגביל weight שזמין רק ב-RowScope וב-ColumnScope.

ניקח Row שמכיל שני רכיבים של Box. התיבה הראשונה מקבלת ערך של פי שניים מ-weight של התיבה השנייה, כך שהיא מקבלת פי שניים ברוחב. מכיוון ש-Row הוא 210.dp רחב, ה-Box הראשון הוא 140.dp רחב והשני הוא 70.dp:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

רוחב התמונה הוא פי שניים מרוחב הטקסט

חילוץ של משתני אופן פעולה ושימוש חוזר בהם

אפשר לשרשר יחד כמה משתני אופן כדי לקשט או להוסיף לרכיב ה-Composable. השרשרת הזו נוצרת באמצעות הממשק Modifier, שמייצג רשימה מסודרת ואי אפשר לשנות אותה של Modifier.Elements יחיד.

כל Modifier.Element מייצג התנהגות ספציפית, כמו התנהגות של פריסה, ציור וגרפיקה, כל ההתנהגויות שקשורות לתנועות, ל-focus ולסמנטיקה, וגם אירועי קלט של המכשיר. הסדר שלהם חשוב: רכיבי הצירוף שמתווספים ראשונים יחולו ראשונים.

לפעמים כדאי לעשות שימוש חוזר באותן מופע של שרשרת המשתנים במודולים מרובים, על ידי חילוץ שלהם למשתנים והעברה שלהם להיקפים גבוהים יותר. יש כמה סיבות לכך:

  • הקצאת המשתנים לא תתבצע מחדש כשיתבצע עיבוד מחדש של רכיבים מורכבים שמשתמשים בהם
  • שרשורי המשתנים יכולים להיות ארוכים ומורכבים מאוד, ולכן שימוש חוזר באותה מכונה של שרשרת יכול להפחית את עומס העבודה שסביבת זמן הריצה של Compose צריכה לבצע כשמשווים ביניהם
  • החילוץ הזה מעודד ניקיון, עקביות ותחזוקה של הקוד בבסיס הקוד

שיטות מומלצות לשימוש חוזר במודיפיקורים

אתם יכולים ליצור רשתות Modifier משלכם ולחלץ אותן כדי לעשות בהן שימוש חוזר במספר רכיבים שאפשר לשלב. אפשר פשוט לשמור את המשתנה המשנה, כי הוא אובייקט שדומה לנתונים:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

חילוץ ושימוש חוזר של מגבילים במהלך צפייה במצב שמשתנה לעיתים קרובות

כשמשתמשים ברכיבים מורכבים שמשתנים בתדירות גבוהה, כמו מצבי אנימציה או scrollState, יכול להיות שיתבצעו מספר רב של קומפוזיציות מחדש. במקרה כזה, המשנים יוקצו לכל הרכבה מחדש, ויכול להיות שגם לכל פריים:

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

במקום זאת, אפשר ליצור את אותו מופע של הצירוף, לחלץ אותו ולהשתמש בו שוב, ולהעביר אותו לתוכן הקומפוזבילי, באופן הבא:

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

חילוץ של משתני אופן פעולה ללא היקף ושימוש חוזר בהם

אפשר לבטל את ההיקף של המשתנים המשתנים או להגדיר את ההיקף שלהם ל-composable ספציפי. במקרה של משתני מודיפיקטור ללא היקף, אפשר לחלץ אותם בקלות מחוץ לרכיבים הניתנים לשילוב כמשתנים פשוטים:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

האפשרות הזו יכולה להיות שימושית במיוחד בשילוב עם פריסות 'איטז'. ברוב המקרים, כדאי להגדיר את אותם המשתנים לכל כמות הפריטים, שעשויה להיות משמעותית:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

חילוץ של מגבילי היקף ושימוש חוזר בהם

כשעובדים עם מודיפיקרים שמוגדרים לרכיבים מורכבים מסוימים, אפשר לחלץ אותם לרמה הגבוהה ביותר האפשרית ולהשתמש בהם מחדש במקרים המתאימים:

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

צריך להעביר את המשתנים המשתנים ברמת ההיקף שחולצו רק לצאצאים הישירים באותו היקף. למידע נוסף על החשיבות של המדיניות הזו, אפשר לעיין בסעיף בטיחות היקף בכתיבה:

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

שרשור נוסף של המשתנים שחולצו

אפשר לשרשר או לצרף עוד שרשראות שחולצו באמצעות קריאה לפונקציה .then():

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

חשוב לזכור שסדר המשתנים המשתנים חשוב!

מידע נוסף

אנחנו מספקים רשימה מלאה של המשתנים המשתנים, עם הפרמטרים וההיקפים שלהם.

כדי לתרגל את השימוש במטמיעים, אפשר גם לעבור על הקודלאב בנושא פריסות בסיסיות ב-Compose או לעיין במאגר Now in Android.

למידע נוסף על משתני אופן התצוגה בהתאמה אישית ועל האופן שבו יוצרים אותם, אפשר לעיין במאמר פריסות בהתאמה אישית – שימוש במשתנה אופן התצוגה.