Skip to content

Instantly share code, notes, and snippets.

@kazemihabib
Last active January 10, 2024 14:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kazemihabib/fb6ee75fc0da2528370d8452869953e9 to your computer and use it in GitHub Desktop.
Save kazemihabib/fb6ee75fc0da2528370d8452869953e9 to your computer and use it in GitHub Desktop.
Jetpack compose Solar system animation inside Interactive drawLayer based on https://gist.github.com/zach-klippenstein/e2c8e6edf0d950d8ba527cd0681c5b60
//image earth: https://media.prod.mdn.mozit.cloud/attachments/2012/07/09/1429/e2d55b8d5c9efd75a12112264d4ac091/Canvas_earth.png
//image sun: https://www.extremetech.com/wp-content/uploads/2020/01/NASA-Sun-640x611.jpg (scaled down to 20%)
//image sky: https://unblast.com/wp-content/uploads/2018/10/Sky-Stars-Pattern-1600x1190.jpg
import androidx.animation.*
import androidx.animation.Spring.DampingRatioHighBouncy
import androidx.animation.Spring.StiffnessLow
import androidx.compose.Composable
import androidx.compose.Model
import androidx.compose.remember
import androidx.ui.animation.Transition
import androidx.ui.animation.animate
import androidx.ui.core.Modifier
import androidx.ui.core.drawLayer
import androidx.ui.foundation.*
import androidx.ui.foundation.shape.corner.RoundedCornerShape
import androidx.ui.geometry.Offset
import androidx.ui.graphics.Color
import androidx.ui.graphics.ImageAsset
import androidx.ui.graphics.Paint
import androidx.ui.layout.*
import androidx.ui.material.Slider
import androidx.ui.material.SliderPosition
import androidx.ui.material.Surface
import androidx.ui.res.imageResource
import androidx.ui.unit.dp
import com.github.kazemihabib.bookapp.SolarModel.eachDayInMS
@Model
object SolarModel {
var eachDayInMS = 100
}
@Model
data class DrawModel(
var scaleX: Float = 1f,
var scaleY: Float = 1f,
var rotationX: Float = 0f,
var rotationY: Float = 0f,
var rotationZ: Float = 0f,
var elevation: Float = 0f,
var alpha: Float = 1f
)
@Composable
fun App() {
val model = remember { DrawModel() }
Column {
Config(model, SolarModel)
Spacer(modifier = Modifier.weight(1f))
SolarSystem(model)
}
}
@Composable
private fun Config(model: DrawModel, solarModel: SolarModel) {
LabeledSlider(
label = "ScaleX",
value = model.scaleX,
range = 0f..2f,
onChanged = model::scaleX::set
)
LabeledSlider(
label = "ScaleY",
value = model.scaleY,
range = 0f..2f,
onChanged = model::scaleY::set
)
LabeledSlider(
label = "RotationX",
value = model.rotationX,
range = -180f..180f,
onChanged = model::rotationX::set
)
LabeledSlider(
label = "RotationY",
value = model.rotationY,
range = -180f..180f,
onChanged = model::rotationY::set
)
LabeledSlider(
label = "RotationZ",
value = model.rotationZ,
range = -180f..180f,
onChanged = model::rotationZ::set
)
LabeledSlider(
label = "Elevation",
value = model.elevation,
range = 0f..10f,
onChanged = model::elevation::set
)
LabeledSlider(
label = "Alpha",
value = model.alpha,
range = 0f..1f,
onChanged = model::alpha::set
)
//I couldn't make this one work :(
LabeledSlider(
label = "EachDay in ms",
value = solarModel.eachDayInMS.toFloat(),
range = 100f..10000f,
onChanged = {solarModel.eachDayInMS = it.toInt()}
)
}
@Composable
private fun LabeledSlider(
label: String,
value: Float,
range: ClosedFloatingPointRange<Float>,
onChanged: (Float) -> Unit
) {
val position = SliderPosition(initial = 0f, valueRange = range)
position.value = value
Text(label)
Slider(position = position, onValueChange = onChanged)
}
@Composable
fun SolarSystem(model: DrawModel) {
val earthImage = imageResource(R.drawable.earth)
val sunImage = imageResource(R.drawable.sun)
val skyImage = imageResource(R.drawable.sky)
val anim = remember {
PhysicsBuilder<Float>(dampingRatio = DampingRatioHighBouncy, stiffness = StiffnessLow)
}
Box(gravity = ContentGravity.Center, modifier = Modifier.fillMaxWidth()) {
Surface(
shape = RoundedCornerShape(10.dp),
border = Border(1.dp, Color.Red),
modifier = Modifier.aspectRatio(1f).drawLayer(
scaleX = animate(model.scaleX, animBuilder = anim),
scaleY = animate(model.scaleY, animBuilder = anim),
rotationX = animate(model.rotationX, animBuilder = anim),
rotationY = animate(model.rotationY, animBuilder = anim),
rotationZ = animate(model.rotationZ, animBuilder = anim),
elevation = animate(model.elevation, animBuilder = anim),
alpha = model.alpha
)
) {
Transition(definition = transDef, initState = "start", toState = "end") { state ->
Canvas(modifier = Modifier.fillMaxSize()) {
save()
val minCanvasHeightWidth = minOf(size.width.value, size.height.value)
val sunOffset = Offset(size.width.value / 2, size.height.value / 2)
val earthDistanceFromSun = minCanvasHeightWidth / 2 * 0.5f
val moonDistanceFromEarth = minCanvasHeightWidth / 2 * 0.15f
drawBackground(image = skyImage, offset = Offset(0f, 0f))
drawSun(image = sunImage, offset = sunOffset)
translate(sunOffset.dx, sunOffset.dy)
rotate(state[earthRotation])
rotate(50f)
drawEarth(
image = earthImage,
offset = Offset(earthDistanceFromSun, earthDistanceFromSun)
)
translate(earthDistanceFromSun, earthDistanceFromSun)
rotate(state[moonRotation])
drawMoon(offset = Offset(moonDistanceFromEarth, moonDistanceFromEarth))
restore()
}
}
}
}
}
fun CanvasScope.drawBackground(image: ImageAsset, offset: Offset) {
val paint = Paint()
drawImage(image, offset, paint)
}
fun CanvasScope.drawSun(image: ImageAsset, offset: Offset) {
val paint = Paint()
val centeredImageOffset = offset - Offset(image.width/2f, image.height/2f)
drawImage(image, centeredImageOffset, paint)
}
fun CanvasScope.drawEarth(image: ImageAsset, offset: Offset) {
val paint = Paint()
val centeredImageOffset = offset - Offset(image.width/2f, image.height/2f)
drawImage(image, centeredImageOffset, paint)
}
fun CanvasScope.drawMoon(offset: Offset) {
val paint = Paint().apply { color = Color.White }
drawCircle(offset, radius = 7f, paint = paint)
}
private val earthRotation = FloatPropKey()
private val moonRotation = FloatPropKey()
private val transDef = transitionDefinition {
state("start") {
this[earthRotation] = 0f
this[moonRotation] = 0f
}
state("end") {
this[earthRotation] = 360f
this[moonRotation] = 360f
}
transition {
earthRotation using repeatable {
animation = tween {
duration = 365 * eachDayInMS
easing = LinearEasing
}
iterations = Infinite
}
moonRotation using repeatable {
animation = tween {
duration = 27 * eachDayInMS
easing = LinearEasing
}
iterations = Infinite
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment