Improve reordering performance
This commit is contained in:
@@ -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
|
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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user