Skip to content

Instantly share code, notes, and snippets.

@ahmedhosnypro
Created September 13, 2023 04:48
Show Gist options
  • Save ahmedhosnypro/873b27a11d3238463749108489ae8be8 to your computer and use it in GitHub Desktop.
Save ahmedhosnypro/873b27a11d3238463749108489ae8be8 to your computer and use it in GitHub Desktop.
Jetpack Compose: Reorderable Vertical Grid with Swipe to Delete
....
implementation("org.burnoutcrew.composereorderable:reorderable:0.9.6")
.....
//....
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import org.burnoutcrew.reorderable.ReorderableItem
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
import org.burnoutcrew.reorderable.reorderable
import kotlin.math.roundToInt
@OptIn(ExperimentalFoundationApi::class)
@Preview(
showBackground = true,
showSystemUi = true,
backgroundColor = 0x000000,
)
@Composable
fun ReorderedVerticalGridWithSwipeToDelete(
modifier: Modifier = Modifier,
) {
val items = remember { mutableStateOf(List(100) { "Item $it" }) }
val gridState = rememberReorderableLazyListState(onMove = { from, to ->
items.value = items.value.toMutableList().apply {
add(to.index, removeAt(from.index))
}
})
LazyColumn(
state = gridState.listState,
modifier = Modifier
.fillMaxSize()
.background(Color.Gray)
.reorderable(gridState)
.detectReorderAfterLongPress(gridState)
.padding(start = 8.dp, end = 8.dp),
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(items.value, { it }) { item ->
ReorderableItem(gridState, key = item) { isDragging ->
val elevation = animateDpAsState(if (isDragging) 16.dp else 0.dp, label = "")
// create de
val density = LocalDensity.current
val state = remember {
AnchoredDraggableState(
initialValue = DragAnchors.Start,
positionalThreshold = { distance: Float -> distance * .25f },
velocityThreshold = { with(density) { 100.dp.toPx() } },
animationSpec = tween(),
)
}
// Wrap the item in a BoxWithConstraints to get the maxWidth or maxHeight of the parent
BoxWithConstraints(modifier = Modifier.shadow(elevation.value)) {
// Get the maxWidth of the Grid
val parentWidth = this.maxWidth
// Convert the maxWidth to px to use it in the anchors as velocityThreshold
val contentSizePx = with(density) { parentWidth.toPx() }
// Create an interaction source to track the drag gesture
val interactionSource = remember { MutableInteractionSource() }
// Create a DragChangeDetector to detect when the drag ends
val dragChangeDetector = remember { DragChangeDetector() }
Card(
colors = CardDefaults.cardColors(
containerColor = Color.Black,
contentColor = Color(0xFFE0E0E0),
),
modifier = Modifier
.fillMaxWidth()
.onSizeChanged { layoutSize ->
val dragEndPoint = layoutSize.width + contentSizePx
state.updateAnchors(DraggableAnchors {
DragAnchors
.values()
.forEach { anchor ->
anchor at dragEndPoint * anchor.fraction
}
})
}
.offset {
IntOffset(
state
.requireOffset()
.roundToInt(), 0
)
}
.anchoredDraggable(
state,
Orientation.Horizontal,
interactionSource = interactionSource,
)
) {
Text(
text = item,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
// Check if the item is dragged and if the closest anchor is the end anchor
val isDragged = interactionSource.collectIsDraggedAsState().value
val closestAnchor = state.anchors.closestAnchor(state.offset)
if (dragChangeDetector.dragEnd(isDragged) &&
(closestAnchor == DragAnchors.End || closestAnchor == DragAnchors.LeftEnd)
) {
// remove item with id = item.id from items
items.value = items.value.toMutableList().apply {
// logic to remove the items from the list
removeIf { it === item }
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment