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

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

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

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu