Skip to content

Instantly share code, notes, and snippets.

@SmartToolFactory
Last active August 13, 2022 06:30
Show Gist options
  • Save SmartToolFactory/01df080eb6c44c9e1df2f4b675121dac to your computer and use it in GitHub Desktop.
Save SmartToolFactory/01df080eb6c44c9e1df2f4b675121dac to your computer and use it in GitHub Desktop.

Revisions

  1. SmartToolFactory revised this gist Aug 13, 2022. 1 changed file with 51 additions and 22 deletions.
    73 changes: 51 additions & 22 deletions AnimatedSwappableList.kt
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,37 @@
    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() {
    @@ -116,7 +150,20 @@ private fun AnimatedList() {
    .fillMaxWidth(),
    onClick = {
    animate = true
    // alternativeAnimate(fromString, toString, coroutineScope, animatable, items)

    // 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")
    @@ -125,45 +172,29 @@ private fun AnimatedList() {
    }

    private fun alternativeAnimate(
    fromString: String,
    toString: String,
    from: Int,
    to: Int,
    coroutineScope: CoroutineScope,
    animatable: Animatable<Int, AnimationVector1D>,
    items: SnapshotStateList<MyData>
    ) {
    val from = try {
    Integer.parseInt(fromString)
    } catch (e: Exception) {
    0
    }

    val to = try {
    Integer.parseInt(toString)
    } catch (e: Exception) {
    0
    }

    val difference = from - to
    val increasing = difference < 0

    var currentValue: Int = from

    coroutineScope.launch {
    animatable.snapTo(from)

    animatable.animateTo(to,
    tween(350 * abs(difference)),
    tween(350 * abs(difference), easing = LinearEasing),
    block = {
    val nextValue = this.value

    if (abs(currentValue - nextValue) == 1) {
    println("🔥difference: $difference, from: $from, to: $to, Animating current: $currentValue, nextValue: $nextValue")
    swap(items, currentValue, nextValue)
    currentValue = nextValue
    }
    }
    )

    }
    }

    @@ -198,8 +229,6 @@ private fun animatedSwap(
    // lazyListState.animateScrollToItem(0)
    // }
    delay(350)


    }
    onFinish()
    }
  2. SmartToolFactory created this gist Aug 11, 2022.
    221 changes: 221 additions & 0 deletions AnimatedSwappableList.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,221 @@
    @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
    // alternativeAnimate(fromString, toString, coroutineScope, animatable, items)
    }
    ) {
    Text("Swap")
    }
    }
    }

    private fun alternativeAnimate(
    fromString: String,
    toString: String,
    coroutineScope: CoroutineScope,
    animatable: Animatable<Int, AnimationVector1D>,
    items: SnapshotStateList<MyData>
    ) {
    val from = try {
    Integer.parseInt(fromString)
    } catch (e: Exception) {
    0
    }

    val to = try {
    Integer.parseInt(toString)
    } catch (e: Exception) {
    0
    }

    val difference = from - to
    val increasing = difference < 0

    var currentValue: Int = from

    coroutineScope.launch {
    animatable.snapTo(from)

    animatable.animateTo(to,
    tween(350 * abs(difference)),
    block = {
    val nextValue = this.value

    if (abs(currentValue - nextValue) == 1) {
    println("🔥difference: $difference, from: $from, to: $to, Animating current: $currentValue, nextValue: $nextValue")
    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() })