Blog Infos
Author
Published
Topics
Published

In this series of articles we are trying to recreate this amazing clock animation

Animation by Oleg Frolov

 

Step 4: Assemble animation of a Clock Hand

Assemble animation without rotation

private fun calculateAssembleDistance(stepHeight: Float, currentHour: Int): Float =
    stepHeight * (23 - currentHour)
// Start calculation each time the animationAngle changes.
val assembleValue = remember(animationAngle) {
// We only need this animation for second rotation
if (animationAngle >= 360) {
// Reversed linear interpolation between 0..30 degrees, transformed into 0..1
(animationAngle % 30) / 30
} else -1f
}

Now we have to draw a dot transition, and based on the animation value, draw it in the proper place. As the dot will be drawn along with a clock hand, it should be put under the same rotation section as a clock hand is.

Spacer(
...
.drawBehind {
// A rotation section of a clock hand
rotate(animationAngle, pivot = center){
// Drawing a clock hand itself
drawLine(
color = Color.White,
start = center,
end = endOffset,
strokeWidth = strokeWidth,
)
// Drawing a clock hand
if (assembleValue != -1f) {
val positionY = halfStroke +
calculateAssembleDistance(stepHeight, currentHour) *
assembleValue
val start = Offset(size.width / 2, positionY - halfStroke)
val end = Offset(size.width / 2, positionY + halfStroke)
drawLine(
color = Color.White,
start = start,
end = end,
strokeWidth = strokeWidth
)
}
}})

Animation after Step 5

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Jetpack Compose: Drawing without pain and recomposition

This is a talk on recomposition in Jetpack Compose and the myths of too many calls it is followed by. I’ll briefly explain the reasons behind recompositions and why they are not as problematic as…
Watch Video

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jobs

Step 5: Parallel animations
val currentHourChannel = remember { Channel<Int>(12, BufferOverflow.DROP_OLDEST) }
val currentHourFlow = remember(currentHourChannel) { 
    currentHourChannel.receiveAsFlow() 
}
Sending Events
// Remove derivedStateOf
var currentHour by remember { mutableStateOf(0) }
LaunchedEffect(animationAngle) {
// Add hour calculation inside of a launchEffect
val newCurrentHour = animationAngle.toInt() / 30
if (newCurrentHour != currentHour) {
currentHour = newCurrentHour
// Sending currentHour through channel
currentHourChannel.trySend(currentHour)
}
}
Receiving events
val disassembleAnimations =
        remember { hours.map { Animatable(1f) } }

Channel events will be received through currentHourFlow. On each new event a new animation will be launched asynchronously with appropriate length and easing.

// Assume that duration is 1/12th of the whole duration, which equals to the length of 2 hours. It can be longer or shorter if necessary
val disassembleDuration = duration / 12
LaunchedEffect(currentHourFlow) {
currentHourFlow.collectLatest {
// launch each animation asynchronously
launch {
if (currentHour < 12) {
disassembleAnimations[currentHour].snapTo(0f)
// Set a tween spec with LinearOutSlowIn easing
disassembleAnimations[currentHour].animateTo(
1f,
tween(disassembleDuration, easing = LinearOutSlowInEasing)
)
}
}
}
}
Drawing an animation
Spacer(
...
.drawBehind {
...
hours.forEach {
if (!dotsVisibility[it]) return@forEach
val degree = it * 30f
rotate(degree) {
// Based on the hour value, the travel distance will be longer.
val positionY = halfStroke +
stepHeight * it * (1 - disassembleAnimations[it].value)
val start = Offset(size.width / 2, positionY - halfStroke)
val end = Offset(size.width / 2, positionY + halfStroke)
drawLine(
color = Color.White,
start = start,
end = end,
strokeWidth = strokeWidth,
)
}
}
}

Final animation!

🤔 But what if I tell you that we can write this animation in a single go, without creating a bunch of threads and animations? And a progress of which can be controlled with a slider? Like this

 

Animation controlled by a slider!

 

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
Menu