Improve reordering performance

This commit is contained in:
vfsfitvnm 2022-08-21 12:40:23 +02:00
parent 020386dadb
commit c40010397b
3 changed files with 108 additions and 32 deletions

View file

@ -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)
}
}
}

View file

@ -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)
}

View file

@ -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
}
}