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

אמצעי שינוי מאפשרים להוסיף עיצוב או לשפר רכיב שאפשר להרכיב. המשנים מאפשרים לכם לבצע פעולות מהסוגים הבאים:

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

המשנים הם אובייקטים סטנדרטיים של 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 גורמת לרכיב הניתן להרכבה למלא את הרוחב המקסימלי שמוגדר לו מרכיב האב.

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

padding וגם size

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

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

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

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

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

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

matchParentSize בעוד Box

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

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

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

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

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

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

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

weight ב-Row וגם ב-Column

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

נניח שיש לנו Row שמכיל שני רכיבי Box composable. התיבה הראשונה מקבלת פי שניים יותר 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)
        ) {
            /*...*/
        }
    }
}

רוחב התמונה הוא כפול מרוחב הטקסט

חילוץ של משנים ושימוש חוזר בהם

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

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

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

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

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

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

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

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

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