Last active
August 13, 2022 06:30
-
-
Save SmartToolFactory/01df080eb6c44c9e1df2f4b675121dac to your computer and use it in GitHub Desktop.
LazyColumn that swaps items one by one with animation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.smarttoolfactory.composeimage | |
import androidx.compose.animation.core.* | |
import androidx.compose.foundation.ExperimentalFoundationApi | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.layout.* | |
import androidx.compose.foundation.lazy.LazyColumn | |
import androidx.compose.foundation.lazy.LazyListState | |
import androidx.compose.foundation.lazy.items | |
import androidx.compose.foundation.lazy.rememberLazyListState | |
import androidx.compose.foundation.shape.RoundedCornerShape | |
import androidx.compose.foundation.text.KeyboardOptions | |
import androidx.compose.material.Button | |
import androidx.compose.material.Text | |
import androidx.compose.material.TextField | |
import androidx.compose.runtime.* | |
import androidx.compose.runtime.snapshots.SnapshotStateList | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.clip | |
import androidx.compose.ui.draw.shadow | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.layout.ContentScale | |
import androidx.compose.ui.res.painterResource | |
import androidx.compose.ui.text.input.KeyboardType | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | |
import kotlinx.coroutines.CoroutineScope | |
import kotlinx.coroutines.delay | |
import kotlinx.coroutines.launch | |
import java.util.* | |
import kotlin.math.abs | |
@OptIn(ExperimentalFoundationApi::class) | |
@Composable | |
private fun AnimatedList() { | |
Column(modifier = Modifier.fillMaxSize()) { | |
val items: SnapshotStateList<MyData> = remember { | |
mutableStateListOf<MyData>().apply { | |
repeat(20) { | |
add(MyData(uuid = UUID.randomUUID().toString(), "Row $it")) | |
} | |
} | |
} | |
val lazyListState = rememberLazyListState() | |
LazyColumn( | |
modifier = Modifier | |
.fillMaxWidth() | |
.weight(1f), | |
state = lazyListState, | |
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp), | |
verticalArrangement = Arrangement.spacedBy(4.dp) | |
) { | |
items( | |
items = items, | |
key = { | |
it.uuid | |
} | |
) { | |
Row( | |
modifier = Modifier | |
.animateItemPlacement( | |
tween(durationMillis = 200) | |
) | |
.shadow(1.dp, RoundedCornerShape(8.dp)) | |
.background(Color.White) | |
.fillMaxWidth() | |
.padding(8.dp), | |
verticalAlignment = Alignment.CenterVertically | |
) { | |
Image( | |
modifier = Modifier | |
.clip(RoundedCornerShape(10.dp)) | |
.size(50.dp), | |
painter = painterResource(id = R.drawable.landscape1), | |
contentScale = ContentScale.FillBounds, | |
contentDescription = null | |
) | |
Spacer(modifier = Modifier.width(10.dp)) | |
Text(it.value, fontSize = 18.sp) | |
} | |
} | |
} | |
var fromString by remember { | |
mutableStateOf("7") | |
} | |
var toString by remember { | |
mutableStateOf("3") | |
} | |
var animate by remember { mutableStateOf(false) } | |
val coroutineScope = rememberCoroutineScope() | |
val animatable = remember { Animatable(0, IntToVector) } | |
if (animate) { | |
val from = try { | |
Integer.parseInt(fromString) | |
} catch (e: Exception) { | |
0 | |
} | |
val to = try { | |
Integer.parseInt(toString) | |
} catch (e: Exception) { | |
0 | |
} | |
animatedSwap( | |
lazyListState = lazyListState, | |
items = items, | |
from = from, | |
to = to | |
) { | |
animate = false | |
} | |
} | |
Row(modifier = Modifier.fillMaxWidth()) { | |
TextField( | |
value = fromString, | |
onValueChange = { | |
fromString = it | |
}, | |
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) | |
) | |
TextField( | |
value = toString, | |
onValueChange = { | |
toString = it | |
}, | |
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) | |
) | |
} | |
Button( | |
modifier = Modifier | |
.padding(8.dp) | |
.fillMaxWidth(), | |
onClick = { | |
animate = true | |
// ALTERNATIVE with Animatable | |
// val from = try { | |
// Integer.parseInt(fromString) | |
// } catch (e: Exception) { | |
// 0 | |
// } | |
// | |
// val to = try { | |
// Integer.parseInt(toString) | |
// } catch (e: Exception) { | |
// 0 | |
// } | |
// alternativeAnimate(from, to, coroutineScope, animatable, items) | |
} | |
) { | |
Text("Swap") | |
} | |
} | |
} | |
private fun alternativeAnimate( | |
from: Int, | |
to: Int, | |
coroutineScope: CoroutineScope, | |
animatable: Animatable<Int, AnimationVector1D>, | |
items: SnapshotStateList<MyData> | |
) { | |
val difference = from - to | |
var currentValue: Int = from | |
coroutineScope.launch { | |
animatable.snapTo(from) | |
animatable.animateTo(to, | |
tween(350 * abs(difference), easing = LinearEasing), | |
block = { | |
val nextValue = this.value | |
if (abs(currentValue - nextValue) == 1) { | |
swap(items, currentValue, nextValue) | |
currentValue = nextValue | |
} | |
} | |
) | |
} | |
} | |
@Composable | |
private fun animatedSwap( | |
lazyListState: LazyListState, | |
items: SnapshotStateList<MyData>, | |
from: Int, | |
to: Int, | |
onFinish: () -> Unit | |
) { | |
LaunchedEffect(key1 = Unit) { | |
val difference = from - to | |
val increasing = difference < 0 | |
var currentValue: Int = from | |
repeat(abs(difference)) { | |
val temp = currentValue | |
if (increasing) { | |
currentValue++ | |
} else { | |
currentValue-- | |
} | |
swap(items, temp, currentValue) | |
// if (!increasing && currentValue == 0) { | |
// lazyListState.animateScrollToItem(0) | |
// } | |
delay(350) | |
} | |
onFinish() | |
} | |
} | |
private fun swap(list: SnapshotStateList<MyData>, from: Int, to: Int) { | |
val size = list.size | |
if (from in 0 until size && to in 0 until size) { | |
val temp = list[from] | |
list[from] = list[to] | |
list[to] = temp | |
} | |
} | |
class MyData(val uuid: String, val value: String) | |
val IntToVector: TwoWayConverter<Int, AnimationVector1D> = | |
TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() }) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment