Animuj pojedynczą wartość za pomocą funkcji animate*AsState
Funkcje animate*AsState
to najprostsze interfejsy API animacji w Compose do animowania pojedynczej wartości. Musisz podać tylko wartość docelową (lub wartość końcową), a interfejs API rozpocznie animację od bieżącej wartości do określonej wartości.
Poniżej znajdziesz przykład animowania kanału alfa za pomocą tego interfejsu API. Wystarczy owinąć wartość docelową w animateFloatAsState
, aby wartość alfa stała się wartością animacji między podanymi wartościami (w tym przypadku 1f
lub 0.5f
).
var enabled by remember { mutableStateOf(true) } val alpha: Float by animateFloatAsState(if (enabled) 1f else 0.5f, label = "alpha") Box( Modifier .fillMaxSize() .graphicsLayer(alpha = alpha) .background(Color.Red) )
Pamiętaj, że nie musisz tworzyć instancji żadnej klasy animacji ani obsługiwać przerwania. W tle zostanie utworzony obiekt animacji (czyli instancja Animatable
) i zapamiętany w miejscu wywołania, przy czym jego początkową wartością będzie pierwsza wartość docelowa. Za każdym razem, gdy podasz w tym elemencie kompozycyjnym inną wartość docelową, automatycznie rozpoczyna się animacja w kierunku tej wartości. Jeśli jest już aktywna animacja, rozpoczyna się od jej bieżącej wartości (i prędkości) i jest animowana w kierunku wartości docelowej. Podczas animacji ten komponent jest ponownie składany i zwraca zaktualizowaną wartość animacji w każdym ujęciu.
Bezpośrednio po zainstalowaniu Compose udostępnia funkcje animate*AsState
dla typów danych Float
, Color
, Dp
, Size
, Offset
, Rect
, Int
, IntOffset
i IntSize
. Obsługę innych typów danych możesz łatwo dodać, podając parametr TwoWayConverter
dla parametru animateValueAsState
, który przyjmuje typ ogólny.
Specyfikację animacji możesz dostosować, dodając AnimationSpec
.
Więcej informacji znajdziesz w specyfikacji AnimationSpec.
Animowanie wielu właściwości jednocześnie za pomocą przejścia
Transition
zarządza co najmniej 1 animacją jako elementem podrzędnym i uruchamia ją jednocześnie w różnych stanach.
Stany mogą mieć dowolne typy danych. W wielu przypadkach możesz użyć niestandardowego typu enum
, aby zapewnić bezpieczeństwo typu, jak w tym przykładzie:
enum class BoxState { Collapsed, Expanded }
updateTransition
tworzy i zapamiętuje instancję Transition
oraz aktualizuje jej stan.
var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "box state")
Następnie możesz użyć jednej z funkcji rozszerzenia animate*
, aby zdefiniować animację podrzędną podczas tego przejścia. Określ wartości docelowe dla każdego stanu.
Te funkcje animate*
zwracają wartość animacji, która jest aktualizowana każda klatka w trakcie animacji, gdy stan przejścia jest aktualizowany za pomocą parametru updateTransition
.
val rect by transition.animateRect(label = "rectangle") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "border width") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } }
Opcjonalnie możesz przekazać parametr transitionSpec
, aby określić inną wartość AnimationSpec
dla każdej z kombinacji zmian stanu przejścia. Więcej informacji znajdziesz w pliku AnimationSpec.
val color by transition.animateColor( transitionSpec = { when { BoxState.Expanded isTransitioningTo BoxState.Collapsed -> spring(stiffness = 50f) else -> tween(durationMillis = 500) } }, label = "color" ) { state -> when (state) { BoxState.Collapsed -> MaterialTheme.colorScheme.primary BoxState.Expanded -> MaterialTheme.colorScheme.background } }
Gdy przejście do stanu docelowego zostanie zakończone, Transition.currentState
będzie takie samo jak Transition.targetState
. Może to służyć jako sygnał, że przejście zostało zakończone.
Czasami chcemy, aby stan początkowy różnił się od pierwszego stanu docelowego. Możemy użyć do tego celu updateTransition
i MutableTransitionState
. Umożliwia to na przykład rozpoczęcie animacji, gdy tylko kod wejdzie w kompozycję.
// Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // ……
Aby utworzyć bardziej złożone przejście z użyciem wielu funkcji kompozycyjnych, możesz użyć funkcji createChildTransition
, aby utworzyć przejście podrzędne. Ta technika jest przydatna do oddzielania problemów w kilku podkomponentach złożonego komponentu. Przejście nadrzędne będzie wiedzieć o wszystkich wartościach animacji w przejęciach podrzędnych.
enum class DialerState { DialerMinimized, NumberPad } @Composable fun DialerButton(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun NumberPad(isVisibleTransition: Transition<Boolean>) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun Dialer(dialerState: DialerState) { val transition = updateTransition(dialerState, label = "dialer state") Box { // Creates separate child transitions of Boolean type for NumberPad // and DialerButton for any content animation between visible and // not visible NumberPad( transition.createChildTransition { it == DialerState.NumberPad } ) DialerButton( transition.createChildTransition { it == DialerState.DialerMinimized } ) } }
Używanie przejścia z uwzględnieniem właściwości AnimatedVisibility
i AnimatedContent
AnimatedVisibility
i AnimatedContent
są dostępne jako funkcje rozszerzenia funkcji Transition
. targetState
dla Transition.AnimatedVisibility
i Transition.AnimatedContent
pochodzi z Transition
i w razie potrzeby aktywuje przejścia typu „wejście/wyjście” po zmianie targetState
właściwości Transition
. Te funkcje rozszerzenia umożliwiają przeniesienie do elementu Transition
wszystkich animacji wejścia/wyjścia/zmiany rozmiaru, które w przeciwnym razie byłyby wewnętrzne dla elementów AnimatedVisibility
/AnimatedContent
.
Dzięki tym funkcjom rozszerzenia można obserwować zmiany stanu AnimatedVisibility
/AnimatedContent
z zewnątrz. Zamiast parametru logicznego visible
ta wersja funkcji AnimatedVisibility
przyjmuje funkcję lambda, która konwertuje stan docelowy nadrzędnej funkcji przejścia na wartość logiczną.
Szczegółowe informacje znajdziesz w artykułach AnimatedVisibility i AnimatedContent.
var selected by remember { mutableStateOf(false) } // Animates changes when `selected` is changed. val transition = updateTransition(selected, label = "selected state") val borderColor by transition.animateColor(label = "border color") { isSelected -> if (isSelected) Color.Magenta else Color.White } val elevation by transition.animateDp(label = "elevation") { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( onClick = { selected = !selected }, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, borderColor), shadowElevation = elevation ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text(text = "Hello, world!") // AnimatedVisibility as a part of the transition. transition.AnimatedVisibility( visible = { targetSelected -> targetSelected }, enter = expandVertically(), exit = shrinkVertically() ) { Text(text = "It is fine today.") } // AnimatedContent as a part of the transition. transition.AnimatedContent { targetState -> if (targetState) { Text(text = "Selected") } else { Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone") } } } }
Umieszczaj przejście i umożliwiaj ponowne korzystanie z nich
W przypadku prostych zastosowań definiowanie animacji przejść w tym samym komponencie co interfejs użytkownika jest całkowicie wystarczające. Jeśli jednak pracujesz nad złożonym komponentem z wieloma animowanymi wartościami, możesz chcieć oddzielić implementację animacji od kompozycyjnego interfejsu użytkownika.
Możesz to zrobić, tworząc klasę, która zawiera wszystkie wartości animacji, oraz funkcję „update”, która zwraca instancję tej klasy. Implementację przejścia można wyodrębnić do nowej, oddzielnej funkcji. Ten wzorzec jest przydatny, gdy trzeba scentralizować logikę animacji lub umożliwić wielokrotne wykorzystanie złożonych animacji.
enum class BoxState { Collapsed, Expanded } @Composable fun AnimatingBox(boxState: BoxState) { val transitionData = updateTransitionData(boxState) // UI tree Box( modifier = Modifier .background(transitionData.color) .size(transitionData.size) ) } // Holds the animation values. private class TransitionData( color: State<Color>, size: State<Dp> ) { val color by color val size by size } // Create a Transition and return its animation values. @Composable private fun updateTransitionData(boxState: BoxState): TransitionData { val transition = updateTransition(boxState, label = "box state") val color = transition.animateColor(label = "color") { state -> when (state) { BoxState.Collapsed -> Color.Gray BoxState.Expanded -> Color.Red } } val size = transition.animateDp(label = "size") { state -> when (state) { BoxState.Collapsed -> 64.dp BoxState.Expanded -> 128.dp } } return remember(transition) { TransitionData(color, size) } }
Tworzenie animacji powtarzanej w nieskończoność za pomocą rememberInfiniteTransition
InfiniteTransition
zawiera co najmniej 1 animację podrzędną, tak jak Transition
, ale animacje zaczynają się odtwarzać, gdy tylko zostaną dodane do kompozycji, i nie przestaną, dopóki nie zostaną usunięte. Możesz utworzyć instancję InfiniteTransition
za pomocą rememberInfiniteTransition
. Animacje podrzędne można dodawać za pomocą właściwości animateColor
, animatedFloat
i animatedValue
. Musisz też określić infiniteRepeatable, aby podać specyfikację animacji.
val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Red, targetValue = Color.Green, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Box( Modifier .fillMaxSize() .background(color) )
Interfejsy API animacji niskiego poziomu
Wszystkie interfejsy API animacji wysokiego poziomu wymienione w poprzedniej sekcji są oparte na interfejsach API animacji niskiego poziomu.
Funkcje animate*AsState
to najprostsze interfejsy API, które powodują natychmiastową zmianę wartości jako wartość animacji. Jest ono obsługiwane przez interfejs Animatable
, który jest interfejsem API opartym na korobojacji służącym do animowania pojedynczej wartości. updateTransition
tworzy obiekt przejścia, który może zarządzać wieloma animowanymi wartościami i uruchamiać je na podstawie zmiany stanu. rememberInfiniteTransition
działa podobnie, ale tworzy nieskończone przejście, które może zarządzać wieloma animacjami, które będą działać w nieskończoność. Wszystkie te interfejsy API są kompozycjami, z wyjątkiem interfejsu Animatable
, co oznacza, że te animacje można tworzyć poza kompozycją.
Wszystkie te interfejsy API są oparte na bardziej podstawowym interfejsie Animation
. Chociaż większość aplikacji nie będzie wchodzić w bezpośrednią interakcję z interfejsem Animation
, niektóre funkcje dostosowywania interfejsu Animation
są dostępne za pomocą interfejsów API wyższego poziomu. Więcej informacji o opcjach AnimationVector
i AnimationSpec
znajdziesz w artykule Dostosowywanie animacji.
Animatable
: animacja pojedynczej wartości na podstawie współprogramu
Animatable
to uchwyt wartości, który może animować wartość podczas jej zmiany za pomocą animateTo
. To interfejs API obsługujący implementację animate*AsState
.
Zapewnia to spójne kontynuowanie i wykluczanie się wzajemne, co oznacza, że zmiana wartości jest zawsze ciągła, a każda trwająca animacja zostanie anulowana.
Wiele funkcji usługi Animatable
, w tym animateTo
, jest udostępnianych jako funkcje zawieszone. Oznacza to, że muszą być one zawinięte w odpowiednim zakresie współbieżności. Możesz na przykład użyć komponentu LaunchedEffect
, aby utworzyć zakres tylko na czas trwania określonej wartości klucza.
// Start out gray and animate to green/red based on `ok` val color = remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box( Modifier .fillMaxSize() .background(color.value) )
W tym przykładzie tworzymy i zapamiętujemy instancję zmiennej Animatable
o wartości początkowej Color.Gray
. W zależności od wartości flagi logicznej ok
kolor zmienia się na Color.Green
lub Color.Red
. Każda kolejna zmiana wartości logicznej powoduje rozpoczęcie animacji do innego koloru. Jeśli w momencie zmiany wartości trwa animacja, zostaje ona anulowana, a nowa animacja rozpoczyna się od bieżącej wartości migawki z bieżącą prędkością.
To jest implementacja animacji, która obsługuje interfejs API animate*AsState
wymieniony w poprzedniej sekcji. W porównaniu z wersją animate*AsState
używanie Animatable
bezpośrednio daje nam bardziej szczegółową kontrolę pod wieloma względami. Po pierwsze,
Animatable
może mieć inną wartość początkową niż pierwsza wartość docelowa.
Na przykład w tym przykładzie kodu najpierw wyświetla się szare pole, które natychmiast zaczyna się animować na zielono lub na czerwono. Po drugie Animatable
udostępnia więcej operacji na wartości treści, czyli snapTo
i animateDecay
. snapTo
natychmiast ustawia bieżącą wartość na wartość docelową. Jest to przydatne, gdy sama animacja nie jest jedynym źródłem informacji i musi być synchronizowana z innymi stanami, takimi jak zdarzenia dotykowe. animateDecay
uruchamia animację, która zwalnia z danej prędkości. Jest to przydatne podczas implementowania działania fling. Więcej informacji znajdziesz w sekcji Gesty i animacja.
Domyślnie Animatable
obsługuje Float
i Color
, ale można użyć dowolnego typu danych, podając TwoWayConverter
. Więcej informacji znajdziesz w AnimationVector.
Specyfikacje animacji możesz dostosować, podając AnimationSpec
.
Więcej informacji znajdziesz w specyfikacji AnimationSpec.
Animation
: animacja sterowana ręcznie
Animation
to interfejs API animacji o najniższym poziomie dostępu. Wiele z animacji, które do tej pory widzieliśmy, powstało na podstawie Animation. Istnieją 2 podtypy Animation
:
TargetBasedAnimation
i DecayAnimation
.
Elementu Animation
należy używać tylko do ręcznego sterowania czasem animacji.
Animation
jest bezstanowy i nie ma żadnej koncepcji cyklu życia. Służy on jako mechanizm obliczania animacji wykorzystywany przez interfejsy API wyższego poziomu.
TargetBasedAnimation
Inne interfejsy API obsługują większość przypadków użycia, ale korzystanie bezpośrednio z TargetBasedAnimation
pozwala Ci kontrolować czas odtwarzania animacji. W poniższym przykładzie czas odtwarzania TargetAnimation
jest ręcznie kontrolowany na podstawie czasu kadru podanego przez withFrameNanos
.
val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) }
DecayAnimation
W przeciwieństwie do TargetBasedAnimation
DecayAnimation
nie wymaga podania targetValue
. Zamiast tego oblicza wartość targetValue
na podstawie warunków początkowych ustawionych przez initialVelocity
i initialValue
oraz podanych wartości DecayAnimationSpec
.
Animacje wygaszania są często używane po użyciu gestu przesunięcia, aby spowolnić elementy aż do zatrzymania. Prędkość animacji zaczyna się od wartości określonej przez initialVelocityVector
i z czasem zwalnia.
Obecnie nie ma rekomendacji.
Zaloguj się na swoje konto Google.