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

משתני ה-modifier מאפשרים לקשט או לשפר רכיב Composable. בעזרת מגדירי שינוי אפשר לבצע פעולות כמו:

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

משתני ה-modifier הם אובייקטים רגילים של 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)
    }
}

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

בקוד שלמעלה, שימו לב לפונקציות השונות של משתני ה-modifier שמשמשות יחד.

  • ‫padding מוסיף רווח מסביב לרכיב.
  • fillMaxWidth גורם לרכיב ה-composable למלא את הרוחב המקסימלי שניתן לו מההורה שלו.

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

סדר מקשי הצירוף חשוב

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

padding וגם size

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

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

שימו לב: יכול להיות שהגודל שציינתם לא יכובד אם הוא לא עומד במגבלות שמגיעות מההורה של הפריסה. אם אתם רוצים שהגודל של הרכיב הניתן לקומפוזיציה יהיה קבוע, בלי קשר לאילוצים הנכנסים, אתם יכולים להשתמש ב-modifier‏ 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, כי ל-modifier‏ requiredSize יש קדימות.

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

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

גובה התמונה זהה לגובה של רכיב ההורה שלה

כדי להוסיף מרווח פנימי מסביב לרכיב, מגדירים משתנה מסוג padding. הפרמטרים start ו-end של padding פועלים לפי כיוון הפריסה. כדי להוסיף ריפוד לקצה השמאלי או הימני בלי קשר לכיוון הפריסה, משתמשים ב-absolutePadding עם הפרמטרים left ו-right.

אם רוצים להוסיף מרווח מעל שורת בסיס לטקסט כדי להגיע למרחק מסוים מהחלק העליון של הפריסה לשורת הבסיס, משתמשים במשנה 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 מזיז את הרכיב ימינה, ובהקשר של מימין לשמאל, הוא מזיז את הרכיב שמאלה. אם אתם צריכים להגדיר היסט בלי להתחשב בכיוון הפריסה, עיינו ב-modifier‏ absoluteOffset, שבו ערך היסט חיובי תמיד מעביר את הרכיב ימינה.

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

בטיחות בהיקף ב-Compose

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

לדוגמה, אם רוצים שרכיב הצאצא יהיה בגודל של רכיב ההורה Box בלי להשפיע על הגודל של Box, משתמשים ב-modifier‏ matchParentSize. ‫matchParentSize זמין רק בBoxScope. לכן, אפשר להשתמש בו רק בצאצא בתוך הורה מסוג Box.

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

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

matchParentSize ב-Box

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

חשוב לזכור שהפונקציה matchParentSize זמינה רק בתוך היקף Box, כלומר היא חלה רק על צאצאים ישירים של פונקציות ה-composable מסוג Box.

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

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

רקע אפור שממלא את המאגר שלו

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

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

‫weight ב-Row ו-Column

כפי שראיתם בקטע הקודם בנושא מרווח פנימי וגודל, כברירת מחדל, הגודל של רכיב ה-Composable מוגדר לפי התוכן שהוא כולל. אפשר להגדיר גודל גמיש של פונקציה קומפוזבילית בתוך רכיב ההורה שלה באמצעות העיבוד 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 מייצג התנהגות ספציפית, כמו התנהגויות של פריסה, שרטוט וגרפיקה, כל ההתנהגויות שקשורות לתנועות, למיקוד ולסמנטיקה, וגם אירועי קלט של המכשיר. הסדר שלהם חשוב: רכיבי ה-modifier שנוספים קודם יופעלו קודם.

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

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

שיטות מומלצות לשימוש חוזר בשינויי התאמה

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

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

חילוץ ושימוש חוזר ב-modifiers כשמבצעים מעקב אחרי מצב שמשתנה לעיתים קרובות

כשצופים במצבים שמשתנים לעיתים קרובות בתוך רכיבים קומפוזביליים, כמו מצבי אנימציה או 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
    )
}

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

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,
            )
        }
    }
}

חילוץ ושימוש חוזר ב-scoped modifiers

כשמשתמשים ב-modifiers שמוגבלים לפונקציות קומפוזביליות מסוימות, אפשר לחלץ אותם לרמה הגבוהה ביותר האפשרית ולהשתמש בהם שוב במקומות המתאימים:

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

צריך להעביר את המשתנים שחולצו ומוגדרים ברמת ההיקף רק לצאצאים הישירים שמוגדרים באותו היקף. בקטע בטיחות בהיקף ב-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)

חשוב לזכור שלסדר של המגבילים יש חשיבות.

מידע נוסף

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

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

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