Last active
March 29, 2022 11:00
-
-
Save parthdesai1208/7ba7fed88f6110cfa1129944cc41312a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
********************************************************************************************************************************* | |
AnimatedVisibility - without params | |
********************************************************************************************************************************* | |
@Preview(showSystemUi = true) | |
@Composable | |
fun SimpleAnimatedVisibility() { | |
var visibility by remember { mutableStateOf(true) } | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
AnimatedVisibility(visibility) { | |
Text(text = "Hello, world!") | |
} | |
Button(onClick = { visibility = !visibility }) { | |
Text(text = "Click Me") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
AnimatedVisibility - with params | |
********************************************************************************************************************************* | |
@Preview(showSystemUi = true) | |
@OptIn(ExperimentalAnimationApi::class) | |
@Composable | |
fun AnimateVisibility() { | |
var visible by remember { | |
mutableStateOf(true) | |
} | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
AnimatedVisibility( | |
visible = visible, | |
//execute when view is visible | |
enter = fadeIn(tween(4000)) + expandVertically( | |
animationSpec = tween( | |
4000, | |
easing = BounceInterpolator().toEasing() | |
) | |
), | |
//execute when view is gone | |
exit = fadeOut(tween(4000)) + shrinkVertically( | |
animationSpec = tween( | |
4000, | |
easing = BounceInterpolator().toEasing() | |
) | |
) | |
) { | |
Text(text = "Hello, world!",Modifier.background(MaterialTheme.colors.secondary)) | |
} | |
Button(onClick = { visible = !visible }) { | |
Text("Click Me") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
AnimatedVisibility - with state | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalAnimationApi::class) | |
@Composable | |
fun AnimateVisibilityState() { | |
val state = remember { | |
MutableTransitionState(false).apply { | |
// Start the animation immediately. | |
targetState = true | |
} | |
} | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
AnimatedVisibility( | |
visibleState = state, | |
//execute when view is visible | |
enter = fadeIn(tween(4000)) + expandVertically ( | |
animationSpec = tween(4000, | |
easing = BounceInterpolator().toEasing())), | |
//execute when view is gone | |
exit = fadeOut(tween(4000)) + shrinkVertically ( | |
animationSpec = tween(4000, | |
easing = BounceInterpolator().toEasing())) | |
) { | |
// Use the MutableTransitionState to know the current animation state | |
// of the AnimatedVisibility. | |
Text(text = when { | |
state.isIdle && state.currentState -> "Hello, World!" | |
!state.isIdle && state.currentState -> "Disappearing" | |
state.isIdle && !state.currentState -> "" | |
else -> "Appearing" | |
}) | |
} | |
Button(onClick = { state.targetState = !state.targetState }) { | |
Text("Click Me") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
enter exit visibility animation | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalAnimationApi::class) | |
@Composable | |
fun AnimateEnterExitChild() { | |
var visible by remember { | |
mutableStateOf(true) | |
} | |
var color by remember { | |
mutableStateOf(Color.Black) | |
} | |
Column( Modifier.fillMaxSize()) { | |
Button(onClick = { visible = !visible }) { | |
Text(if (visible) "Hide" else "Show") | |
} | |
Box(modifier = Modifier.fillMaxWidth().height(30.dp).background(color)) | |
AnimatedVisibility( | |
visible = visible, | |
enter = fadeIn(animationSpec = tween( | |
durationMillis = 3000, | |
easing = LinearOutSlowInEasing | |
) | |
), | |
exit = fadeOut(animationSpec = tween( | |
durationMillis = 3000, | |
easing = LinearOutSlowInEasing | |
) | |
) | |
) { | |
val background by transition.animateColor(label = "") { state -> | |
when(state) { | |
EnterExitState.PreEnter -> Color.Red | |
EnterExitState.PostExit -> Color.Green | |
EnterExitState.Visible -> Color.Blue | |
} | |
} | |
color = background | |
Box( | |
Modifier | |
.weight(1f) | |
.fillMaxWidth() | |
.background(background) | |
) { | |
Box( | |
Modifier | |
.align(Alignment.Center) | |
.animateEnterExit( | |
// Slide in/out the inner box. | |
enter = slideInVertically( | |
animationSpec = tween( | |
durationMillis = 3000, | |
easing = LinearOutSlowInEasing | |
) | |
), | |
exit = slideOutVertically( | |
animationSpec = tween( | |
durationMillis = 3000, | |
easing = LinearOutSlowInEasing | |
) | |
) | |
) | |
.sizeIn(minWidth = 256.dp, minHeight = 64.dp) | |
.background(Color.Red) | |
) { | |
// Content of the notification… | |
} | |
} | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
trigger animation when any state changes using crossfade() | |
********************************************************************************************************************************* | |
@Composable | |
fun CrossFade() { | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
var currentPage by remember { mutableStateOf(0) } | |
Crossfade( | |
targetState = currentPage, | |
animationSpec = tween(durationMillis = 1000) | |
) { screen -> ColorBox(screen) | |
} | |
Button(onClick = { | |
currentPage = (0..0xFFFFFF).random() | |
}) { | |
Text("Click Me") | |
} | |
} | |
} | |
@Composable | |
fun ColorBox(screen: Int) { | |
Box( | |
Modifier | |
.size(100.dp) | |
.background(Color(screen + 0xFF000000)), | |
contentAlignment = Alignment.Center | |
) { | |
Text(get6DigitHex(screen), color = contrastColor(screen)) | |
} | |
} | |
fun get6DigitHex(value: Int): String { | |
return "0x" + "%x".format(value).padStart(6, '0').toUpperCase(Locale.current) | |
} | |
fun contrastColor(color: Int): Color { | |
return if (ColorUtils.calculateLuminance(color) < 0.5) | |
Color.White | |
else | |
Color.Black | |
} | |
********************************************************************************************************************************* | |
trigger animation when any state changes using animateTo() | |
********************************************************************************************************************************* | |
@Composable | |
fun AnimatableOnly() { | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
//var enabled by remember { mutableStateOf(true) } | |
var currentPage by remember { mutableStateOf(0) } | |
val color = remember { Animatable(Color.Gray) } | |
LaunchedEffect(currentPage) { //animateTo() is suspend function | |
color.animateTo( | |
//target value must be color because we use color.animateTo() | |
targetValue = Color(currentPage + 0xFF000000), | |
animationSpec = tween( | |
durationMillis = 3000, | |
easing = LinearOutSlowInEasing | |
) | |
) | |
} | |
Box( | |
Modifier | |
.size(100.dp) | |
.background(color.value) | |
) | |
Button(onClick = { currentPage = (0..0xFFFFFF).random() }) { | |
Text("Click Me") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
AnimatedContent - with targetState | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalAnimationApi::class) | |
@Composable | |
fun AnimatedContentSimple() { | |
Row(modifier = Modifier.fillMaxSize(), | |
verticalAlignment = Alignment.CenterVertically, | |
horizontalArrangement = Arrangement.Center) { | |
var count by remember { mutableStateOf(0) } | |
Button(onClick = { count++ }) { | |
Text("Add") | |
} | |
AnimatedContent(targetState = count) { targetCount -> | |
Text(text = "Count: $targetCount", modifier = Modifier.padding(start = 8.dp)) | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
AnimatedContent - with targetState, transitionSpec ex-1 | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalAnimationApi::class) | |
@Composable | |
fun AnimatedContentSimple() { | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
var currentPage by remember { mutableStateOf(0) } | |
Box { | |
Crossfade( | |
targetState = currentPage, | |
animationSpec = animationSpec() | |
) { screen -> ColorBoxOnly(screen) } | |
AnimatedContent(targetState = currentPage, | |
transitionSpec = { | |
if (targetState > initialState) { | |
upColorTransition() | |
} else { | |
downColorTransition() | |
} //display animation outside the box | |
.using(SizeTransform(clip = false)) | |
} | |
) { screen -> | |
ColorBoxTextOnly(screen) | |
} | |
} | |
Button(onClick = { | |
currentPage = (0..0xFFFFFF).random() | |
}) { | |
Text("Click Me") | |
} | |
} | |
} | |
@OptIn(ExperimentalAnimationApi::class) | |
private fun downColorTransition() = | |
slideInHorizontally( | |
initialOffsetX = { fullWidth -> -fullWidth }, | |
animationSpec = animationSpec() | |
) + fadeIn( | |
animationSpec = animationSpec() | |
) with slideOutVertically( | |
targetOffsetY = { fullHeight -> fullHeight }, | |
animationSpec = animationSpec() | |
) + fadeOut(animationSpec = animationSpec()) | |
@OptIn(ExperimentalAnimationApi::class) | |
private fun upColorTransition() = | |
slideInHorizontally( | |
initialOffsetX = { fullWidth -> fullWidth }, | |
animationSpec = animationSpec() | |
) + fadeIn( | |
animationSpec = animationSpec() | |
) with slideOutVertically( | |
targetOffsetY = { fullHeight -> -fullHeight }, | |
animationSpec = animationSpec() | |
) + fadeOut(animationSpec = animationSpec()) | |
fun <T> animationSpec() = tween<T>( | |
durationMillis = 3000, | |
easing = LinearOutSlowInEasing | |
) | |
@Composable | |
fun ColorBoxOnly(screen: Int) { | |
Box( | |
Modifier | |
.size(100.dp) | |
.background(Color(screen + 0xFF000000)) | |
) | |
} | |
@Composable | |
fun ColorBoxTextOnly(screen: Int) { | |
Box( | |
Modifier.size(100.dp), | |
contentAlignment = Alignment.Center | |
) { | |
Text(get6DigitHex(screen), color = contrastColor(screen)) | |
} | |
} | |
********************************************************************************************************************************* | |
AnimatedContent - with targetState, transitionSpec ex-2 | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalAnimationApi::class) | |
@Composable | |
fun AnimatedContentSimple() { | |
Row( | |
modifier = Modifier.fillMaxSize(), | |
verticalAlignment = Alignment.CenterVertically, | |
horizontalArrangement = Arrangement.Center | |
) { | |
var count by remember { mutableStateOf(0) } | |
Button(onClick = { count++ }) { | |
Text(text = "Add") | |
} | |
Button(onClick = { count-- }, modifier = Modifier.padding(start = 8.dp)) { | |
Text(text = "Minus") | |
} | |
AnimatedContent( | |
targetState = count, | |
transitionSpec = { | |
if (targetState > initialState) { | |
// If the target number is larger than old value | |
slideInVertically { height -> height } + fadeIn() with | |
slideOutVertically { height -> -height } + fadeOut() | |
} else { | |
// If the target number is smaller than old value | |
slideInVertically { height -> -height } + fadeIn() with | |
slideOutVertically { height -> height } + fadeOut() | |
}.using( | |
//for adding effect on slide up-down animation | |
SizeTransform(clip = false) | |
) | |
} | |
) { targetCount -> | |
Text(text = "$targetCount", modifier = Modifier.padding(start = 8.dp)) | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
AnimatedContent - with targetState, transitionSpec ex-3 between 2 content | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalAnimationApi::class, androidx.compose.material.ExperimentalMaterialApi::class) | |
@Composable | |
fun AnimatedContentSimple() { | |
var expanded by remember { mutableStateOf(false) } | |
Surface( | |
color = MaterialTheme.colors.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() | |
} | |
} | |
} | |
} | |
@Composable | |
fun Expanded() { | |
Text( | |
text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.", | |
modifier = Modifier.padding(10.dp) | |
) | |
} | |
@Composable | |
fun ContentIcon() { | |
Image( | |
painter = painterResource(id = R.drawable.ic_baseline_call_24), | |
contentDescription = "call icon", | |
modifier = Modifier.padding(10.dp) | |
) | |
} | |
********************************************************************************************************************************* | |
animateContentSize | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalAnimationApi::class) | |
@Composable | |
fun AnimatedContentSize() { | |
Column { | |
var expanded by remember { | |
mutableStateOf(false) | |
} | |
Image( | |
painter = painterResource( | |
id = if (expanded) | |
R.drawable.download | |
else | |
R.drawable.ic_launcher_background | |
), | |
contentDescription = "", | |
modifier = Modifier | |
.background(Color.Yellow) | |
.animateContentSize(tween(1500)) | |
) | |
Button(onClick = { expanded = !expanded }) { | |
Text(if (expanded) "Hide" else "Show") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
Animate Content Size Transform | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalAnimationApi::class) | |
@Composable | |
fun AnimatedContentSizeTransform() { | |
val time = 500 | |
Column { | |
var expanded by remember { | |
mutableStateOf(false) | |
} | |
AnimatedContent( | |
targetState = expanded, | |
transitionSpec = { | |
if (targetState) { | |
expandFading(time) using expandSizing(time) | |
} else { | |
shrinkFading(time) using shrinkSizing(time) | |
} | |
} | |
) { targetExpanded -> | |
Image( | |
painter = painterResource( | |
id = if (targetExpanded) | |
R.drawable.download | |
else | |
R.drawable.ic_launcher_background | |
), | |
contentDescription = "", | |
modifier = Modifier.background(Color.Yellow) | |
) | |
} | |
Button(onClick = { expanded = !expanded }) { | |
Text(if (expanded) "Hide" else "Show") | |
} | |
} | |
} | |
@OptIn(ExperimentalAnimationApi::class) | |
private fun shrinkSizing(time: Int) = | |
SizeTransform { initialSize, targetSize -> | |
keyframes { | |
// Shrink to target height first | |
IntSize(initialSize.width, targetSize.height) at time | |
// Then shrink to target width | |
durationMillis = time * 3 | |
} | |
} | |
@OptIn(ExperimentalAnimationApi::class) | |
private fun shrinkFading(time: Int) = | |
fadeIn(animationSpec = tween(time, time * 2)) with | |
fadeOut(animationSpec = tween(time * 3)) | |
@OptIn(ExperimentalAnimationApi::class) | |
private fun expandSizing(time: Int) = | |
SizeTransform { initialSize, targetSize -> | |
keyframes { | |
// Expand to target width first | |
IntSize(targetSize.width, initialSize.height) at time | |
// Then expand to target height | |
durationMillis = time * 3 | |
} | |
} | |
@OptIn(ExperimentalAnimationApi::class) | |
private fun expandFading(time: Int) = | |
fadeIn(animationSpec = tween(time * 3)) with | |
fadeOut(animationSpec = tween(time)) | |
********************************************************************************************************************************* | |
animateFloatAsState | |
********************************************************************************************************************************* | |
@Composable | |
fun AnimateAsState() { | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
var enabled by remember { mutableStateOf(true) } | |
val alpha: Float by animateFloatAsState( | |
if (enabled) 1f else 0.2f, | |
animationSpec = tween( | |
durationMillis = 3000, | |
easing = LinearOutSlowInEasing | |
) | |
) | |
Box( | |
Modifier | |
.size(100.dp) | |
.graphicsLayer(alpha = alpha) | |
.background(Color.Red) | |
) | |
Button(onClick = { enabled = !enabled }) { | |
Text("Click Me") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
animateColorAsState | |
********************************************************************************************************************************* | |
@Composable | |
fun AnimateAsState() { | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
var currentPage by remember { mutableStateOf(0) } | |
val color : Color by animateColorAsState(targetValue = Color(currentPage + 0xFF000000)) | |
Box( | |
Modifier | |
.size(100.dp) | |
.background(color) | |
) | |
Button(onClick = { currentPage = (0..0xFFFFFF).random() }) { | |
Text("Click Me") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
animateDpAsState | |
********************************************************************************************************************************* | |
enum class BikePosition { | |
Start, Finish | |
} | |
@Preview | |
@Composable | |
fun BikeScreen() { | |
var bikeState by remember { mutableStateOf(BikePosition.Start) } | |
val offsetAnimation: Dp by animateDpAsState( | |
if (bikeState == BikePosition.Start) 5.dp else 300.dp, | |
) | |
Box( | |
modifier = Modifier | |
.fillMaxSize() | |
) { | |
Image( | |
painter = painterResource(R.drawable.ic_launcher_foreground), | |
contentDescription = null, | |
modifier = Modifier | |
.height(90.dp) | |
.absoluteOffset(x = offsetAnimation) | |
) | |
Button( | |
onClick = { | |
bikeState = when (bikeState) { | |
BikePosition.Start -> BikePosition.Finish | |
BikePosition.Finish -> BikePosition.Start | |
} | |
}, modifier = Modifier | |
.fillMaxSize() | |
.wrapContentSize(align = Alignment.Center) | |
) { | |
Text(text = "Ride") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
animateSizeAsState | |
********************************************************************************************************************************* | |
@Composable | |
fun AnimateAsState() { | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
var isOn by remember { mutableStateOf(true) } | |
val size1: Size by animateSizeAsState(targetValue = if (isOn) Size(100f,100f) else Size(200f,200f)) | |
Box(Modifier.background(MaterialTheme.colors.onSurface).size(width = size1.width.dp, height = size1.height.dp)) | |
Button(onClick = { isOn = !isOn }) { | |
Text("Click Me") | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
updateTransition-1 | |
********************************************************************************************************************************* | |
@Composable | |
fun UpdateTransitionBasic1() { | |
//change in boxState will invoke the animation | |
var boxState by remember { mutableStateOf(BoxState1.small) } | |
//create updateTransition object | |
val transition = updateTransition(targetState = boxState, label = "Box transition") | |
val color by transition.animateColor( | |
label = "Color", | |
transitionSpec = { tween(2000, easing = FastOutSlowInEasing) } | |
) { | |
when (it) { | |
BoxState1.small -> Red | |
BoxState1.large -> Yellow | |
} | |
} | |
val size by transition.animateDp(label = "size", | |
transitionSpec = { tween(2000, easing = LinearOutSlowInEasing) }) { | |
when (it) { | |
BoxState1.small -> 32.dp | |
BoxState1.large -> 128.dp | |
} | |
} | |
Column { | |
Button(onClick = { | |
boxState = if (boxState == BoxState1.small) BoxState1.large else BoxState1.small | |
}) { | |
Text(text = "Toggle") | |
} | |
Box( | |
Modifier | |
.background(color = color) | |
.size(size) | |
) | |
} | |
} | |
private enum class BoxState1 { | |
small, large | |
} | |
********************************************************************************************************************************* | |
updateTransition-2 | |
********************************************************************************************************************************* | |
enum class BoxState { | |
Collapsed, | |
Expanded | |
} | |
@Composable | |
fun UpdateTransitionBasic() { | |
var currentState by remember { mutableStateOf(BoxState.Collapsed) } | |
val transition = updateTransition(targetState = currentState, label = "") | |
val rect by transition.animateRect(transitionSpec = transitioningSpec(), label = "") { state -> | |
when (state) { | |
BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) | |
BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) | |
} | |
} | |
val color by transition.animateColor( | |
transitionSpec = transitioningSpec(), | |
label = "" | |
) { state -> | |
when (state) { | |
BoxState.Collapsed -> MaterialTheme.colors.primary | |
BoxState.Expanded -> MaterialTheme.colors.secondary | |
} | |
} | |
val borderWidth by transition.animateDp( | |
transitionSpec = transitioningSpec(), | |
label = "" | |
) { state -> | |
when (state) { | |
BoxState.Collapsed -> 5.dp | |
BoxState.Expanded -> 20.dp | |
} | |
} | |
Column { | |
Canvas( | |
modifier = Modifier | |
.fillMaxWidth() | |
.height(500.dp) | |
.border(BorderStroke(borderWidth, Color.Green)) | |
) { | |
drawPath(Path().apply { addRect(rect) }, color) | |
} | |
Button(onClick = { | |
currentState = if (currentState == BoxState.Expanded) | |
BoxState.Collapsed else BoxState.Expanded | |
}) { | |
Text("Click Me") | |
} | |
} | |
} | |
@Composable | |
fun <T> transitioningSpec(): @Composable() (Transition.Segment<BoxState>.() -> FiniteAnimationSpec<T>) = | |
{ | |
when { | |
BoxState.Expanded isTransitioningTo BoxState.Collapsed -> | |
spring(stiffness = 20f, dampingRatio = 0.25f) | |
else -> | |
tween(durationMillis = 3000) | |
} | |
} | |
********************************************************************************************************************************* | |
UpdateTransitionChild | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalTransitionApi::class) | |
@Composable | |
fun UpdateTransitionChild() { | |
var currentState by remember { mutableStateOf(BoxState.Collapsed) } | |
val transition = updateTransition(currentState, label = "") | |
val rect by transition.animateRect(transitionSpec = transitioningSpec(), label = "") { state -> | |
when (state) { | |
BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) | |
BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) | |
} | |
} | |
Column { | |
Canvas( | |
modifier = Modifier.fillMaxWidth().height(200.dp) | |
.border(BorderStroke(1.dp, Color.Green)) | |
) { | |
drawPath(Path().apply { addRect(rect) }, Color.Red) | |
} | |
Child(transition.createChildTransition { currentState }) | |
Button(onClick = { | |
currentState = | |
if (currentState == BoxState.Expanded) BoxState.Collapsed | |
else BoxState.Expanded | |
}) { | |
Text("Click Me") | |
} | |
} | |
//for launch as expanded by default for the first time | |
LaunchedEffect(Unit) { | |
currentState = BoxState.Expanded | |
} | |
} | |
@Composable | |
fun Child(transition: Transition<BoxState>) { | |
val rect by transition.animateRect(transitionSpec = transitioningSpec(), label = "") { state -> | |
when (state) { | |
BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) | |
BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) | |
} | |
} | |
Column { | |
Canvas( | |
modifier = Modifier.fillMaxWidth().height(200.dp) | |
.border(BorderStroke(1.dp, Color.Green)) | |
) { | |
drawPath(Path().apply { addRect(rect) }, Color.Red) | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
multiple animation using updateTransition | |
********************************************************************************************************************************* | |
@OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class) | |
@Composable | |
fun UpdateTransitionExtension() { | |
var selected by remember { mutableStateOf(false) } | |
val transition = updateTransition(targetState = selected, label = "") | |
val borderColor by transition.animateColor(label = "") { isSelected -> | |
if (isSelected) Color.Magenta else Color.White | |
} | |
val elevation by transition.animateDp(label = "") { isSelected -> | |
if (isSelected) 10.dp else 2.dp | |
} | |
Surface( | |
onClick = { selected = !selected }, | |
shape = RoundedCornerShape(8.dp), | |
border = BorderStroke(2.dp, borderColor), | |
elevation = elevation, | |
modifier = Modifier.padding(10.dp) | |
) { | |
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 { | |
androidx.compose.material.Icon( | |
imageVector = Icons.Default.Phone, | |
contentDescription = "Phone" | |
) | |
} | |
} | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
InfiniteAnimation | |
********************************************************************************************************************************* | |
@Composable | |
fun InfiniteAnimation() { | |
val infiniteTransition = rememberInfiniteTransition() | |
val color by infiniteTransition.animateColor( | |
initialValue = Color.Red, | |
targetValue = Color.Green, | |
animationSpec = infiniteRepeatable( | |
animation = tween(1000, easing = LinearEasing), | |
repeatMode = RepeatMode.Reverse | |
) | |
) | |
Box(Modifier.fillMaxSize().background(color)) | |
} | |
********************************************************************************************************************************* | |
TargetBasedAnimation | |
********************************************************************************************************************************* | |
@Composable | |
fun TargetBasedAnimationFun() { | |
var state by remember { | |
mutableStateOf(0) | |
} | |
val anim = remember { | |
TargetBasedAnimation( | |
animationSpec = tween(2000), | |
typeConverter = Float.VectorConverter, | |
initialValue = 100f, | |
targetValue = 300f | |
) | |
} | |
var playTime by remember { mutableStateOf(0L) } | |
var animationValue by remember { | |
mutableStateOf(0) | |
} | |
LaunchedEffect(state) { | |
val startTime = withFrameNanos { it } | |
do { | |
playTime = withFrameNanos { it } - startTime | |
animationValue = anim.getValueFromNanos(playTime).toInt() | |
} while (!anim.isFinishedFromNanos(playTime)) | |
} | |
Box(modifier = Modifier.fillMaxSize(1f),contentAlignment = Alignment.Center) { | |
Box(modifier = Modifier | |
.size(animationValue.dp) | |
.background(Color.Red,shape = RoundedCornerShape(animationValue/5)) | |
.clickable { | |
state++ | |
},contentAlignment = Alignment.Center) { | |
Text(text = animationValue.toString(),style = TextStyle(color = Color.White,fontSize = (animationValue/5).sp)) | |
} | |
} | |
} | |
********************************************************************************************************************************* | |
spring | |
********************************************************************************************************************************* | |
enum class BikePosition { | |
Start, Finish | |
} | |
@Preview | |
@Composable | |
fun BikeScreen() { | |
var bikeState by rememberSaveable { mutableStateOf(BikePosition.Start) } | |
//100.dp = 90.dp is width of the image & 10.dp is for padding | |
val targetValue = if (bikeState == BikePosition.Start) 5.dp else getScreenWidth().dp - 100.dp | |
val offsetAnimationStiffnessLow: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(stiffness = Spring.StiffnessLow) | |
) | |
val offsetAnimationStiffnessVeryLow: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(stiffness = Spring.StiffnessVeryLow) | |
) | |
val offsetAnimationStiffnessMediumLow: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(stiffness = Spring.StiffnessMediumLow) | |
) | |
val offsetAnimationStiffnessMedium: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(stiffness = Spring.StiffnessMedium) | |
) | |
val offsetAnimationStiffnessHigh: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(stiffness = Spring.StiffnessHigh) | |
) | |
val offsetAnimationDampingRatioNoBouncy: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy) | |
) | |
val offsetAnimationDampingRatioLowBouncy: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(dampingRatio = Spring.DampingRatioLowBouncy) | |
) | |
val offsetAnimationDampingRatioMediumBouncy: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy) | |
) | |
val offsetAnimationDampingRatioHighBouncy: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = spring(dampingRatio = Spring.DampingRatioHighBouncy) | |
) | |
Box(modifier = Modifier.fillMaxSize()) { | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.verticalScroll(rememberScrollState()) | |
) { | |
Text(text = "StiffnessLow") | |
DrawImage(offsetAnimationStiffnessLow) | |
Text(text = "StiffnessVeryLow") | |
DrawImage(offsetAnimationStiffnessVeryLow) | |
Text(text = "StiffnessMediumLow") | |
DrawImage(offsetAnimationStiffnessMediumLow) | |
Text(text = "StiffnessMedium") | |
DrawImage(offsetAnimationStiffnessMedium) | |
Text(text = "StiffnessHigh") | |
DrawImage(offsetAnimationStiffnessHigh) | |
Text(text = "DampingRatioNoBouncy") | |
DrawImage(offsetAnimationDampingRatioNoBouncy) | |
Text(text = "DampingRatioLowBouncy") | |
DrawImage(offsetAnimationDampingRatioLowBouncy) | |
Text(text = "DampingRatioMediumBouncy") | |
DrawImage(offsetAnimationDampingRatioMediumBouncy) | |
Text(text = "DampingRatioHighBouncy") | |
DrawImage(offsetAnimationDampingRatioHighBouncy) | |
Spacer(modifier = Modifier.height(100.dp).fillMaxWidth()) | |
} | |
ExtendedFloatingActionButton( | |
onClick = { | |
bikeState = when (bikeState) { | |
BikePosition.Start -> BikePosition.Finish | |
BikePosition.Finish -> BikePosition.Start | |
} | |
}, modifier = Modifier | |
.align(alignment = Alignment.BottomEnd) | |
.padding(end = 16.dp, bottom = 16.dp), text = { Text(text = "Ride") } | |
) | |
} | |
} | |
@Composable | |
fun DrawImage(stiffness: Dp) { | |
Image( | |
painter = painterResource(R.drawable.cycle), | |
contentDescription = null, | |
modifier = Modifier | |
.width(90.dp) | |
.height(90.dp) | |
.absoluteOffset(x = stiffness) | |
) | |
} | |
@Composable | |
fun getScreenWidth(): Float { | |
val context = LocalContext.current | |
val displayMetrics: DisplayMetrics = context.resources.displayMetrics | |
return displayMetrics.widthPixels / displayMetrics.density | |
} | |
********************************************************************************************************************************* | |
tween | |
********************************************************************************************************************************* | |
@Preview | |
@Composable | |
fun BikeScreen() { | |
var bikeState by rememberSaveable { mutableStateOf(BikePosition.Start) } | |
//100.dp = 90.dp is width of the image & 10.dp is for padding | |
val targetValue = if (bikeState == BikePosition.Start) 5.dp else getScreenWidth().dp - 100.dp | |
val easingLinearEasing : Dp by animateDpAsState( | |
targetValue, | |
animationSpec = tween(durationMillis = 1000,easing = LinearEasing) | |
) | |
val easingLinearOutSlowInEasing: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = tween(durationMillis = 1000, easing = LinearOutSlowInEasing) | |
) | |
val easingFastOutLinearInEasing: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = tween(durationMillis = 1000, easing = FastOutLinearInEasing) | |
) | |
val easingFastOutSlowInEasing: Dp by animateDpAsState( | |
targetValue, | |
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing) | |
) | |
val withDelay : Dp by animateDpAsState( | |
targetValue, | |
animationSpec = tween(durationMillis = 1000, delayMillis = 1000) | |
) | |
Box(modifier = Modifier.fillMaxSize()) { | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.verticalScroll(rememberScrollState()) | |
) { | |
Text(text = "LinearEasing") | |
DrawImage(easingLinearEasing) | |
Text(text = "LinearOutSlowInEasing") | |
DrawImage(easingLinearOutSlowInEasing) | |
Text(text = "FastOutLinearInEasing") | |
DrawImage(easingFastOutLinearInEasing) | |
Text(text = "FastOutLinearInEasing") | |
DrawImage(easingFastOutSlowInEasing) | |
Text(text = "withDelay") | |
DrawImage(withDelay) | |
Spacer(modifier = Modifier.height(100.dp).fillMaxWidth()) | |
} | |
ExtendedFloatingActionButton( | |
onClick = { | |
bikeState = when (bikeState) { | |
BikePosition.Start -> BikePosition.Finish | |
BikePosition.Finish -> BikePosition.Start | |
} | |
}, modifier = Modifier | |
.align(alignment = Alignment.BottomEnd) | |
.padding(end = 16.dp, bottom = 16.dp), text = { Text(text = "Ride") } | |
) | |
} | |
} | |
********************************************************************************************************************************* | |
keyframes | |
********************************************************************************************************************************* | |
@Preview | |
@Composable | |
fun BikeScreen() { | |
var bikeState by rememberSaveable { mutableStateOf(BikePosition.Start) } | |
//100.dp = 90.dp is width of the image & 10.dp is for padding | |
val targetValue = if (bikeState == BikePosition.Start) 5.dp else getScreenWidth().dp - 100.dp | |
val keyframesAnimation : Dp by animateDpAsState( | |
targetValue, | |
animationSpec = keyframes { | |
durationMillis = 1000 | |
50.dp at 400 with LinearOutSlowInEasing // for 0-400 ms | |
70.dp at 800 with FastOutLinearInEasing // for 400-800 ms | |
} | |
) | |
Box(modifier = Modifier.fillMaxSize()) { | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.verticalScroll(rememberScrollState()) | |
) { | |
Text(text = "keyframes") | |
DrawImage(keyframesAnimation) | |
Spacer(modifier = Modifier.height(100.dp).fillMaxWidth()) | |
} | |
ExtendedFloatingActionButton( | |
onClick = { | |
bikeState = when (bikeState) { | |
BikePosition.Start -> BikePosition.Finish | |
BikePosition.Finish -> BikePosition.Start | |
} | |
}, modifier = Modifier | |
.align(alignment = Alignment.BottomEnd) | |
.padding(end = 16.dp, bottom = 16.dp), text = { Text(text = "Ride") } | |
) | |
} | |
} | |
********************************************************************************************************************************* | |
repeatable | |
********************************************************************************************************************************* | |
@Preview | |
@Composable | |
fun BikeScreen() { | |
var bikeState by rememberSaveable { mutableStateOf(BikePosition.Start) } | |
//100.dp = 90.dp is width of the image & 10.dp is for padding | |
val targetValue = if (bikeState == BikePosition.Start) 5.dp else getScreenWidth().dp - 100.dp | |
val repeatableRestartAnimation : Dp by animateDpAsState( | |
targetValue, | |
animationSpec = repeatable(iterations = 5, animation = tween(), repeatMode = RepeatMode.Restart) | |
//will restart the animation from the start value to the end value. | |
) | |
val repeatableReverseAnimation : Dp by animateDpAsState( | |
targetValue, | |
animationSpec = repeatable(iterations = 5, animation = tween(), repeatMode = RepeatMode.Reverse) | |
//will reverse the last iteration as the animation repeats. | |
) | |
Box(modifier = Modifier.fillMaxSize()) { | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.verticalScroll(rememberScrollState()) | |
) { | |
Text(text = "repeatable Restart") | |
DrawImage(repeatableRestartAnimation) | |
Spacer(modifier = Modifier.height(150.dp).fillMaxWidth()) | |
Text(text = "repeatable Reverse") | |
DrawImage(repeatableReverseAnimation) | |
} | |
ExtendedFloatingActionButton( | |
onClick = { | |
bikeState = when (bikeState) { | |
BikePosition.Start -> BikePosition.Finish | |
BikePosition.Finish -> BikePosition.Start | |
} | |
}, modifier = Modifier | |
.align(alignment = Alignment.BottomEnd) | |
.padding(end = 16.dp, bottom = 16.dp), text = { Text(text = "Ride") } | |
) | |
} | |
} | |
********************************************************************************************************************************* | |
infiniteRepeatable | |
********************************************************************************************************************************* | |
@Preview | |
@Composable | |
fun BikeScreen() { | |
var bikeState by rememberSaveable { mutableStateOf(BikePosition.Start) } | |
//100.dp = 90.dp is width of the image & 10.dp is for padding | |
val targetValue = if (bikeState == BikePosition.Start) 5.dp else getScreenWidth().dp - 100.dp | |
val infiniteRepeatableRestartAnimation : Dp by animateDpAsState( | |
targetValue, | |
animationSpec = infiniteRepeatable(animation = tween(), repeatMode = RepeatMode.Restart) | |
//will restart the animation from the start value to the end value. | |
) | |
val infiniteRepeatableReverseAnimation : Dp by animateDpAsState( | |
targetValue, | |
animationSpec = infiniteRepeatable(animation = tween(), repeatMode = RepeatMode.Reverse) | |
//will reverse the last iteration as the animation repeats. | |
) | |
Box(modifier = Modifier.fillMaxSize()) { | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.verticalScroll(rememberScrollState()) | |
) { | |
Text(text = "infinite repeatable Restart") | |
DrawImage(infiniteRepeatableRestartAnimation) | |
Spacer(modifier = Modifier.height(150.dp).fillMaxWidth()) | |
Text(text = "infinite repeatable Reverse") | |
DrawImage(infiniteRepeatableReverseAnimation) | |
} | |
ExtendedFloatingActionButton( | |
onClick = { | |
bikeState = when (bikeState) { | |
BikePosition.Start -> BikePosition.Finish | |
BikePosition.Finish -> BikePosition.Start | |
} | |
}, modifier = Modifier | |
.align(alignment = Alignment.BottomEnd) | |
.padding(end = 16.dp, bottom = 16.dp), text = { Text(text = "Ride") } | |
) | |
} | |
} | |
********************************************************************************************************************************* | |
snap | |
********************************************************************************************************************************* | |
@Preview | |
@Composable | |
fun BikeScreen() { | |
var bikeState by rememberSaveable { mutableStateOf(BikePosition.Start) } | |
//100.dp = 90.dp is width of the image & 10.dp is for padding | |
val targetValue = if (bikeState == BikePosition.Start) 5.dp else getScreenWidth().dp - 100.dp | |
val snapAnimation : Dp by animateDpAsState( | |
targetValue, | |
animationSpec = snap(delayMillis = 500) | |
) | |
Box(modifier = Modifier.fillMaxSize()) { | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.verticalScroll(rememberScrollState()) | |
) { | |
Text(text = "snap animation") | |
DrawImage(snapAnimation) | |
Spacer(modifier = Modifier.height(150.dp).fillMaxWidth()) | |
} | |
ExtendedFloatingActionButton( | |
onClick = { | |
bikeState = when (bikeState) { | |
BikePosition.Start -> BikePosition.Finish | |
BikePosition.Finish -> BikePosition.Start | |
} | |
}, modifier = Modifier | |
.align(alignment = Alignment.BottomEnd) | |
.padding(end = 16.dp, bottom = 16.dp), text = { Text(text = "Ride") } | |
) | |
} | |
} | |
********************************************************************************************************************************* | |
AnimationVector - TypeConverter | |
********************************************************************************************************************************* | |
//animate circle on touch | |
@Composable | |
fun PointerInput() { | |
//here we want to animate circle to touch position | |
val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } | |
Box( | |
modifier = Modifier | |
.fillMaxSize() | |
.pointerInput(Unit) { | |
coroutineScope { | |
while (true) { | |
// Detect a tap event and obtain its position. | |
val position = awaitPointerEventScope { | |
awaitFirstDown().position //consume tap down event & its position | |
} | |
launch { | |
// Animate to the tap position. | |
offset.animateTo( | |
position, | |
animationSpec = tween( | |
durationMillis = 500, | |
easing = LinearOutSlowInEasing | |
) | |
) | |
} | |
} | |
} | |
} | |
) { | |
Circle(modifier = Modifier.offset { offset.value.toIntOffset() }) | |
} | |
} | |
private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt()) | |
@Composable | |
fun Circle( | |
color: Color = Color.Red, | |
modifier: Modifier = Modifier | |
) { | |
Box( | |
modifier = modifier | |
.size(26.dp) | |
.clip(CircleShape) | |
.background(color) | |
) | |
} | |
********************************************************************************************************************************* | |
xxxx | |
********************************************************************************************************************************* | |
fun TimeInterpolator.toEasing() = Easing { x -> getInterpolation(x) } | |
********************************************************************************************************************************* | |
xxxx | |
********************************************************************************************************************************* |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
AnimatedVisibility - without params
AnimatedVisibility - with params
AnimatedVisibility - with state
enter exit visibility animation