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

מגבילי התאמה מאפשרים לכם לקשט או לשפר תוכן קומפוזבילי. מגבילי התאמה מאפשרים לך דברים כאלה:

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

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

סדר ההתאמות חשוב

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

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

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

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

כמו שראית בסעיף הקודם על ריפוד size, כברירת מחדל, גודל קומפוזבילי מוגדר על ידי את התוכן שעוטף. אפשר להגדיר גודל קומפוזבילי שיהיה גמיש במסגרת משתמש בתכונת הצירוף 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)
        ) {
            /*...*/
        }
    }
}

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

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

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

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

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

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

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

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

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.

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