[go: nahoru, domu]

blob: 8138f412c201475c8eb5c76c661b2c5a7d5c5ce4 [file] [log] [blame]
/*
* Copyright 2020 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.core.samples
import androidx.annotation.Sampled
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationEndReason
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.animation.core.exponentialDecay
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.splineBasedDecay
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.verticalDrag
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@Sampled
@Composable
fun AnimatableAnimateToGenericsType() {
// Creates an `Animatable` to animate Offset and `remember` it.
val animatedOffset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) }
Box(
Modifier.fillMaxSize().background(Color(0xffb99aff)).pointerInput(Unit) {
coroutineScope {
while (true) {
val offset = awaitPointerEventScope {
awaitFirstDown().position
}
// Launch a new coroutine for animation so the touch detection thread is not
// blocked.
launch {
// Animates to the pressed position, with the given animation spec.
animatedOffset.animateTo(
offset,
animationSpec = spring(stiffness = Spring.StiffnessLow)
)
}
}
}
}
) {
Text("Tap anywhere", Modifier.align(Alignment.Center))
Box(
Modifier
.offset {
IntOffset(
animatedOffset.value.x.roundToInt(),
animatedOffset.value.y.roundToInt()
)
}
.size(40.dp)
.background(Color(0xff3c1361), CircleShape)
)
}
}
@Sampled
fun AnimatableDecayAndAnimateToSample() {
fun Modifier.swipeToDismiss(): Modifier = composed {
// Creates a Float type `Animatable` and `remember`s it
val animatedOffset = remember { Animatable(0f) }
this.pointerInput(Unit) {
coroutineScope {
while (true) {
val pointerId = awaitPointerEventScope {
awaitFirstDown().id
}
val velocityTracker = VelocityTracker()
awaitPointerEventScope {
verticalDrag(pointerId) {
// Snaps the value by the amount of finger movement
launch {
animatedOffset.snapTo(animatedOffset.value + it.positionChange().y)
}
velocityTracker.addPosition(
it.uptimeMillis,
it.position
)
}
}
val velocity = velocityTracker.calculateVelocity().y
launch {
// Either fling vertically up, or spring back
val decay = splineBasedDecay<Float>(this@pointerInput)
// Checks where the animation will end using decay
if (decay.calculateTargetValue(
animatedOffset.value,
velocity
) < -size.height
) { // If the animation can naturally end outside of visual bounds, we will
// animate with decay.
// (Optionally) updates lower bounds. This stops the animation as soon
// as bounds are reached.
animatedOffset.updateBounds(
lowerBound = -size.height.toFloat()
)
// Animate with the decay animation spec using the fling velocity
animatedOffset.animateDecay(velocity, decay)
} else {
// Not enough velocity to be dismissed, spring back to 0f
animatedOffset.animateTo(0f, initialVelocity = velocity)
}
}
}
}
}.offset { IntOffset(0, animatedOffset.value.roundToInt()) }
}
}
@Sampled
fun AnimatableAnimationResultSample() {
suspend fun CoroutineScope.animateBouncingOffBounds(
animatable: Animatable<Offset, *>,
flingVelocity: Offset,
parentSize: Size
) {
launch {
var startVelocity = flingVelocity
// Set bounds for the animation, so that when it reaches bounds it will stop
// immediately. We can then inspect the returned `AnimationResult` and decide whether
// we should start another animation.
animatable.updateBounds(Offset(0f, 0f), Offset(parentSize.width, parentSize.height))
do {
val result = animatable.animateDecay(startVelocity, exponentialDecay())
// Copy out the end velocity of the previous animation.
startVelocity = result.endState.velocity
// Negate the velocity for the dimension that hits the bounds, to create a
// bouncing off the bounds effect.
with(animatable) {
if (value.x == upperBound?.x || value.x == lowerBound?.x) {
// x dimension hits bounds
startVelocity = startVelocity.copy(x = -startVelocity.x)
}
if (value.y == upperBound?.y || value.y == lowerBound?.y) {
// y dimension hits bounds
startVelocity = startVelocity.copy(y = -startVelocity.y)
}
}
// Repeat the animation until the animation ends for reasons other than hitting
// bounds, e.g. if `stop()` is called, or preempted by another animation.
} while (result.endReason == AnimationEndReason.BoundReached)
}
}
}
@Sampled
fun AnimatableFadeIn() {
fun Modifier.fadeIn(): Modifier = composed {
// Creates an `Animatable` and remembers it.
val alpha = remember { Animatable(0f) }
// Launches a coroutine for the animation when entering the composition.
// Uses `Unit` as the subject so the job in `LaunchedEffect` will run once, until it
// leaves composition.
LaunchedEffect(Unit) {
// Animates to 1f from 0f for the fade-in, and uses a 500ms tween animation.
alpha.animateTo(
targetValue = 1f,
// Default animationSpec uses [spring] animation, here we overwrite the default.
animationSpec = tween(500)
)
}
this.graphicsLayer(alpha = alpha.value)
}
}