zhuinden
03/03/2021, 2:41 PMzhuinden
03/03/2021, 2:42 PMmodifier.then(
when (stateChange.direction) {
StateChange.FORWARD -> Modifier.graphicsLayer(translationX = fullWidth + (-1) * fullWidth * animationProgress)
StateChange.BACKWARD -> Modifier.graphicsLayer(translationX = -1 * fullWidth + fullWidth * animationProgress)
else /* REPLACE */ -> Modifier.graphicsLayer(alpha = 0 + animationProgress)
}
)
and it's based on this code:
var isAnimating by remember { mutableStateOf(true) } // true renders previous initially for fullWidth
val scope = rememberCoroutineScope()
val lerping = Animatable(0.0f, Float.VectorConverter, 1.0f) // DO NOT REMEMBER!
var animationProgress by remember { mutableStateOf(0.0f) }
var fullWidth by remember { mutableStateOf(0) }
var fullHeight by remember { mutableStateOf(0) }
val measurePolicy = MeasurePolicy { measurables, constraints ->
val placeables = measurables.fastMap { it.measure(constraints) }
val maxWidth = placeables.fastMaxBy { it.width }?.width ?: 0
val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
if (fullWidth == 0) {
fullWidth = maxWidth
}
if (fullHeight == 0) {
fullHeight = maxHeight
}
layout(maxWidth, maxHeight) {
placeables.fastForEach { placeable ->
placeable.place(0, 0)
}
}
}
Layout(
content = {
if (fullWidth > 0 && fullHeight > 0) {
key(topNewKey) { topNewKey.RenderComposable(modifier) }
}
},
measurePolicy = measurePolicy,
modifier = when {
!isAnimating -> modifier
else -> animationConfiguration.customComposableTransitions.newComposableTransition.animateNewComposable(modifier, stateChange, fullWidth, fullHeight, animationProgress)
}
)
Layout(
content = {
if (isAnimating) {
key(topPreviousKey) {
topPreviousKey.RenderComposable(modifier)
}
}
},
measurePolicy = measurePolicy,
modifier = when {
!isAnimating -> modifier
else -> animationConfiguration.customComposableTransitions.previousComposableTransition.animatePreviousComposable(modifier, stateChange, fullWidth, fullHeight, animationProgress)
}
)
DisposableEffect(key1 = completionCallback, effect = {
scope.launch {
isAnimating = true
animationProgress = 0.0f
lerping.animateTo(1.0f, animationConfiguration.animationSpec) {
animationProgress = this.value
}
isAnimating = false
completionCallback.stateChangeComplete()
}
onDispose {
// do nothing
}
})
but for whatever reason it causes the "composable being animated in" to flicker in before it actually is rendered "elsewhere" by the graphicsLayer. what am I missing? π€zhuinden
03/03/2021, 2:48 PMZach Klippenstein (he/him) [MOD]
03/03/2021, 3:20 PMDisposableEffect
wonβt execute until after the composition is committed, which means the first frame will be composed without isAnimating == true
?Adam Powell
03/03/2021, 3:32 PMAdam Powell
03/03/2021, 3:33 PMAdam Powell
03/03/2021, 3:34 PMAdam Powell
03/03/2021, 3:35 PMzhuinden
03/03/2021, 3:47 PMisAnimating == true
it flickers π€ just checked now
@Composable
fun RenderScreen(modifier: Modifier = Modifier) {
val stateChange = stateChange ?: return
val callback = callback ?: return
var completionCallback by remember { mutableStateOf<StateChanger.Callback?>(null) }
var isAnimating by remember { mutableStateOf(true) } // true renders previous initially for fullWidth
if (completionCallback !== callback) {
isAnimating = true
completionCallback = callback
}
zhuinden
03/03/2021, 3:54 PMLaunchedEffect
instead of DisposableEffect
+ rememberCoroutineScope
zhuinden
03/03/2021, 3:55 PMLaunchedEffect(key1 = completionCallback, block = {
animationProgress = 0.0f
isAnimating = true
lerping.animateTo(1.0f, animationConfiguration.animationSpec) {
animationProgress = this.value
}
isAnimating = false
completionCallback.stateChangeComplete()
})
the flicker is still not gone π©zhuinden
03/03/2021, 3:57 PMEither way, an effect can't influence the composition by way of feedback in the same frame.i'm actually not sure what you mean here and i'm also not sure where the first render is coming from. I've been punching this thing for about 10 hours now lol
zhuinden
03/03/2021, 4:21 PMZach Klippenstein (he/him) [MOD]
03/03/2021, 4:56 PMisAnimating = true
animationProgress = 0.0f
I think these should be called directly from your composable function, before reading them in your layouts, so that the first frame that is composed reads the correct initial values.zhuinden
03/03/2021, 5:45 PMDoris Liu
03/03/2021, 6:30 PMAnimatable
(with a new value) if you don't remember it.Doris Liu
03/03/2021, 6:42 PManimationProgress
after
animationProgress = this.value
to inspect whether there's any suspicious jump in animationProgress
.zhuinden
03/03/2021, 8:01 PMzhuinden
03/03/2021, 8:06 PMzhuinden
03/03/2021, 8:19 PMzhuinden
03/03/2021, 8:20 PMDoris Liu
03/03/2021, 8:21 PMzhuinden
03/03/2021, 8:21 PMval stateChange = stateChange ?: return
val callback = callback ?: return
var completionCallback by remember { mutableStateOf<StateChanger.Callback?>(null) }
val topNewKey = stateChange.topNewKey<DefaultComposeKey>()
val topPreviousKey = stateChange.topPreviousKey<DefaultComposeKey>()
var isAnimating by remember { mutableStateOf(true) }
val lerping = remember { Animatable(0.0f, Float.VectorConverter, 1.0f) }
var animationProgress by remember { mutableStateOf(0.0f) }
if (completionCallback !== callback) {
completionCallback = callback
if (topPreviousKey != null) {
animationProgress = 0.0f
isAnimating = true
}
}
if (topPreviousKey == null) {
key(topNewKey) {
topNewKey.RenderComposable(modifier)
}
DisposableEffect(key1 = completionCallback, effect = {
completionCallback!!.stateChangeComplete()
onDispose {
// do nothing
}
})
return
}
var fullWidth by remember { mutableStateOf(0) }
var fullHeight by remember { mutableStateOf(0) }
val measurePolicy = MeasurePolicy { measurables, constraints ->
val placeables = measurables.fastMap { it.measure(constraints) }
val maxWidth = placeables.fastMaxBy { it.width }?.width ?: 0
val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
if (fullWidth == 0 && maxWidth != 0) {
fullWidth = maxWidth
}
if (fullHeight == 0 && maxHeight != 0) {
fullHeight = maxHeight
}
layout(maxWidth, maxHeight) {
placeables.fastForEach { placeable ->
placeable.place(0, 0)
}
}
}
Layout(
content = {
if (fullWidth > 0 && fullHeight > 0) {
key(topNewKey) {
topNewKey.RenderComposable(modifier)
)
}
},
measurePolicy = measurePolicy,
modifier = when {
!isAnimating -> modifier
else -> animationConfiguration.customComposableTransitions.newComposableTransition.animateNewComposable(modifier, stateChange, fullWidth, fullHeight, animationProgress)
}
)
Layout(
content = {
if (isAnimating) {
key(topPreviousKey) {
topPreviousKey.RenderComposable(modifier)
}
}
},
measurePolicy = measurePolicy,
modifier = when {
!isAnimating -> modifier
else -> animationConfiguration.customComposableTransitions.previousComposableTransition.animatePreviousComposable(modifier, stateChange, fullWidth, fullHeight, animationProgress)
}
)
LaunchedEffect(key1 = completionCallback, block = {
lerping.animateTo(1.0f, animationConfiguration.animationSpec) {
animationProgress = this.value
}
isAnimating = false
lerping.snapTo(0f)
completionCallback!!.stateChangeComplete()
})
}
zhuinden
03/03/2021, 8:22 PMDoris Liu
03/03/2021, 8:28 PMAnimatable
in the snippet above has a VisibilityThreshold
/ precision of 1.0f. That means it'll consider itself done when it's 1.0f away from the target. That's likely not what you want. I'd recommend using the factory method Animatable(0f)
, or setting the visibilityThreshold
to a much smaller number (0.01f
perhaps).zhuinden
03/03/2021, 8:29 PMzhuinden
03/03/2021, 8:32 PMval lerping = remember { Animatable(0.0f) }`
the flicker in the video is still there thoughzhuinden
03/03/2021, 8:32 PMzhuinden
03/03/2021, 8:36 PMzhuinden
03/03/2021, 8:39 PMzhuinden
03/03/2021, 8:43 PMzhuinden
03/03/2021, 8:43 PMkey()
lets me wrap that via structural equalityzhuinden
03/03/2021, 8:45 PMzhuinden
03/03/2021, 8:45 PMDoris Liu
03/03/2021, 8:51 PMDoris Liu
03/03/2021, 8:52 PMzhuinden
03/03/2021, 8:54 PMnewKey.RenderComposable()
to become previousKey.RenderComposable()
makes it become a "new composable"zhuinden
03/03/2021, 8:54 PMAdam Powell
03/03/2021, 9:10 PMAdam Powell
03/03/2021, 9:13 PMval myKey = when { ... }
myKey.RenderComposable() // same call identity, even when myKey changes
zhuinden
03/04/2021, 3:20 AMDoris Liu
03/04/2021, 3:24 AMCrossfade
in case it helps: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[β¦]ommonMain/kotlin/androidx/compose/animation/Crossfade.kt;l=85zhuinden
03/04/2021, 4:48 AMzhuinden
03/04/2021, 4:48 AMval stateChange = stateChange ?: return
val callback = callback ?: return
var completionCallback by remember { mutableStateOf<StateChanger.Callback?>(null) }
val topNewKey by rememberUpdatedState(newValue = stateChange.topNewKey<DefaultComposeKey>())
val topPreviousKey by rememberUpdatedState(newValue = stateChange.topPreviousKey<DefaultComposeKey>())
var isAnimating by remember { mutableStateOf(false) }
val lerping = remember { Animatable(0.0f) }
var animationProgress by remember { mutableStateOf(0.0f) }
var initialization by remember { mutableStateOf(true) }
if (completionCallback !== callback) {
completionCallback = callback
if (topPreviousKey != null) {
initialization = false
animationProgress = 0.0f
isAnimating = true
} else {
initialization = true
}
}
var fullWidth by remember { mutableStateOf(0) }
var fullHeight by remember { mutableStateOf(0) }
val measurePolicy = MeasurePolicy { measurables, constraints ->
val placeables = measurables.fastMap { it.measure(constraints) }
val maxWidth = placeables.fastMaxBy { it.width }?.width ?: 0
val maxHeight = placeables.fastMaxBy { it.height }?.height ?: 0
if (fullWidth == 0 && maxWidth != 0) {
fullWidth = maxWidth
}
if (fullHeight == 0 && maxHeight != 0) {
fullHeight = maxHeight
}
layout(maxWidth, maxHeight) {
placeables.fastForEach { placeable ->
placeable.place(0, 0)
}
}
}
val keySlot1 by rememberUpdatedState(when {
initialization -> topNewKey
else -> topPreviousKey
})
val keySlot2 by rememberUpdatedState(when {
initialization -> topPreviousKey
else -> topNewKey
})
Layout(
content = {
if (initialization || isAnimating) {
keySlot1?.RenderComposable(modifier)
}
},
measurePolicy = measurePolicy,
modifier = when {
!isAnimating || initialization -> modifier
else -> animationConfiguration.customComposableTransitions.previousComposableTransition.animateComposable(
modifier,
stateChange,
fullWidth,
fullHeight,
animationProgress,
)
}
)
Layout(
content = {
if (!initialization || isAnimating) {
keySlot2?.RenderComposable(modifier)
}
},
measurePolicy = measurePolicy,
modifier = when {
!isAnimating || initialization -> modifier
else -> animationConfiguration.customComposableTransitions.newComposableTransition.animateComposable(
modifier,
stateChange,
fullWidth,
fullHeight,
animationProgress,
)
}
)
LaunchedEffect(key1 = completionCallback, block = {
if (topPreviousKey != null) {
lerping.animateTo(1.0f, animationConfiguration.animationSpec) {
animationProgress = this.value
}
isAnimating = false
lerping.snapTo(0f)
}
completionCallback!!.stateChangeComplete()
})
zhuinden
03/04/2021, 4:56 AMkey {
to render them each with a modifier πzhuinden
03/04/2021, 4:57 AMzhuinden
03/04/2021, 4:57 AMzhuinden
03/04/2021, 4:58 AMDoris Liu
03/04/2021, 5:05 AMDoris Liu
03/04/2021, 5:10 AMzhuinden
03/04/2021, 6:30 AMzhuinden
03/04/2021, 6:32 AMzhuinden
03/04/2021, 6:32 AMzhuinden
03/04/2021, 6:33 AMzhuinden
03/04/2021, 7:04 AMzhuinden
03/04/2021, 7:04 AMzhuinden
03/04/2021, 8:09 AMzhuinden
03/04/2021, 8:32 AMzhuinden
03/04/2021, 10:03 AMzhuinden
03/04/2021, 10:21 AMShakil Karim
10/15/2021, 4:39 PMzhuinden
10/15/2021, 7:28 PMzhuinden
10/15/2021, 7:29 PM