Dostosuj animacje

Wiele interfejsów Animation API powszechnie akceptuje parametry umożliwiające dostosowanie ich działania.

Dostosowywanie animacji za pomocą parametru AnimationSpec

Większość interfejsów Animation API umożliwia programistom dostosowywanie specyfikacji animacji za pomocą opcjonalnego parametru AnimationSpec.

val alpha: Float by animateFloatAsState(
    targetValue = if (enabled) 1f else 0.5f,
    // Configure the animation duration and easing.
    animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
    label = "alpha"
)

Istnieją różne rodzaje AnimationSpec do tworzenia różnych typów animacji.

Tworzenie animacji opartej na fizyce za pomocą funkcji spring

Funkcja spring tworzy animację opartą na fizyce między wartościami początkową a końcową. Przyjmuje 2 parametry: dampingRatio i stiffness.

dampingRatio określa, jak sprężysta ma być sprężyna. Wartością domyślną jest Spring.DampingRatioNoBouncy.

Rysunek 1. Ustawianie różnych współczynników tłumienia sprężyny.

stiffness określa, jak szybko sprężyna ma się przesuwać w kierunku wartości końcowej. Wartością domyślną jest Spring.StiffnessMedium.

Rysunek 2. Ustawianie różnej sztywności sprężyny.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessMedium
    ),
    label = "spring spec"
)

Funkcja spring może płynniej obsługiwać przerwy niż typy AnimationSpec oparte na czasie trwania, ponieważ gwarantuje ciągłość prędkości, gdy wartość docelowa zmienia się w trakcie animacji. Wiele interfejsów Animation API, takich jak animate*AsState i updateTransition, używa funkcji spring jako domyślnej funkcji AnimationSpec.

Jeśli na przykład zastosujemy konfigurację spring do poniższej animacji, która jest wywoływana przez dotyk użytkownika, to po przerwaniu animacji w trakcie jej trwania widać, że użycie funkcji tween nie reaguje tak płynnie jak użycie funkcji spring.

Rysunek 3. Ustawianie specyfikacji tween i spring dla animacji oraz jej przerywanie.

Animowanie między wartościami początkową a końcową za pomocą krzywej wygładzania z użyciem funkcji tween

Funkcja tween animuje między wartościami początkową a końcową w określonym czasie durationMillis za pomocą krzywej wygładzania. tween to skrót od słowa „between” (między) – animacja przechodzi między 2 wartościami.

Możesz też określić delayMillis, aby opóźnić rozpoczęcie animacji.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = tween(
        durationMillis = 300,
        delayMillis = 50,
        easing = LinearOutSlowInEasing
    ),
    label = "tween delay"
)

Więcej informacji znajdziesz w artykule Wygładzanie.

Animowanie do określonych wartości w określonych momentach za pomocą funkcji keyframes

Funkcja keyframes animuje na podstawie wartości migawek określonych w różnych znacznikach czasu w czasie trwania animacji. W dowolnym momencie wartość animacji będzie interpolowana między 2 wartościami klatek kluczowych. Dla każdej z tych klatek kluczowych można określić wygładzanie, aby określić krzywą interpolacji.

Określenie wartości w 0 ms i w czasie trwania jest opcjonalne. Jeśli nie określisz tych wartości, domyślnie będą to odpowiednio wartości początkowa i końcowa animacji.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = keyframes {
        durationMillis = 375
        0.0f at 0 using LinearOutSlowInEasing // for 0-15 ms
        0.2f at 15 using FastOutLinearInEasing // for 15-75 ms
        0.4f at 75 // ms
        0.4f at 225 // ms
    },
    label = "keyframe"
)

Płynne animowanie między klatkami kluczowymi za pomocą funkcji keyframesWithSplines

Aby utworzyć animację, która podczas przejścia między wartościami podąża za gładką krzywą, możesz użyć keyframesWithSplines zamiast keyframes specyfikacji animacji.

val offset by animateOffsetAsState(
    targetValue = Offset(300f, 300f),
    animationSpec = keyframesWithSpline {
        durationMillis = 6000
        Offset(0f, 0f) at 0
        Offset(150f, 200f) atFraction 0.5f
        Offset(0f, 100f) atFraction 0.7f
    }
)

Klatki kluczowe oparte na splajnach są szczególnie przydatne w przypadku ruchu 2D elementów na ekranie.

Filmy poniżej pokazują różnice między funkcjami keyframes i keyframesWithSpline w przypadku tego samego zestawu współrzędnych x i y, po których ma się poruszać okrąg.

keyframes keyframesWithSplines

Jak widać, klatki kluczowe oparte na splajnach zapewniają płynniejsze przejścia między punktami, ponieważ używają krzywych Beziera do płynnego animowania między elementami. Ta specyfikacja jest przydatna w przypadku gotowej animacji. Jeśli jednak pracujesz z punktami wyznaczanymi przez użytkownika, lepiej jest używać sprężyn, aby uzyskać podobną płynność między punktami, ponieważ można je przerwać.

Powtarzanie animacji za pomocą funkcji repeatable

Funkcja repeatable wielokrotnie uruchamia animację opartą na czasie trwania (np. tween lub keyframes), aż osiągnie określoną liczbę iteracji. Możesz przekazać parametr repeatMode, aby określić, czy animacja ma się powtarzać od początku (RepeatMode.Restart) czy od końca (RepeatMode.Reverse).

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "repeatable spec"
)

Powtarzanie animacji w nieskończoność za pomocą funkcji infiniteRepeatable

Funkcja infiniteRepeatable jest podobna do funkcji repeatable, ale powtarza się w nieskończoność.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "infinite repeatable"
)

W testach z użyciem ComposeTestRule, animacje korzystające z infiniteRepeatable nie są uruchamiane. Komponent będzie renderowany przy użyciu wartości początkowej każdej animowanej wartości.

Natychmiastowe przejście do wartości końcowej za pomocą funkcji snap

Funkcja snap to specjalna funkcja AnimationSpec, która natychmiast przełącza wartość na wartość końcową. Możesz określić delayMillis, aby opóźnić rozpoczęcie animacji.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = snap(delayMillis = 50),
    label = "snap spec"
)

Ustawianie niestandardowej funkcji wygładzania

Operacje AnimationSpec oparte na czasie trwania (np. tween lub keyframes) używają funkcji Easing do dostosowania ułamka animacji. Dzięki temu animowana wartość może przyspieszać i zwalniać, a nie poruszać się ze stałą prędkością. Ułamek to wartość z zakresu od 0 (początek) do 1,0 (koniec) wskazująca bieżący punkt w animacji.

Wygładzanie to w rzeczywistości funkcja, która przyjmuje wartość ułamka z zakresu od 0 do 1,0 i zwraca liczbę zmiennoprzecinkową. Zwracana wartość może wykraczać poza granice, aby reprezentować przekroczenie lub niedobór. Niestandardowe wygładzanie można utworzyć za pomocą kodu poniżej.

val CustomEasing = Easing { fraction -> fraction * fraction }

@Composable
fun EasingUsage() {
    val value by animateFloatAsState(
        targetValue = 1f,
        animationSpec = tween(
            durationMillis = 300,
            easing = CustomEasing
        ),
        label = "custom easing"
    )
    // ……
}

Compose udostępnia kilka wbudowanych funkcji Easing, które obejmują większość przypadków użycia. Więcej informacji o tym, jakiego wygładzania użyć w zależności od scenariusza, znajdziesz w artykule Szybkość – Material Design.

  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing
  • Więcej informacji

Animowanie niestandardowych typów danych przez konwertowanie na AnimationVector i z niego

Większość interfejsów Animation API w Compose domyślnie obsługuje jako wartości animacji typy danych Float, Color, Dp i inne podstawowe typy danych, ale czasami trzeba animować inne typy danych, w tym typy niestandardowe. Podczas animacji każda animowana wartość jest reprezentowana jako AnimationVector. Wartość jest konwertowana na AnimationVector i odwrotnie przez odpowiedni TwoWayConverter, dzięki czemu podstawowy system animacji może je obsługiwać w jednolity sposób. Na przykład Int jest reprezentowany jako AnimationVector1D, który zawiera pojedynczą wartość zmiennoprzecinkową. TwoWayConverter dla Int wygląda tak:

val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
    TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })

Color to zasadniczo zestaw 4 wartości: czerwonej, zielonej, niebieskiej i alfa, dlatego Color jest konwertowany na AnimationVector4D, który zawiera 4 wartości zmiennoprzecinkowe. W ten sposób każdy typ danych używany w animacjach jest konwertowany na AnimationVector1D, AnimationVector2D, AnimationVector3D lub AnimationVector4D w zależności od jego wymiarowości. Dzięki temu różne komponenty obiektu mogą być animowane niezależnie, każdy z własnym śledzeniem prędkości. Wbudowane konwertery podstawowych typów danych są dostępne za pomocą konwerterów takich jak Color.VectorConverter czy Dp.VectorConverter.

Jeśli chcesz dodać obsługę nowego typu danych jako wartości animacji, możesz utworzyć własny TwoWayConverter i udostępnić go interfejsowi API. Możesz na przykład użyć funkcji animateValueAsState, aby animować niestandardowy typ danych w ten sposób:

data class MySize(val width: Dp, val height: Dp)

@Composable
fun MyAnimation(targetSize: MySize) {
    val animSize: MySize by animateValueAsState(
        targetSize,
        TwoWayConverter(
            convertToVector = { size: MySize ->
                // Extract a float value from each of the `Dp` fields.
                AnimationVector2D(size.width.value, size.height.value)
            },
            convertFromVector = { vector: AnimationVector2D ->
                MySize(vector.v1.dp, vector.v2.dp)
            }
        ),
        label = "size"
    )
}

Poniższa lista zawiera niektóre wbudowane konwertery VectorConverter: