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

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

מפרט האנימציה

כדי לשנות את הגדרות האנימציה שמשמשות לתנועת הגודל והמיקום, אפשר לציין פרמטר 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 first מחשב את פריסת רכיב הצאצא באמצעות אילוצי התצוגה המקדימה (או היעד). לאחר מכן, הפריסה היציבה של רכיב הצאצא מותאמת כך שתתאים לגבולות המשותפים. אפשר לחשוב על ScaleToBounds כעל 'סולם גרפי' בין המצבים.

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

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

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

ScaleToBounds

RemeasureToBounds

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

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

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

ללא Modifier.skipToLookahead() – שימו לב לטקסט 'לורם איפסום' שמוצג בשורות חדשות

Modifier.skipToLookahead() – שימו לב שהטקסט 'לורם איפסום' שומר על המצב הסופי שלו בתחילת האנימציה

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

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

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

בלי Modifier.renderInSharedTransitionScopeOverlay()

עם Modifier.renderInSharedTransitionScopeOverlay()

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

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