diff --git a/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/AnimatablesPool.kt b/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/AnimatablesPool.kt new file mode 100644 index 0000000..db16388 --- /dev/null +++ b/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/AnimatablesPool.kt @@ -0,0 +1,38 @@ +package it.vfsfitvnm.reordering + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector +import androidx.compose.animation.core.TwoWayConverter +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +class AnimatablesPool( + private val size: Int, + private val initialValue: T, + typeConverter: TwoWayConverter +) { + private val values = MutableList(size) { + Animatable(initialValue = initialValue, typeConverter = typeConverter) + } + + private val mutex = Mutex() + + init { + require(size > 0) + } + + suspend fun acquire(): Animatable { + return mutex.withLock { + require(values.isNotEmpty()) + values.removeFirst() + } + } + + suspend fun release(animatable: Animatable) { + mutex.withLock { + require(values.size < size) + animatable.snapTo(initialValue) + values.add(animatable) + } + } +} diff --git a/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/DraggedItem.kt b/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/DraggedItem.kt index 249d6b4..d59b8bb 100644 --- a/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/DraggedItem.kt +++ b/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/DraggedItem.kt @@ -1,24 +1,34 @@ package it.vfsfitvnm.reordering +import android.annotation.SuppressLint import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.offset -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.zIndex +@SuppressLint("UnnecessaryComposedModifier") fun Modifier.draggedItem( reorderingState: ReorderingState, index: Int -): Modifier = composed { - val translation by reorderingState.translationFor(index) - - offset { +): Modifier = when (reorderingState.draggingIndex) { + -1 -> this + index -> offset { when (reorderingState.lazyListState.layoutInfo.orientation) { - Orientation.Vertical -> IntOffset(0, translation) - Orientation.Horizontal -> IntOffset(translation, 0) + Orientation.Vertical -> IntOffset(0, reorderingState.offset.value) + Orientation.Horizontal -> IntOffset(reorderingState.offset.value, 0) + } + }.zIndex(1f) + else -> offset { + val offset = when (index) { + in reorderingState.indexesToAnimate -> reorderingState.indexesToAnimate.getValue(index).value + in (reorderingState.draggingIndex + 1)..reorderingState.reachedIndex -> -reorderingState.draggingItemSize + in reorderingState.reachedIndex until reorderingState.draggingIndex -> reorderingState.draggingItemSize + else -> 0 + } + when (reorderingState.lazyListState.layoutInfo.orientation) { + Orientation.Vertical -> IntOffset(0, offset) + Orientation.Horizontal -> IntOffset(offset, 0) } } - .zIndex(if (reorderingState.draggingIndex == index) 1f else 0f) } diff --git a/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/ReorderingState.kt b/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/ReorderingState.kt index 7897664..38ff272 100644 --- a/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/ReorderingState.kt +++ b/compose-reordering/src/main/kotlin/it/vfsfitvnm/reordering/ReorderingState.kt @@ -5,14 +5,13 @@ package it.vfsfitvnm.reordering import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.VectorConverter -import androidx.compose.animation.core.animateIntAsState import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.lazy.LazyListBeyondBoundsInfo import androidx.compose.foundation.lazy.LazyListItemInfo import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable -import androidx.compose.runtime.State import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -35,35 +34,21 @@ class ReorderingState( ) { private lateinit var lazyListBeyondBoundsInfoInterval: LazyListBeyondBoundsInfo.Interval internal val lazyListBeyondBoundsInfo = LazyListBeyondBoundsInfo() - internal val offset: Animatable = Animatable(0, Int.VectorConverter) + internal val offset = Animatable(0, Int.VectorConverter) internal var draggingIndex by mutableStateOf(-1) - private var reachedIndex by mutableStateOf(-1) - private var draggingItemSize by mutableStateOf(0) + internal var reachedIndex by mutableStateOf(-1) + internal var draggingItemSize by mutableStateOf(0) lateinit var itemInfo: LazyListItemInfo - var previousItemSize = 0 - var nextItemSize = 0 + private var previousItemSize = 0 + private var nextItemSize = 0 private var overscrolled = 0 - private val noTranslation = object : State { - override val value = 0 - } - - @Composable - internal fun translationFor(index: Int): State = when (draggingIndex) { - -1 -> noTranslation - index -> offset.asState() - else -> animateIntAsState( - when (index) { - in (draggingIndex + 1)..reachedIndex -> -draggingItemSize - in reachedIndex until draggingIndex -> draggingItemSize - else -> 0 - } - ) - } + internal var indexesToAnimate = mutableStateMapOf>() + private var animatablesPool: AnimatablesPool? = null fun onDragStart(index: Int) { overscrolled = 0 @@ -85,6 +70,11 @@ class ReorderingState( lazyListBeyondBoundsInfoInterval = lazyListBeyondBoundsInfo.addInterval(index + extraItemCount, index + extraItemCount) + + val size = + lazyListState.layoutInfo.viewportEndOffset - lazyListState.layoutInfo.viewportStartOffset + + animatablesPool = AnimatablesPool(size / draggingItemSize + 2, 0, Int.VectorConverter) } fun onDrag(change: PointerInputChange, dragAmount: Offset) { @@ -106,12 +96,49 @@ class ReorderingState( reachedIndex += 1 nextItemSize += draggingItemSize previousItemSize += draggingItemSize + + val indexToAnimate = reachedIndex - if (draggingIndex < reachedIndex) 0 else 1 + + coroutineScope.launch { + val animatable = indexesToAnimate.getOrPut(indexToAnimate) { + animatablesPool?.acquire() ?: return@launch + } + + if (draggingIndex < reachedIndex) { + animatable.snapTo(0) + animatable.animateTo(-draggingItemSize) + } else { + animatable.snapTo(draggingItemSize) + animatable.animateTo(0) + } + + indexesToAnimate.remove(indexToAnimate) + animatablesPool?.release(animatable) + } } } else if (targetOffset < previousItemSize) { if (reachedIndex > 0) { reachedIndex -= 1 previousItemSize -= draggingItemSize nextItemSize -= draggingItemSize + + val indexToAnimate = reachedIndex + if (draggingIndex > reachedIndex) 0 else 1 + + coroutineScope.launch { + val animatable = indexesToAnimate.getOrPut(indexToAnimate) { + animatablesPool?.acquire() ?: return@launch + } + + if (draggingIndex > reachedIndex) { + animatable.snapTo(0) + animatable.animateTo(draggingItemSize) + } else { + animatable.snapTo(-draggingItemSize) + animatable.animateTo(0) + } + indexesToAnimate.remove(indexToAnimate) + animatablesPool?.release(animatable) + } } } else { val offsetInViewPort = targetOffset + itemInfo.offset - overscrolled @@ -146,6 +173,7 @@ class ReorderingState( } lazyListBeyondBoundsInfo.removeInterval(lazyListBeyondBoundsInfoInterval) + animatablesPool = null } }