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

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

מפרט אנימציה

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

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

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

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

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