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

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

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

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

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

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