התאמה אישית של מעבר רכיב משותף

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

מפרט אנימציה

כדי לשנות את מפרט האנימציה שמשמש לתנועה של הגודל והמיקום, אפשר לציין פרמטר boundsTransform שונה ב-Modifier.sharedElement(). כך מקבלים את המיקום הראשוני Rect ואת מיקום היעד Rect.

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

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

אפשר להשתמש בכל AnimationSpec. בדוגמה הזו נעשה שימוש במפרט keyframes.

איור 1. דוגמה שמציגה פרמטרים שונים של boundsTransform

מצב שינוי גודל

כשאתם יוצרים אנימציה בין שני גבולות משותפים, אתם יכולים להגדיר את הפרמטר resizeMode לערך RemeasureToBounds או לערך ScaleToBounds. הפרמטר הזה קובע איך הרכיב המשותף עובר בין שני המצבים. ScaleToBounds קודם מודדת את פריסת הצאצא עם מגבלות המבט לאחור (או היעד). לאחר מכן הפריסה היציבה של הילד או הילדה מותאמת לגבולות המשותפים. אפשר לחשוב על ScaleToBounds כ'סולם גרפי' בין המצבים.

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

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

אפשר לראות את ההבדל בין שני מצבי הגודל בדוגמאות הבאות:

ScaleToBounds

RemeasureToBounds

דילוג לפריסה הסופית

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

הדוגמה הבאה ממחישה את טקסט התיאור "Lorem Ipsum" כניסה את המסך בשתי דרכים שונות. הדוגמה הראשונה שבה הטקסט זורם מחדש כפי שהוא נכנס ככל שהמאגר גדל, והדוגמה השנייה לטקסט לא הזרמה חוזרת כשזה גדל. הוספת Modifier.skipToLookaheadSize() מונעת את הצורך בזרימה מחדש כשהטקסט מתארך.

No Modifier.skipToLookahead() - שימו לב ל-"Lorem Ipsum" הזרמה חוזרת של טקסט

Modifier.skipToLookahead() – שימו לב ל-"Lorem Ipsum" הטקסט נשמר במצב הסופי בתחילת האנימציה

קליפים ושכבות-על

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

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

כדי לחתוך רכיב משותף לצורה, משתמשים בפונקציה הרגילה Modifier.clip(). צריך להוסיף אותו אחרי ה-sharedElement():

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

אם אתם רוצים לוודא שרכיב משותף אף פעם לא יופיע מחוץ לקונטיינר הורה, תוכלו להגדיר את clipInOverlayDuringTransition ב-sharedElement(). כברירת מחדל, למסגרות משותפות בתצוגת עץ, clipInOverlayDuringTransition משתמש בנתיב של הקליפ מהרכיב ההורה sharedBounds().

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

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

בלי Modifier.renderInSharedTransitionScopeOverlay()

עם Modifier.renderInSharedTransitionScopeOverlay()

לפעמים כדאי להציג אנימציה של רכיב ה-Composable שאינו משותף, וגם להשאיר אותו מעל שאר רכיבי ה-Composable לפני המעבר. במקרים כאלה, צריך להשתמש renderInSharedTransitionScopeOverlay().animateEnterExit() כדי להוסיף אנימציה קומפוזביליות כשמעבר הרכיב המשותף פועל:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

איור 2.סרגל האפליקציה התחתון גולש פנימה והחוצה בזמן המעבר בין האנימציה

במקרים נדירים שבהם רוצים שהרכיב המשותף לא יוצג כשכבת-על, אפשר להגדיר את הערך של renderInOverlayDuringTransition ב-sharedElement() כ-false.

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

כברירת מחדל, sharedBounds() וגם sharedElement() לא שולחים הודעות להורה מכל גודל שהוא משתנה ככל שהפריסה עוברת.

כדי להעביר שינויים בגודל לקונטיינר ההורה במהלך המעבר, צריך לשנות את הפרמטר placeHolderSize ל-PlaceHolderSize.animatedSize. הפעולה הזו תגרום לפריט לגדול או להתכווץ. כל שאר הפריטים בפריסה מגיבים ל את השינוי.

PlaceholderSize.contentSize (ברירת מחדל)

PlaceholderSize.animatedSize

(שים לב איך הפריטים האחרים ברשימה יורדים בתגובה לפריט אחד שגדל)