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

,

Reimagining Android Dialogs with Jetpack Compose

Traditional Android dialogs are hard to test, easy to leak, and painful to customize — and in a world of Compose-first apps, they’re overdue for an upgrade.
Watch Video

Reimagining Android Dialogs with Jetpack Compose

Keith Abdulla
Staff Android Engineer, Design Systems
Block

Reimagining Android Dialogs with Jetpack Compose

Keith Abdulla
Staff Android Engine ...
Block

Reimagining Android Dialogs with Jetpack Compose

Keith Abdulla
Staff Android Engineer, D ...
Block

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
Material3 is the newest iteration of Material Design, with dynamic theming, revised components and…
READ MORE
Menu