diff --git a/README.md b/README.md index 8295263..eaee580 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,9 @@ https://user-images.githubusercontent.com/8353068/149623082-444043b8-61cf-43f0-9 ## [SpringPlayground.kt](app/src/main/java/com/antonshilov/composeanimations/SpringPlayground.kt) https://user-images.githubusercontent.com/8353068/174637974-3abf0e0c-071f-46aa-9423-474b486d0784.mp4 + +## AnimationGallery + +Use `AnimationGallery` to explore all available animations in one screen. The +list uses snapping scroll behavior so each example is centered as you swipe +through. Tap on any preview to open it in a dedicated screen. diff --git a/app/src/main/java/com/antonshilov/composeanimations/AirbnbStaggeredPhotos.kt b/app/src/main/java/com/antonshilov/composeanimations/AirbnbStaggeredPhotos.kt index 6e19b41..0da581d 100644 --- a/app/src/main/java/com/antonshilov/composeanimations/AirbnbStaggeredPhotos.kt +++ b/app/src/main/java/com/antonshilov/composeanimations/AirbnbStaggeredPhotos.kt @@ -36,6 +36,11 @@ private val rotationDegrees = listOf(0f, -10f, 10f) @Preview() @Composable private fun PreviewStaggeredPhotos() { + AirbnbStaggeredPhotos() +} + +@Composable +fun AirbnbStaggeredPhotos() { var run by remember { mutableStateOf(true) } Box( Modifier @@ -69,7 +74,6 @@ private fun PreviewStaggeredPhotos() { ) } } - } @Composable diff --git a/app/src/main/java/com/antonshilov/composeanimations/AnimationGallery.kt b/app/src/main/java/com/antonshilov/composeanimations/AnimationGallery.kt new file mode 100644 index 0000000..5d0a787 --- /dev/null +++ b/app/src/main/java/com/antonshilov/composeanimations/AnimationGallery.kt @@ -0,0 +1,70 @@ +package com.antonshilov.composeanimations + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.foundation.clickable +import androidx.compose.ui.unit.dp + +data class AnimationItem(val title: String, val content: @Composable () -> Unit) + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun AnimationGallery( + modifier: Modifier = Modifier, + onItemSelected: (AnimationItem) -> Unit = {} +) { + val listState = rememberLazyListState() + val fling = rememberSnapFlingBehavior(listState) + val animations = listOf( + AnimationItem("Certificates stack") { CertificatesStack() }, + AnimationItem("Airbnb row photos") { AirbnbRowPhotos() }, + AnimationItem("Airbnb staggered photos") { AirbnbStaggeredPhotos() }, + AnimationItem("Swipe button") { ConfirmationButton() }, + AnimationItem("Spring playground") { SpringPlayground() }, + ) + LazyColumn( + modifier = modifier.fillMaxSize(), + state = listState, + flingBehavior = fling, + verticalArrangement = Arrangement.spacedBy(24.dp), + contentPadding = PaddingValues(16.dp) + ) { + items(animations) { item -> + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { onItemSelected(item) }, + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + ) { + Column( + Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = item.title, style = MaterialTheme.typography.titleLarge) + Box(Modifier.padding(top = 16.dp)) { + item.content() + } + } + } + } + } +} diff --git a/app/src/main/java/com/antonshilov/composeanimations/MainActivity.kt b/app/src/main/java/com/antonshilov/composeanimations/MainActivity.kt index edf5eed..86ea410 100644 --- a/app/src/main/java/com/antonshilov/composeanimations/MainActivity.kt +++ b/app/src/main/java/com/antonshilov/composeanimations/MainActivity.kt @@ -3,8 +3,22 @@ package com.antonshilov.composeanimations import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Surface import com.antonshilov.composeanimations.ui.theme.ComposeAnimationsTheme +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -12,9 +26,36 @@ class MainActivity : ComponentActivity() { setContent { ComposeAnimationsTheme { Surface { - CertificatesStack() + AppContent() } } } } } + +private sealed interface Screen { + data object Gallery : Screen + data class Detail(val item: AnimationItem) : Screen +} + +@Composable +private fun AppContent() { + var screen: Screen by remember { mutableStateOf(Screen.Gallery) } + when (val current = screen) { + Screen.Gallery -> AnimationGallery { screen = Screen.Detail(it) } + is Screen.Detail -> AnimationDetail(item = current.item, onBack = { screen = Screen.Gallery }) + } +} + +@Composable +private fun AnimationDetail(item: AnimationItem, onBack: () -> Unit) { + BackHandler(onBack = onBack) + Box(Modifier.fillMaxSize()) { + IconButton(onClick = onBack, modifier = Modifier.align(Alignment.TopStart)) { + Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Back") + } + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + item.content() + } + } +} diff --git a/app/src/main/java/com/antonshilov/composeanimations/SwipeButton.kt b/app/src/main/java/com/antonshilov/composeanimations/SwipeButton.kt index 3c51212..db42d1d 100644 --- a/app/src/main/java/com/antonshilov/composeanimations/SwipeButton.kt +++ b/app/src/main/java/com/antonshilov/composeanimations/SwipeButton.kt @@ -1,3 +1,5 @@ +package com.antonshilov.composeanimations + import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.gestures.AnchoredDraggableState diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d1208b..78e0a44 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,26 +1,26 @@ [versions] # Plugins -androidGradlePlugin = "8.9.0" -kotlin = "2.1.10" +androidGradlePlugin = "8.10.1" +kotlin = "2.1.21" # Libraries -compose = "1.8.0-rc01" +compose = "1.8.2" composeCompiler = "1.5.15" -coreKtx = "1.15.0" -lifecycleRuntimeKtx = "2.8.7" +coreKtx = "1.16.0" +lifecycleRuntimeKtx = "2.9.0" activityCompose = "1.10.1" -coilCompose = "2.1.0" +coilCompose = "2.7.0" composeMaterialColors = "1.0.0" composeMaterialIcons = "1.7.8" # SDK versions -compileSdk = "35" +compileSdk = "36" minSdk = "21" -targetSdk = "35" +targetSdk = "36" # JVM targets jvmTarget = "1.8" -material3Android = "1.3.1" +material3Android = "1.3.2" [libraries] # AndroidX diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 81aa1c0..0b55a3b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists