This project demonstrates how to implement shared element transitions using Jetpack Compose Animation and type-safe navigation with serialization. It also utilizes Coil to fetch images from the internet through URLs.
Clone the repository:
git clone https://github.com/Bhavyansh03-tech/Shared_Element_Transition.git
Open the project in Android Studio.
Sync the project with Gradle files.
Add the following dependencies in your libs.versions.toml
file:
[versions]
# COIL COMPOSE :->
coil = "2.6.0"
# ANIMATION :->
animation = "1.7.0-beta05"
foundation = "1.7.0-beta05"
# NAVIGATION :->
navigationCompose = "2.8.0-beta05"
kotlinxSerializationJson = "1.7.1"
[libraries]
# ANIMATION :->
androidx-animation = { module = "androidx.compose.animation:animation", version.ref = "animation" }
androidx-animation-core = { module = "androidx.compose.animation:animation-core", version.ref = "animation" }
androidx-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
# NAVIGATION :->
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
# COIL COMPOSE :->
coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
[plugins]
# COMPOSE COMPILER :->
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
# NAVIGATION
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
To use Kotlin serialization, first, annotate your data class with @Serializable
:
import kotlinx.serialization.Serializable
@Serializable object Home
@Serializable data class Detail(
val imageIdArg: Int = 1
)
Then, pass the serialized data through the navigation:
composable<Detail> {
val args = it.toRoute<Detail>()
DetailScreen(
animatedVisibilityScope = this@composable,
imageId = args.imageIdArg, // Passing Image Id As Argument.
onClick = { navController.popBackStack() } // Going back to previous activity by removing it from back stack.
)
}
Shared element transitions are implemented for both images and text. Below is an example of how to achieve this:
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SharedTransitionScope.DetailScreen(
animatedVisibilityScope: AnimatedVisibilityScope,
imageId: Int,
onClick: () -> Unit
) {
Column(modifier = Modifier.fillMaxSize()) {
AsyncImage(
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = "image-${imageId}"),
animatedVisibilityScope = animatedVisibilityScope
)
.fillMaxWidth()
.height(350.dp)
.clickable { onClick() },
model = imageList[imageId - 1].photo,
contentDescription = null,
contentScale = ContentScale.Crop
)
Box(
modifier = Modifier
.fillMaxSize()
.padding(all = 12.dp)
) {
Text(
modifier = Modifier
.sharedBounds(
sharedContentState = rememberSharedContentState(key = "text-${imageId}"),
animatedVisibilityScope = animatedVisibilityScope
),
text = imageList[imageId - 1].title,
fontSize = MaterialTheme.typography.titleLarge.fontSize,
fontWeight = FontWeight.Medium
)
}
}
}
AsyncImage(
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = "image-${imageId}"),
animatedVisibilityScope = animatedVisibilityScope
)
.fillMaxWidth()
.height(350.dp)
.clickable { onClick() },
model = imageList[imageId - 1].photo,
contentDescription = null,
contentScale = ContentScale.Crop
)
Text(
modifier = Modifier
.sharedBounds(
sharedContentState = rememberSharedContentState(key = "text-${imageId}"),
animatedVisibilityScope = animatedVisibilityScope
),
text = imageList[imageId - 1].title,
fontSize = MaterialTheme.typography.titleLarge.fontSize,
fontWeight = FontWeight.Medium
)
Contributions are welcome! Please fork the repository and submit a pull request for any improvements or bug fixes.
git checkout -b feature/your-feature
).git commit -am 'Add some feature'
).git push origin feature/your-feature
).For questions or feedback, please contact @Bhavyansh03-tech.