Skip to content

Commit 960f6de

Browse files
authored
Added snippet for AnimatedVisibility usage with Shared elements (#272)
* Added AnimatedVisibility shared element examples. * Apply Spotless * Switch to using LazyColumn instead of Grid as it has better animation support at present. * Apply Spotless * Fixed import. * Apply Spotless * Seperate samples into different files, simplify snippet. * Apply Spotless --------- Co-authored-by: riggaroo <[email protected]>
1 parent 8abe5cc commit 960f6de

File tree

7 files changed

+480
-6
lines changed

7 files changed

+480
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
@file:OptIn(ExperimentalSharedTransitionApi::class)
18+
19+
package com.example.compose.snippets.animations.sharedelement
20+
21+
import androidx.compose.animation.AnimatedVisibility
22+
import androidx.compose.animation.BoundsTransform
23+
import androidx.compose.animation.ExperimentalSharedTransitionApi
24+
import androidx.compose.animation.SharedTransitionLayout
25+
import androidx.compose.animation.SharedTransitionScope
26+
import androidx.compose.animation.core.animateFloatAsState
27+
import androidx.compose.animation.core.tween
28+
import androidx.compose.animation.fadeIn
29+
import androidx.compose.animation.fadeOut
30+
import androidx.compose.animation.scaleIn
31+
import androidx.compose.animation.scaleOut
32+
import androidx.compose.foundation.background
33+
import androidx.compose.foundation.layout.Arrangement
34+
import androidx.compose.foundation.layout.Box
35+
import androidx.compose.foundation.layout.fillMaxSize
36+
import androidx.compose.foundation.layout.padding
37+
import androidx.compose.foundation.lazy.LazyColumn
38+
import androidx.compose.foundation.lazy.itemsIndexed
39+
import androidx.compose.foundation.shape.RoundedCornerShape
40+
import androidx.compose.runtime.Composable
41+
import androidx.compose.runtime.getValue
42+
import androidx.compose.runtime.mutableStateOf
43+
import androidx.compose.runtime.remember
44+
import androidx.compose.runtime.setValue
45+
import androidx.compose.ui.Modifier
46+
import androidx.compose.ui.draw.clip
47+
import androidx.compose.ui.draw.drawWithContent
48+
import androidx.compose.ui.graphics.BlurEffect
49+
import androidx.compose.ui.graphics.Color
50+
import androidx.compose.ui.graphics.TileMode
51+
import androidx.compose.ui.graphics.layer.GraphicsLayer
52+
import androidx.compose.ui.graphics.layer.drawLayer
53+
import androidx.compose.ui.graphics.rememberGraphicsLayer
54+
import androidx.compose.ui.tooling.preview.Preview
55+
import androidx.compose.ui.unit.dp
56+
import com.example.compose.snippets.R
57+
58+
private val listSnacks = listOf(
59+
Snack("Cupcake", "", R.drawable.cupcake),
60+
Snack("Donut", "", R.drawable.donut),
61+
Snack("Eclair", "", R.drawable.eclair),
62+
Snack("Froyo", "", R.drawable.froyo),
63+
Snack("Gingerbread", "", R.drawable.gingerbread),
64+
Snack("Honeycomb", "", R.drawable.honeycomb),
65+
)
66+
67+
private fun <T> animationSpec() = tween<T>(durationMillis = 500)
68+
private val boundsTransition = BoundsTransform { _, _ -> animationSpec() }
69+
private val shapeForSharedElement = RoundedCornerShape(16.dp)
70+
71+
@OptIn(ExperimentalSharedTransitionApi::class)
72+
@Preview
73+
@Composable
74+
private fun AnimatedVisibilitySharedElementBlurLayer() {
75+
var selectedSnack by remember { mutableStateOf<Snack?>(null) }
76+
val graphicsLayer = rememberGraphicsLayer()
77+
val animateBlurRadius = animateFloatAsState(
78+
targetValue = if (selectedSnack != null) 20f else 0f,
79+
label = "blur radius",
80+
animationSpec = animationSpec()
81+
)
82+
83+
SharedTransitionLayout(modifier = Modifier.fillMaxSize()) {
84+
LazyColumn(
85+
modifier = Modifier
86+
.fillMaxSize()
87+
.background(Color.LightGray.copy(alpha = 0.5f))
88+
.blurLayer(graphicsLayer, animateBlurRadius.value)
89+
.padding(16.dp),
90+
verticalArrangement = Arrangement.spacedBy(8.dp),
91+
) {
92+
itemsIndexed(listSnacks, key = { index, snack -> snack.name }) { index, snack ->
93+
SnackItem(
94+
snack = snack,
95+
onClick = {
96+
selectedSnack = snack
97+
},
98+
visible = selectedSnack != snack,
99+
modifier = Modifier.animateItem(
100+
placementSpec = animationSpec(),
101+
fadeOutSpec = animationSpec(),
102+
fadeInSpec = animationSpec()
103+
)
104+
)
105+
}
106+
}
107+
108+
SnackEditDetails(
109+
snack = selectedSnack,
110+
onConfirmClick = {
111+
selectedSnack = null
112+
}
113+
)
114+
}
115+
}
116+
117+
fun Modifier.blurLayer(layer: GraphicsLayer, radius: Float): Modifier {
118+
return if (radius == 0f) this else this.drawWithContent {
119+
layer.apply {
120+
record {
121+
this@drawWithContent.drawContent()
122+
}
123+
// will apply a blur on API 31+
124+
this.renderEffect = BlurEffect(radius, radius, TileMode.Decal)
125+
}
126+
drawLayer(layer)
127+
}
128+
}
129+
@Composable
130+
fun SharedTransitionScope.SnackItem(
131+
snack: Snack,
132+
visible: Boolean,
133+
onClick: () -> Unit,
134+
modifier: Modifier = Modifier
135+
) {
136+
AnimatedVisibility(
137+
modifier = modifier,
138+
visible = visible,
139+
enter = fadeIn(animationSpec = animationSpec()) + scaleIn(
140+
animationSpec()
141+
),
142+
exit = fadeOut(animationSpec = animationSpec()) + scaleOut(
143+
animationSpec()
144+
)
145+
) {
146+
Box(
147+
modifier = Modifier
148+
.sharedBounds(
149+
sharedContentState = rememberSharedContentState(key = "${snack.name}-bounds"),
150+
animatedVisibilityScope = this,
151+
boundsTransform = boundsTransition,
152+
clipInOverlayDuringTransition = OverlayClip(shapeForSharedElement)
153+
)
154+
.background(Color.White, shapeForSharedElement)
155+
.clip(shapeForSharedElement)
156+
) {
157+
SnackContents(
158+
snack = snack,
159+
modifier = Modifier.sharedElement(
160+
state = rememberSharedContentState(key = snack.name),
161+
animatedVisibilityScope = this@AnimatedVisibility,
162+
boundsTransform = boundsTransition,
163+
),
164+
onClick = onClick
165+
)
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)