πͺ Jetpack Compose animation library that allows you to implement animations such as shared element transition.
Add the dependency below to your module's build.gradle
file:
dependencies {
implementation "com.github.skydoves:orbital:0.3.1"
}
Note: This is an experimental library that demonstrates various animations with Jetpack Compose. Please make sure that your project uses Jetpack Compose
1.5.4
, Compose Compiler1.5.4
, and Kotlin1.9.20
.
You can implement three kinds of animations with Orbital: Movement, Transformation, and Shared Element Transition.
Basically, you can run animation with Orbital
Composable function, which provides OrbitalScope
that allows you to create animations.
The example below shows how to implement resizing animation with the animateTransformation
extension of the OrbitalScope
.
The rememberContentWithOrbitalScope
allows you to create custom animations such as animateTransformation
on the OrbitalScope
.
You can apply the animateTransformation
animation to specific Composables and customize its AnimationSpec
as seen the below:
val transformationSpec = SpringSpec<IntSize>(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = 200f
)
var isTransformed by rememberSaveable { mutableStateOf(false) }
val poster = rememberContentWithOrbitalScope {
GlideImage(
modifier = if (isTransformed) {
Modifier.size(300.dp, 620.dp)
} else {
Modifier.size(100.dp, 220.dp)
}.animateTransformation(this, transformationSpec),
imageModel = ItemUtils.urls[0],
contentScale = ContentScale.Fit
)
}
Orbital(
modifier = Modifier
.clickable { isTransformed = !isTransformed }
) {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
poster()
}
}
The example below shows how to implement movement animation with the animateMovement
extension of the OrbitalScope
.
The rememberContentWithOrbitalScope
allows you to create custom animations such as animateMovement
on the OrbitalScope
.
You can apply the animateMovement
animation to specific Composables and customize its AnimationSpec
as seen the below:
val movementSpec = SpringSpec<IntOffset>(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = 200f
)
var isTransformed by rememberSaveable { mutableStateOf(false) }
val poster = rememberContentWithOrbitalScope {
GlideImage(
modifier = if (isTransformed) {
Modifier.size(360.dp, 620.dp)
} else {
Modifier.size(130.dp, 220.dp)
}.animateMovement(this, movementSpec),
imageModel = ItemUtils.urls[3],
contentScale = ContentScale.Fit
)
}
Orbital(
modifier = Modifier
.clickable { isTransformed = !isTransformed }
) {
if (isTransformed) {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
poster()
}
} else {
Column(
Modifier
.fillMaxSize()
.padding(20.dp),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.Bottom
) {
poster()
}
}
}
The example below shows how to implement shared element transition with the animateSharedElementTransition
extension of the OrbitalScope
.
The rememberContentWithOrbitalScope
allows you to create custom animations such as animateSharedElementTransition
on the OrbitalScope
.
You can apply the animateSharedElementTransition
animation to specific Composables and customize its AnimationSpec
.
Also, you can set the different AnimationSpec
s for the movement and transformation as seen the below:
@Composable
private fun OrbitalSharedElementTransitionExample() {
var isTransformed by rememberSaveable { mutableStateOf(false) }
val item = MockUtils.getMockPosters()[3]
val poster = rememberContentWithOrbitalScope {
GlideImage(
modifier = if (isTransformed) {
Modifier.fillMaxSize()
} else {
Modifier.size(130.dp, 220.dp)
}.animateSharedElementTransition(
this,
SpringSpec(stiffness = 500f),
SpringSpec(stiffness = 500f)
),
imageModel = item.poster,
contentScale = ContentScale.Fit
)
}
Orbital(
modifier = Modifier
.clickable { isTransformed = !isTransformed }
) {
if (isTransformed) {
PosterDetails(
poster = item,
sharedElementContent = { poster() },
pressOnBack = {}
)
} else {
Column(
Modifier
.fillMaxSize()
.padding(20.dp),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.Bottom
) {
poster()
}
}
}
}
Note: LookaheadLayout is a very experimental API, so measuring complex Composables might throw exceptions.
The example below shows how to implement shared element transition with multipe items. The basic concept of the usage is the same as the Shared Element Transition example.
var isTransformed by rememberSaveable { mutableStateOf(false) }
val items = rememberContentWithOrbitalScope {
ItemUtils.urls.forEach { url ->
GlideImage(
modifier = if (isTransformed) {
Modifier.size(140.dp, 180.dp)
} else {
Modifier.size(100.dp, 220.dp)
}
.animateSharedElementTransition(movementSpec, transformationSpec)
.padding(8.dp),
imageModel = url,
contentScale = ContentScale.Fit
)
}
}
Orbital(
modifier = Modifier
.fillMaxSize()
.clickable { isTransformed = !isTransformed },
isTransformed = isTransformed,
onStartContent = {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
items()
}
},
onTransformedContent = {
Row(
verticalAlignment = Alignment.CenterVertically
) { items() }
}
)
Support it by joining stargazers for this repository. β
Also, follow me on GitHub for my next creations! π€©
Designed and developed by 2022 skydoves (Jaewoong Eum)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.