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

, ,

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

With the advent of Android 15, edge-to-edge design has become the default configuration. Consequently, applications must be capable of accommodating window insets, including the system status bar and navigation bar, as well as supporting drawing…
Watch Video

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility GmbH

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android develop ...
Seven Principles Mob ...

Cutting-Edge-to-Edge in Android 15: Using Previews and Testing in Jetpack Compose to Manage Insets.

Timo Drick
Lead Android developer
Seven Principles Mobility ...

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