[go: nahoru, domu]

blob: debcf13e5f496cd830dec711253f112a38037a67 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.animation
import androidx.compose.animation.core.AnimationClockObservable
import androidx.compose.animation.core.AnimationVector
import androidx.compose.animation.core.PropKey
import androidx.compose.animation.core.TransitionAnimation
import androidx.compose.animation.core.TransitionDefinition
import androidx.compose.animation.core.TransitionState
import androidx.compose.Composable
import androidx.compose.Stable
import androidx.compose.getValue
import androidx.compose.mutableStateOf
import androidx.compose.onPreCommit
import androidx.compose.remember
import androidx.compose.setValue
import androidx.ui.core.AnimationClockAmbient
/**
* [transition] composable creates a state-based transition using the animation configuration
* defined in [TransitionDefinition]. This can be especially useful when animating multiple
* values from a predefined set of values to another. For animating a single value, consider using
* [animatedValue], [animatedFloat], [animatedColor] or the more light-weight [animate] APIs.
*
* [transition] starts a new animation or changes the on-going animation when the [toState]
* parameter is changed to a different value. It dutifully ensures that the animation will head
* towards new [toState] regardless of what state (or in-between state) it’s currently in: If the
* transition is not currently animating, having a new [toState] value will start a new animation,
* otherwise the in-flight animation will correct course and animate towards the new [toState]
* based on the interruption handling logic.
*
* [transition] takes a transition definition, a target state and child composables.
* These child composables will be receiving a [TransitionState] object as an argument, which
* captures all the current values of the animation. Child composables should read the animation
* values from the [TransitionState] object, and apply the value wherever necessary.
*
* @sample androidx.compose.animation.samples.TransitionSample
*
* @param definition Transition definition that defines states and transitions
* @param toState New state to transition to
* @param clock Optional animation clock that pulses animations when time changes. By default,
* the system uses a choreographer based clock read from the [AnimationClockAmbient].
* A custom implementation of the [AnimationClockObservable] (such as a
* [androidx.compose.animation.core.ManualAnimationClock]) can be supplied here if there’s a need to
* manually control the clock (for example in tests).
* @param initState Optional initial state for the transition. When undefined, the initial state
* will be set to the first [toState] seen in the transition.
* @param label Optional label for distinguishing different transitions in Android Studio.
* @param onStateChangeFinished An optional listener to get notified when state change animation
* has completed
*
* @return a [TransitionState] instance, from which the animation values can be read
*
* @see [TransitionDefinition]
*/
// TODO: The list of params is getting a bit long. Consider grouping them.
@Composable
fun <T> transition(
definition: TransitionDefinition<T>,
toState: T,
clock: AnimationClockObservable = AnimationClockAmbient.current,
initState: T = toState,
label: String? = null,
onStateChangeFinished: ((T) -> Unit)? = null
): TransitionState {
if (transitionsEnabled) {
val disposableClock = clock.asDisposableClock()
val model = remember(definition, disposableClock) {
TransitionModel(definition, initState, disposableClock, label)
}
model.anim.onStateChangeFinished = onStateChangeFinished
// TODO(b/150674848): Should be onCommit, but that posts to the Choreographer. Until that
// callback is executed, nothing is aware that the animation is kicked off, so if
// Espresso checks for idleness between now and then, it will think all is idle.
onPreCommit(model, toState) {
model.anim.toState(toState)
}
return model
} else {
return remember(definition, toState) { definition.getStateFor(toState) }
}
}
/**
* Stores the enabled state for [transition] animations. Useful for tests to disable
* animations and have reliable screenshot tests.
*/
var transitionsEnabled = true
// TODO(Doris): Use Clock idea instead of TransitionModel with pulse
@Stable
private class TransitionModel<T>(
transitionDef: TransitionDefinition<T>,
initState: T,
clock: AnimationClockObservable,
label: String?
) : TransitionState {
private var animationPulse by mutableStateOf(0L)
internal val anim: TransitionAnimation<T> =
TransitionAnimation(transitionDef, clock, initState, label).apply {
onUpdate = {
animationPulse++
}
}
override fun <T, V : AnimationVector> get(propKey: PropKey<T, V>): T {
// we need to access the animationPulse so Compose will record this state values usage.
@Suppress("UNUSED_VARIABLE")
val pulse = animationPulse
return anim[propKey]
}
}