מגבילי אנימציה ותכנים קומפוזביליים

התכונה 'פיתוח נייטיב' כוללת תכנים קומפוזביליים ומגבילי התאמה מובנים לצורך טיפול בתרחישים נפוצים לדוגמה באנימציה.

תכנים קומפוזביליים מובנים באנימציה

עם AnimatedVisibility אפשר להפוך את המראה והיעלם לאנימציה

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

AnimatedVisibility שמזינים בו אנימציה של המראה וההיעלמות של התוכן שלו,

var visible by remember {
    mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(visible) {
    // your composable here
    // ...
}

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

var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
    visible = visible,
    enter = slideInVertically {
        // Slide in from 40 dp from the top.
        with(density) { -40.dp.roundToPx() }
    } + expandVertically(
        // Expand from the top.
        expandFrom = Alignment.Top
    ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
    ),
    exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
    Text("Hello", Modifier.fillMaxWidth().height(200.dp))
}

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

EnterTransition ו-ExitTransition דוגמאות

כניסה יציאה מהמעבר
fadeIn
להפוך לשקוף בהדרגה באנימציה
fadeOut
אנימציה של יציאה הדרגתית
slideIn
הסטה באנימציה
slideOut
אנימציה של הסטה החוצה
slideInHorizontally
הסטה לאנימציה אופקית
slideOutHorizontally
אנימציה של הסטה אופקית
slideInVertically
הסטה באנימציה אנכית
slideOutVertically
אנימציה של הסטה אנכית
scaleIn
קנה מידה באנימציה
scaleOut
אנימציה של שינוי מרחק התצוגה
expandIn
הרחבה באנימציה
shrinkOut
אנימציה של כיווץ
expandHorizontally
אנימציה של הרחבה אופקית
shrinkHorizontally
אנימציה של כיווץ אופקי
expandVertically
אנימציה מתרחבת
shrinkVertically
אנימציה של כיווץ אנכי

ב-AnimatedVisibility יש גם וריאנט שלוקח MutableTransitionState. כך אפשר להפעיל אנימציה ברגע AnimatedVisibility נוסף לעץ היצירה. הוא שימושי גם למקרים הבאים: לצפות במצב האנימציה.

// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember {
    MutableTransitionState(false).apply {
        // Start the animation immediately.
        targetState = true
    }
}
Column {
    AnimatedVisibility(visibleState = state) {
        Text(text = "Hello, world!")
    }

    // Use the MutableTransitionState to know the current animation state
    // of the AnimatedVisibility.
    Text(
        text = when {
            state.isIdle && state.currentState -> "Visible"
            !state.isIdle && state.currentState -> "Disappearing"
            state.isIdle && !state.currentState -> "Invisible"
            else -> "Appearing"
        }
    )
}

הצגת אנימציה של כניסה ויציאה לילדים

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

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(Modifier.fillMaxSize().background(Color.DarkGray)) {
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    // Slide in/out the inner box.
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
            // Content of the notification…
        }
    }
}

במקרים מסוימים, ייתכן שברצונך שהאפקט של AnimatedVisibility לא יכלול אנימציות של כדי שלילדים יהיו אנימציות ייחודיות משלהם animateEnterExit. כדי לעשות זאת, צריך לציין EnterTransition.None ExitTransition.None בתוכן הקומפוזבילי AnimatedVisibility.

הוספת אנימציה מותאמת אישית

אם רוצים להוסיף אפקטים של אנימציה בהתאמה אישית, מעבר לכניסה וליציאה המובנות אנימציות, לגשת למכונה Transition הבסיסית דרך transition נכס בתוך lambda של התוכן עבור AnimatedVisibility. כל אנימציה שנוספו למכונת המעבר יפעלו בו-זמנית עם הפקודה Enter ולצאת מאנימציות של AnimatedVisibility. AnimatedVisibility בהמתנה עד כל האנימציות בTransition הסתיימו לפני הסרת התוכן. לאנימציות יציאה שנוצרו ללא תלות ב-Transition (כמו שימוש ב- animate*AsState), ל-AnimatedVisibility לא תהיה אפשרות להתייחס אליהם, ולכן הם עשויים להסיר את התוכן הקומפוזבילי לפני שהם מסיימים.

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) { // this: AnimatedVisibilityScope
    // Use AnimatedVisibilityScope#transition to add a custom animation
    // to the AnimatedVisibility.
    val background by transition.animateColor(label = "color") { state ->
        if (state == EnterExitState.Visible) Color.Blue else Color.Gray
    }
    Box(modifier = Modifier.size(128.dp).background(background))
}

לפרטים על Transition, ראו עדכון מעבר.

אנימציה על סמך מצב היעד עם AnimatedContent

AnimatedContent קומפוזבילי אנימציה בתוכן שלו כשהוא משתנה על סמך במצב היעד.

Row {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(targetState = count) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

שימו לב שתמיד צריך להשתמש בפרמטר lambda ולשקף אותו תוכן. ה-API משתמש בערך הזה כמפתח לזיהוי התוכן מוצגת כרגע.

כברירת מחדל, התוכן הראשוני נעלם ולאחר מכן תוכן היעד נעלם (ההתנהגות הזו נקראת מעבר הדרגתי). שלך יכולים להתאים אישית את התנהגות האנימציה הזו על ידי ציון אובייקט ContentTransform פרמטר transitionSpec. אפשר ליצור ContentTransform על ידי שילוב EnterTransition עם ExitTransition באמצעות פונקציית ההוספה with. אפשר להחיל את SizeTransform אל ContentTransform באמצעות צירוף פונקציית תיקון using.

AnimatedContent(
    targetState = count,
    transitionSpec = {
        // Compare the incoming number with the previous number.
        if (targetState > initialState) {
            // If the target number is larger, it slides up and fades in
            // while the initial (smaller) number slides up and fades out.
            slideInVertically { height -> height } + fadeIn() with
                slideOutVertically { height -> -height } + fadeOut()
        } else {
            // If the target number is smaller, it slides down and fades in
            // while the initial number slides down and fades out.
            slideInVertically { height -> -height } + fadeIn() with
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }
) { targetCount ->
    Text(text = "$targetCount")
}

EnterTransition מגדיר איך תוכן היעד ייראה. ExitTransition מגדיר איך התוכן הראשוני צריך להיעלם. כמו כן לכל הפונקציות של EnterTransition ו-ExitTransition שזמינות AnimatedVisibility, AnimatedContent הצעות slideIntoContainer ו-slideOutOfContainer. אלה חלופות נוחות ל-slideInHorizontally/Vertically ול הפונקציה slideOutHorizontally/Vertically שמחשבת את המרחק של השקף על סמך את גודל התוכן הראשוני ותוכן היעד של תוכן בAnimatedContent.

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

var expanded by remember { mutableStateOf(false) }
Surface(
    color = MaterialTheme.colorScheme.primary,
    onClick = { expanded = !expanded }
) {
    AnimatedContent(
        targetState = expanded,
        transitionSpec = {
            fadeIn(animationSpec = tween(150, 150)) with
                fadeOut(animationSpec = tween(150)) using
                SizeTransform { initialSize, targetSize ->
                    if (targetState) {
                        keyframes {
                            // Expand horizontally first.
                            IntSize(targetSize.width, initialSize.height) at 150
                            durationMillis = 300
                        }
                    } else {
                        keyframes {
                            // Shrink vertically first.
                            IntSize(initialSize.width, targetSize.height) at 150
                            durationMillis = 300
                        }
                    }
                }
        }
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

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

בדיוק כמו AnimatedVisibility, גם animateEnterExit זמין בתוך lambda של תוכן של AnimatedContent. שימוש בטיוטה הזו להחיל את EnterAnimation ואת ExitAnimation על כל אחד מהנכסים הישירים או העקיפים לילדים בנפרד.

הוספת אנימציה מותאמת אישית

בדיוק כמו AnimatedVisibility, השדה transition זמין תוכן lambda של AnimatedContent. אפשר להשתמש בה כדי ליצור אנימציה בהתאמה אישית את האפקט שפועל בו-זמנית עם המעבר אל AnimatedContent. צפייה לפרטים נוספים, אפשר לעדכן את המעבר.

יצירת אנימציה בין שתי פריסות עם Crossfade

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

var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage) { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}

מגבילי אנימציה מובנים

אנימציה של שינויי גודל של תוכן קומפוזבילי עם animateContentSize

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

תכונת הצירוף animateContentSize יוצרת אנימציה של שינוי גודל.

var expanded by remember { mutableStateOf(false) }
Box(
    modifier = Modifier
        .background(colorBlue)
        .animateContentSize()
        .height(if (expanded) 400.dp else 200.dp)
        .fillMaxWidth()
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            expanded = !expanded
        }

) {
}

אנימציות של פריט ברשימה

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