identity: add the shared content transitions
expandEnter/collapseExit (revealable sections), itemEnter (resolved content), animateItemMotion (lazy-list relayout) and fadeThrough — all driven by the M3 Expressive motion scheme and honouring rememberReduceMotion. Drawn from Calendula's motion helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", versi
|
|||||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation" }
|
androidx-foundation = { group = "androidx.compose.foundation", name = "foundation" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||||
|
androidx-animation = { group = "androidx.compose.animation", name = "animation" }
|
||||||
androidx-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
|
androidx-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
|
||||||
|
|
||||||
junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }
|
junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.foundation)
|
implementation(libs.androidx.foundation)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
|
implementation(libs.androidx.animation)
|
||||||
// activity-compose for PredictiveBackHandler + BackEventCompat (the back-gesture peek).
|
// activity-compose for PredictiveBackHandler + BackEventCompat (the back-gesture peek).
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package de.jeanlucmakiola.floret.identity
|
||||||
|
|
||||||
|
import androidx.compose.animation.ContentTransform
|
||||||
|
import androidx.compose.animation.EnterTransition
|
||||||
|
import androidx.compose.animation.ExitTransition
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The family's content transitions, driven by the active M3 Expressive motion
|
||||||
|
* scheme so every app reveals, collapses and re-lays-out the same way. Each
|
||||||
|
* honours [rememberReduceMotion]: when the user has asked the system to remove
|
||||||
|
* animations, the spatial movement is dropped and only a quick fade remains.
|
||||||
|
*
|
||||||
|
* Drawn from Calendula's motion helpers; pair [expandEnter] with [collapseExit]
|
||||||
|
* on an `AnimatedVisibility`, use [itemEnter] for content revealed once it
|
||||||
|
* resolves, [animateItemMotion] for `LazyColumn`/`LazyRow` row relayout, and
|
||||||
|
* [fadeThrough] to swap whole blocks with no spatial direction.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enter for a vertically-revealed section (expandable rows, inline fields):
|
||||||
|
* height grows from the top while fading in; a quick fade only under reduced
|
||||||
|
* motion. Pair with [collapseExit].
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun expandEnter(reduceMotion: Boolean = rememberReduceMotion()): EnterTransition {
|
||||||
|
val fade = fadeIn(MaterialTheme.motionScheme.fastEffectsSpec())
|
||||||
|
return if (reduceMotion) fade else expandVertically(MaterialTheme.motionScheme.fastSpatialSpec()) + fade
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Exit counterpart to [expandEnter]: shrink + fade, or fade only under reduced motion. */
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun collapseExit(reduceMotion: Boolean = rememberReduceMotion()): ExitTransition {
|
||||||
|
val fade = fadeOut(MaterialTheme.motionScheme.fastEffectsSpec())
|
||||||
|
return if (reduceMotion) fade else shrinkVertically(MaterialTheme.motionScheme.fastSpatialSpec()) + fade
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enter for content revealed by an `AnimatedContent`/`AnimatedVisibility` (e.g.
|
||||||
|
* results once a query resolves): a gentle rise + fade; fade only under reduced
|
||||||
|
* motion.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun itemEnter(reduceMotion: Boolean = rememberReduceMotion()): EnterTransition {
|
||||||
|
val fade = fadeIn(MaterialTheme.motionScheme.fastEffectsSpec())
|
||||||
|
return if (reduceMotion) fade else fade + slideInVertically(MaterialTheme.motionScheme.fastSpatialSpec()) { h -> h / 6 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `LazyItemScope.animateItem` wired to the app's motion scheme instead of
|
||||||
|
* Compose's default spring, so list rows fade/relocate consistently. Returns a
|
||||||
|
* bare [Modifier] under reduced motion so rows snap into place. Requires stable
|
||||||
|
* item keys.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun LazyItemScope.animateItemMotion(reduceMotion: Boolean = rememberReduceMotion()): Modifier =
|
||||||
|
if (reduceMotion) {
|
||||||
|
Modifier
|
||||||
|
} else {
|
||||||
|
Modifier.animateItem(
|
||||||
|
fadeInSpec = MaterialTheme.motionScheme.fastEffectsSpec(),
|
||||||
|
placementSpec = MaterialTheme.motionScheme.fastSpatialSpec(),
|
||||||
|
fadeOutSpec = MaterialTheme.motionScheme.fastEffectsSpec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cross-fade [ContentTransform] for swapping whole screens or blocks with no
|
||||||
|
* meaningful spatial direction (e.g. onboarding gates). Pure opacity, so it
|
||||||
|
* doubles as its own reduced-motion form.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun fadeThrough(): ContentTransform {
|
||||||
|
val fade = MaterialTheme.motionScheme.fastEffectsSpec<Float>()
|
||||||
|
return fadeIn(fade).togetherWith(fadeOut(fade))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user