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 package it.vfsfitvnm.reordering
import android.annotation.SuppressLint
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
@SuppressLint("UnnecessaryComposedModifier")
fun Modifier.draggedItem( fun Modifier.draggedItem(
reorderingState: ReorderingState, reorderingState: ReorderingState,
index: Int index: Int
): Modifier = composed { ): Modifier = when (reorderingState.draggingIndex) {
val translation by reorderingState.translationFor(index) -1 -> this
index -> offset {
offset {
when (reorderingState.lazyListState.layoutInfo.orientation) { when (reorderingState.lazyListState.layoutInfo.orientation) {
Orientation.Vertical -> IntOffset(0, translation) Orientation.Vertical -> IntOffset(0, reorderingState.offset.value)
Orientation.Horizontal -> IntOffset(translation, 0) 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.Animatable
import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.lazy.LazyListBeyondBoundsInfo import androidx.compose.foundation.lazy.LazyListBeyondBoundsInfo
import androidx.compose.foundation.lazy.LazyListItemInfo import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -35,35 +34,21 @@ class ReorderingState(
) { ) {
private lateinit var lazyListBeyondBoundsInfoInterval: LazyListBeyondBoundsInfo.Interval private lateinit var lazyListBeyondBoundsInfoInterval: LazyListBeyondBoundsInfo.Interval
internal val lazyListBeyondBoundsInfo = LazyListBeyondBoundsInfo() 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) internal var draggingIndex by mutableStateOf(-1)
private var reachedIndex by mutableStateOf(-1) internal var reachedIndex by mutableStateOf(-1)
private var draggingItemSize by mutableStateOf(0) internal var draggingItemSize by mutableStateOf(0)
lateinit var itemInfo: LazyListItemInfo lateinit var itemInfo: LazyListItemInfo
var previousItemSize = 0 private var previousItemSize = 0
var nextItemSize = 0 private var nextItemSize = 0
private var overscrolled = 0 private var overscrolled = 0
private val noTranslation = object : State<Int> { internal var indexesToAnimate = mutableStateMapOf<Int, Animatable<Int, AnimationVector1D>>()
override val value = 0 private var animatablesPool: AnimatablesPool<Int, AnimationVector1D>? = null
}
@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
}
)
}
fun onDragStart(index: Int) { fun onDragStart(index: Int) {
overscrolled = 0 overscrolled = 0
@@ -85,6 +70,11 @@ class ReorderingState(
lazyListBeyondBoundsInfoInterval = lazyListBeyondBoundsInfoInterval =
lazyListBeyondBoundsInfo.addInterval(index + extraItemCount, index + extraItemCount) 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) { fun onDrag(change: PointerInputChange, dragAmount: Offset) {
@@ -106,12 +96,49 @@ class ReorderingState(
reachedIndex += 1 reachedIndex += 1
nextItemSize += draggingItemSize nextItemSize += draggingItemSize
previousItemSize += 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) { } else if (targetOffset < previousItemSize) {
if (reachedIndex > 0) { if (reachedIndex > 0) {
reachedIndex -= 1 reachedIndex -= 1
previousItemSize -= draggingItemSize previousItemSize -= draggingItemSize
nextItemSize -= 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 { } else {
val offsetInViewPort = targetOffset + itemInfo.offset - overscrolled val offsetInViewPort = targetOffset + itemInfo.offset - overscrolled
@@ -146,6 +173,7 @@ class ReorderingState(
} }
lazyListBeyondBoundsInfo.removeInterval(lazyListBeyondBoundsInfoInterval) lazyListBeyondBoundsInfo.removeInterval(lazyListBeyondBoundsInfoInterval)
animatablesPool = null
} }
} }