Olivier Patry
04/25/2021, 4:10 PMupdateTransition
(doc) without a lot of success so far…Olivier Patry
04/25/2021, 4:12 PMval duration = 600
var size by remember { mutableStateOf(IntSize.Zero) }
val scale = remember(
tile.id,
tile.update
) { Animatable(if (animateUpdate && tile.update == TileUpdate.NEW) 0f else 1f) }
val angle = remember(tile.id, tile.update) { Animatable(0f) }
val offsetX = remember(tile.id, tile.update) {
Animatable(
when (tile.update) {
TileUpdate.MOVED_LEFT -> size.width.toFloat()
TileUpdate.MOVED_RIGHT -> -size.width.toFloat()
else -> 0f
}
)
}
val offsetY =
remember(tile.id, tile.update) {
Animatable(
when (tile.update) {
TileUpdate.MOVED_UP -> size.height.toFloat()
TileUpdate.MOVED_DOWN -> -size.height.toFloat()
else -> 0f
}
)
}
LaunchedEffect(tile) {
when (tile.update) {
TileUpdate.NONE -> {
scale.snapTo(1f)
angle.snapTo(0f)
offsetX.snapTo(size.width.toFloat())
offsetY.snapTo(size.height.toFloat())
}
TileUpdate.NEW -> {
scale.animateTo(
targetValue = 1f,
animationSpec = tween(duration)
)
angle.snapTo(0f)
offsetX.snapTo(0f)
offsetY.snapTo(0f)
}
TileUpdate.COMBINED -> {
scale.snapTo(1f)
angle.animateTo(
targetValue = 180f, // FIXME 180f mirrors the card, what we want is tile flip effect without mirroring
animationSpec = tween(duration)
)
offsetX.snapTo(0f)
offsetY.snapTo(0f)
}
TileUpdate.MOVED_UP,
TileUpdate.MOVED_DOWN -> {
scale.snapTo(1f)
angle.snapTo(0f)
offsetX.snapTo(0f)
offsetY.animateTo(
targetValue = 0f,
animationSpec = tween(duration)
)
}
TileUpdate.MOVED_LEFT,
TileUpdate.MOVED_RIGHT -> {
scale.snapTo(1f)
angle.snapTo(0f)
offsetX.animateTo(
targetValue = 0f,
animationSpec = tween(duration)
)
offsetY.snapTo(0f)
}
}
}
Box(
Modifier
.onSizeChanged { if (size == IntSize.Zero) size = it }
.scale(scale.value)
.graphicsLayer(rotationY = angle.value)
.offset(x = offsetX.value.dp, y = offsetY.value.dp)
)
Olivier Patry
04/25/2021, 4:17 PMOlivier Patry
04/25/2021, 4:19 PMAnimationSpec
might need customizationrobnik
04/25/2021, 4:20 PMOlivier Patry
04/25/2021, 4:50 PMOlivier Patry
04/25/2021, 4:51 PMmain
is tight to desktop impl, should be easy to move TilesApp
in an Android existing Compose Android project)Olivier Patry
04/25/2021, 6:30 PMvar size by remember { mutableStateOf(IntSize.Zero) }
val transitionData = updateTransitionData(tile.update, size)
Box(
modifier
.onSizeChanged { if (size == IntSize.Zero) size = it }
.scale(transitionData.scaleX, transitionData.scaleY)
.offset(transitionData.offsetX, transitionData.offsetY)
)
class TileTransitionData(
scaleX: State<Float>,
scaleY: State<Float>,
offsetX: State<Dp>,
offsetY: State<Dp>,
) {
val scaleX by scaleX
val scaleY by scaleY
val offsetX by offsetX
val offsetY by offsetY
}
@Composable
private fun updateTransitionData(updateState: TileUpdate, size: IntSize): TileTransitionData {
val transition = updateTransition(updateState)
val scaleX = transition.animateFloat { update ->
when (update) {
TileUpdate.NEW -> 0f // animate from 0 to 1f
TileUpdate.COMBINED -> 0f // animate from 1f to 0f to 1f
else -> 1f // no animation
}
}
val scaleY = transition.animateFloat { update ->
when (update) {
TileUpdate.NEW -> 0f // animate from 0 to 1f
else -> 1f // no animation
}
}
val offsetX = transition.animateDp { update ->
when (update) {
TileUpdate.MOVED_LEFT -> size.width.dp // animate from width to 0f
TileUpdate.MOVED_RIGHT -> -size.width.dp // animate from -width to 0f
else -> 0.dp // no animation
}
}
val offsetY = transition.animateDp { update ->
when (update) {
TileUpdate.MOVED_UP -> size.height.dp // animate from height to 0f
TileUpdate.MOVED_DOWN -> -size.height.dp // animate from -height to 0f
else -> 0.dp // no animation
}
}
return remember(transition) { TileTransitionData(scaleX, scaleY, offsetX, offsetY) }
}
But I can't make anything work because I don't know how to
1. define that I want an initial state and a target state
2. a trigger from initial to target stateOlivier Patry
04/25/2021, 6:33 PMMutableTransitionState
seems to be something close to what I'd like to use but I can't reach it from updateTransition
AFAIKOlivier Patry
04/25/2021, 6:47 PMTransition.kt
impl internally have the tools I need but I can't use them and the open APIs doesn't suit my needs…robnik
04/25/2021, 6:48 PMOlivier Patry
04/25/2021, 6:53 PMOlivier Patry
04/25/2021, 6:54 PMOlivier Patry
04/25/2021, 6:57 PMOlivier Patry
04/25/2021, 6:57 PMdewildte
04/25/2021, 9:15 PMupdateTransition()
takes a MutableTransitionState
as a parameter.dewildte
04/25/2021, 9:17 PMdewildte
04/25/2021, 9:18 PMDoris Liu
04/25/2021, 9:46 PMBut I can't make anything work because I don't know how to
define that I want an initial state and a target state
a trigger from initial to target state
MutableTransitionState
is likely what you need, as Eric mentioned. There's an updateTransition
API variant that takes a MutableTransitionState
instead of T
: https://developer.android.com/reference/kotlin/androidx/compose/animation/core/package-summary#updatetransition_1 It allows you to define an initial state and a target state for transition as you can see in the sample code. You could also change the target state of the transition by manipulating the MutableTransitionState
.Olivier Patry
04/25/2021, 9:47 PMOlivier Patry
04/25/2021, 9:48 PMOlivier Patry
04/25/2021, 9:48 PMOlivier Patry
04/25/2021, 9:49 PMtransition.animateXXX()
as needed?Doris Liu
04/25/2021, 9:51 PMMutableTransitionState<TileUpdate>
. I think in your case, the TileUpdate states that you defined would work better than a binary state.Olivier Patry
04/25/2021, 9:51 PMLaunchedEffect(tile.update)
? Which seems hackyOlivier Patry
04/25/2021, 9:52 PMDoris Liu
04/25/2021, 10:00 PMBut, this won't recompose when my tile updatesDo you update the
MutableTransitionState#targetState
when the tile updates?Olivier Patry
04/25/2021, 10:01 PMOlivier Patry
04/25/2021, 10:04 PMval tileState = remember { MutableTransitionState(tile.update) }
tileState.targetState = tile.update
val transition = updateTransition(tileState)
val scale by transition.animateFloat {
???
// here depending on tile.update (it) value, I'd like to animate from X to Y
}
Doris Liu
04/25/2021, 10:08 PMMutableTransitionState
is a way to hoist the states of the transition. It allows you to control the transition (both initial and target state) via MutableTransitionState#targetState
, and observe the state change (i.e. the purpose of hoisting).
The code above looks right. Transition.animateFloat
would be same as what you had before:
val scale by transition.animateFloat {
when (it) {
TileUpdate.NEW -> ...
TileUpdate.... -> ...
else -> ...
}
}
Olivier Patry
04/25/2021, 10:10 PMOlivier Patry
04/25/2021, 10:10 PMOlivier Patry
04/25/2021, 10:11 PMDoris Liu
04/25/2021, 10:12 PMTileUpdate.NEW
state?Olivier Patry
04/25/2021, 10:15 PMTileUpdate.NEW
, I'd like the tile UI to animate both scaleX and scaleY from 0 to 1
When in TileUpdate.MOVED_DOWN
, I'd like the tile UI to animate offsetY from -tileHeight to 0
When in TileUpdate.COMBINED
, I'd like the tile UI to animate scaleX (only) from 1 to 0 then back to 1 (using keyframes I guess)Olivier Patry
04/25/2021, 10:15 PMOlivier Patry
04/25/2021, 10:16 PMDoris Liu
04/25/2021, 10:19 PMTileUpdate.MOVE_DOWN
twice in a row, you'd want the UI to reflect two movements, rather than considering the 2nd request no-op.Olivier Patry
04/25/2021, 10:22 PMOlivier Patry
04/25/2021, 10:23 PMDoris Liu
04/25/2021, 10:27 PMAnimatable
, as they work better for imperative usages where you define how the UI should change.
For a more declarative way to define this, just for the sake of comparison:
class Tile {
var combined: Boolean
var x: Int
var y: Int
}
Doris Liu
04/25/2021, 10:31 PMOlivier Patry
04/25/2021, 10:31 PMDoris Liu
04/25/2021, 10:33 PMOlivier Patry
04/25/2021, 10:34 PMDoris Liu
04/25/2021, 10:35 PMclass Tile {
var combined: Boolean by mutableStateOf
var x: Int by mutableStateOf..
var y: Int by mutableStateOf..
@Composable
fun updatePosition(newX: Int, newY: Int)
}
Doris Liu
04/25/2021, 10:36 PMOlivier Patry
04/25/2021, 10:37 PMOlivier Patry
04/25/2021, 10:39 PMDoris Liu
04/25/2021, 10:39 PMOlivier Patry
04/25/2021, 10:41 PMTile
class is a UI class, right?
In my mind, Tile
is part of the business model and mutated by the business logic.
After each user interaction, an updated is notified to whoever is interested.
My composable being one of them.Doris Liu
04/25/2021, 10:42 PMTile
isn't a UI class, it's just data.Olivier Patry
04/25/2021, 10:43 PM@Composable updatePosition()
Doris Liu
04/25/2021, 10:43 PMTile
holds the data of its index on the board. UI interprets where to place it based on the index.Olivier Patry
04/25/2021, 10:44 PMOlivier Patry
04/25/2021, 10:44 PMOlivier Patry
04/25/2021, 10:45 PMDoris Liu
04/25/2021, 10:46 PMBut you suggest to addThat's a demonstration of how you can get a hold of two states simultaneously. You could instead do:@Composable updatePosition()
@Composable
fun updatePosition(tile: Tile, newX: Int, newY: Int)
in the UI code.Doris Liu
04/25/2021, 10:48 PMOlivier Patry
04/25/2021, 10:48 PMOlivier Patry
04/25/2021, 10:49 PMnewX
and newY
from UI side given that it's a business logic responsibility/knowledge?Doris Liu
04/25/2021, 10:49 PMOlivier Patry
04/25/2021, 11:01 PMOlivier Patry
04/25/2021, 11:01 PMDoris Liu
04/25/2021, 11:02 PMHow can I determineIf the same tile object or if it has some other identity, you can leverage composable function to do a bit of local caching for you.andnewX
from UI side given that it's a business logic responsibility/knowledge?newY
Doris Liu
04/25/2021, 11:13 PMTileView
composable function like you linked above, to determine the newX, newY, and oldX, and oldY, you could do something like:
@Composable
fun TileView(tile: Tile) {
var x by remember { mutableStateOf(tile.x) }
var y by remember { mutableStateOf(tile.y) }
// Here x, y contain the old x, y position, since they haven't been updated yet.
// Do something with the old value and new value as needed....
....
// update x, y as needed
x = tile.x
y = tile.y
}
Olivier Patry
04/25/2021, 11:16 PMdewildte
04/26/2021, 1:33 AMOlivier Patry
04/26/2021, 11:40 AMvar
, remember
, mutableStateOf
, deep copy data model without understanding anything anymore. It's time to have a break it seems 🙂
I thought I was understanding something to Compose but it seems I'm not after all 😅
I'll stick to built-in animateColorAsState
and static tile layout.
I can't determine were the issue(s) should be fixed.Olivier Patry
04/26/2021, 7:27 PMDoris Liu
04/26/2021, 8:29 PMTash
05/05/2021, 1:01 AMDoris Liu
05/05/2021, 1:05 AMTash
05/05/2021, 1:22 AMBox
• each Item
Composable in the Box
is draggable & has its own ItemDragState
to hold offset data, isDragging, etc.
• A composite ItemsBoxState
for the whole thing. Declares mutableStateListOf<Item>
to represent the items list
• Also contains mutableStateMapOf<Item, ItemDragState>
to be able to access the ItemDragState
for a given Item
in order to imperatively animate it based on the business logic decision
The main challenge right now is syncing the item change animation when the backing data mutableStateListOf<Item>
needs updating. Attempting to use the example from AnimatedVisiblilityLazyColumnDemo
+ DisposableEffect
to know when to sync removed/added items.Tash
05/05/2021, 1:25 AMAnimatedList
is possible in Compose 🤔Doris Liu
05/05/2021, 1:36 AMAnimatedVisibility
that will make this use case a lot easier. More specifically, it will support MutableTransitionState<Boolean>
as the visible
param. That will make the add/remove a lot easier. The MutableTransitionState
has two fields, targetState
and currentState
. The former can be mutated by users (of the API), the latter is only mutable by the animation but is observable. So then the flow becomes: 1. user requests to remove, 2. business logic checks... and 3. sets the targetState
as needed, AnimatedVisibility
responds to that change, and eventually update currentState
when it finishes animating outDoris Liu
05/05/2021, 1:39 AMcurrentState
and targetState
are the same. This will make it easier to sync the data between UI and business logic. 🙂Tash
05/05/2021, 2:58 AMvisible: MutableTransitionState<Boolean>
will indeed be perfect for this use case. Right now there's a lot of juggling of visible
& initiallyVisible
params of AnimatedVisibility(...)
to achieve the effect provided by a mutable targetState
. Thank you, Doris!
Is this change planned for any of the beta releases?Doris Liu
05/05/2021, 3:25 AM