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

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

מפרט אנימציה

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

ללא Modifier.skipToLookahead() – שימו לב שהטקסט 'Lorem Ipsum' עובר עיצוב מחדש

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

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

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

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

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

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