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

מודификаторים מאפשרים לקשט או להוסיף רכיבים ל-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 accept and respect a Modifier parameter.

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

הסדר של פונקציות המשתנה המשנה הוא משמעותי. מכיוון שכל פונקציה מבצעת שינויים ב-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 Compose יש רשימה של מודיפיקרים מובנים שיעזרו לכם לקשט או להוסיף רכיבים ל-composable. ריכזנו כאן כמה משתני אופן פעולה נפוצים שיעזרו לכם לשנות את הפריסות.

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 (ב-Compose יש גם את המשתנים 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

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

לדוגמה, אם רוצים להגדיל רכיב צאצא לגודל של הרכיב ההורה 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
    )
}

במקום זאת, אפשר ליצור, לחלץ ולהשתמש שוב באותו מופע של המשתנה המשנה ולהעביר אותו ל-composable כך:

// 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 ספציפי. במקרה של משתני מודיפיקטור ללא היקף, אפשר לחלץ אותם בקלות מחוץ לרכיבי ה-Composables כמשתנים פשוטים:

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
        // ...
    )
    // ...
}

צריך להעביר את המשתנים המשתנים ברמת ההיקף שחולצו רק לצאצאים הישירים באותו היקף. בקטע Scope safety in Compose מוסבר למה זה חשוב:

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)

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

מידע נוסף

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

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

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