Improve reordering performance
This commit is contained in:
parent
020386dadb
commit
c40010397b
3 changed files with 108 additions and 32 deletions
|
@ -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<T, V : AnimationVector>(
|
||||
private val size: Int,
|
||||
private val initialValue: T,
|
||||
typeConverter: TwoWayConverter<T, V>
|
||||
) {
|
||||
private val values = MutableList(size) {
|
||||
Animatable(initialValue = initialValue, typeConverter = typeConverter)
|
||||
}
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
init {
|
||||
require(size > 0)
|
||||
}
|
||||
|
||||
suspend fun acquire(): Animatable<T, V> {
|
||||
return mutex.withLock {
|
||||
require(values.isNotEmpty())
|
||||
values.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun release(animatable: Animatable<T, V>) {
|
||||
mutex.withLock {
|
||||
require(values.size < size)
|
||||
animatable.snapTo(initialValue)
|
||||
values.add(animatable)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<Int, AnimationVector1D> = 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<Int> {
|
||||
override val value = 0
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun translationFor(index: Int): State<Int> = 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<Int, Animatable<Int, AnimationVector1D>>()
|
||||
private var animatablesPool: AnimatablesPool<Int, AnimationVector1D>? = 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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue