ursus
04/18/2022, 12:25 AMwhile(true) {
yield() <--- In practise it seems yield emulates <http://Handler.post|Handler.post>
emit(Unit)
}
Or should I just wrap the ValueAnimator as Flow and call it a day?ursus
04/18/2022, 12:56 AMAdam Powell
04/18/2022, 1:24 AMAdam Powell
04/18/2022, 1:27 AMChoreographer
. The way we work with this in Compose is exposed through a MonotonicFrameClock
CoroutineContext
element that is used by the withFrameNanos
API; the implementation we use for Compose UI has its entry point here: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]n/androidx/compose/ui/platform/AndroidUiFrameClock.android.ktAdam Powell
04/18/2022, 1:30 AMsuspendCancellableCoroutine
is used to run frame logic as part of the Choreographer frame callback and then the coroutine is resumed. The withFrameNanos
API accepts a lambda parameter of the code to run on the frame before layout and drawing run; this allows animations to affect the current frame rather than not run until the frame is complete. Android's choreographer runs frame callbacks, layout, and drawing atomically before any other dispatched coroutines can run. Coordinating this is the reason for the AndroidUiDispatcher
to exist.Adam Powell
04/18/2022, 1:32 AMwithFrameNanos
API from a coroutine launched as part of your composition, i.e. from LaunchedEffect
or a scope obtained from rememberCoroutineScope()
. You might do this from a suspend fun
in your ViewModel
like this:
suspend fun updateFrameProgress() {
withFrameNanos { frameTime ->
updateProgress(frameTime)
}
}
Adam Powell
04/18/2022, 1:33 AMLaunchedEffect
:
LaunchedEffect(myViewModel) {
while (true) { // will cancel as needed
myViewModel.updateFrameProgress()
}
}
Adam Powell
04/18/2022, 1:39 AMursus
04/18/2022, 1:42 AMursus
04/18/2022, 1:59 AMdata class Progress(val pageIndex: Int, val pageFraction)
where the fraction is normalized to 0F..1F
Would that not be a lot of garbage for your taste?Adam Powell
04/18/2022, 2:02 AMAdam Powell
04/18/2022, 2:03 AMwithFrameNanos
described above for Android without Compose can be as simple as:
suspend inline fun <R> withFrameNanos(crossinline block: (Long) -> R): R =
suspendCancellableCoroutine { continuation ->
val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
continuation.resumeWith(runCatching { block(frameTimeNanos) })
}
val choreographer = Choreographer.getInstance()
choreographer.postFrameCallback(frameCallback)
continuation.invokeOnCancellation { choreographer.removeFrameCallback(frameCallback) }
}
Adam Powell
04/18/2022, 2:06 AMAdam Powell
04/18/2022, 2:07 AMursus
04/18/2022, 2:07 AMursus
04/18/2022, 2:07 AMAdam Powell
04/18/2022, 2:08 AMAdam Powell
04/18/2022, 2:08 AMursus
04/18/2022, 2:09 AMAdam Powell
04/18/2022, 2:10 AMAdam Powell
04/18/2022, 2:11 AMAdam Powell
04/18/2022, 2:13 AMursus
04/18/2022, 2:14 AManimator.addUpdateListener {
val value = it.animatedValue as Float
}
public Object ValueAnimator.getAnimatedValue()
sooo it appears that if I ValueAnimator(0F, 1F)
then the animatedValue
is boxed anyways, right?Adam Powell
04/18/2022, 2:18 AMAdam Powell
04/18/2022, 2:19 AMursus
04/18/2022, 2:20 AMAdam Powell
04/18/2022, 2:20 AMursus
04/18/2022, 2:21 AM