ComposeReorderable Documentation

repository·main·Indexed 21 days ago

https://github.com/aclassen/composereorderable

A Jetpack Compose modifier library for Android and Desktop that enables drag-and-drop reordering within LazyList and LazyGrid components. It provides state management via rememberReorderableLazyListState and rememberReorderableLazyGridState, along with the ReorderableItem wrapper and modifiers like .reorderable(), .detectReorder(), and .detectReorderAfterLongPress() to implement custom reordering logic and drag handles.

Tokens
3.2K
Snippets
6
Records
9
Agent score
75%

What's inside ComposeReorderable

  1. How to implement reordering in LazyList or LazyGrid

    main

    To enable drag-and-drop reordering, follow these steps:

    1. Initialize State: Create a reorderable state using rememberReorderableLazyListState for LazyColumn/LazyRow or rememberReorderableLazyGridState for LazyVerticalGrid/LazyHorizontalGrid.
    2. Attach Modifier: Add the .reorderable(state) modifier to your list or grid.
    3. Wrap Items: Inside your items block, wrap each item's content with ReorderableItem.
      • For keyed lists (recommended): Use ReorderableItem(state, key = { ... }). This enables item animations.
      • For indexed lists: Use ReorderableItem(state, index = { ... }).
    4. Enable Detection: Apply either .detectReorderAfterLongPress(state) or .detectReorder(state) to the list modifier.
      • Drag Handle Pattern: If you want reordering to be triggered only by a specific part of the item (a drag handle), apply the detection modifier to that specific child composable instead of the list itself.

    ReorderableItem provides an isDragging boolean in its lambda, which you can use to apply visual feedback like elevation or scale during a drag.

    @Composable
    fun VerticalReorderList() {
        val data = remember { mutableStateOf(List(100) { "Item $it" }) }
        val state = rememberReorderableLazyListState(onMove = { from, to ->
            data.value = data.value.toMutableList().apply {
                add(to.index, removeAt(from.index))
            }
        })
        LazyColumn(
            state = state.listState,
            modifier = Modifier
                .reorderable(state)
                .detectReorderAfterLongPress(state)
        ) {
            items(data.value, { it }) { item ->
                ReorderableItem(state, key = item) { isDragging ->
                    val elevation = animateDpAsState(if (isDragging) 16.dp else 0.dp)
                    Column(
                        modifier = Modifier
                            .shadow(elevation.value)
                            .background(MaterialTheme.colors.surface)
                    ) {
                        Text(item)
                    }
                }
            }
        }
    }
  2. Install ComposeReorderable via Gradle

    main

    Add the following dependency to your dependencies block in your build.gradle file to use ComposeReorderable in your Android or Desktop project. Replace <latest_version> with the most recent version available.

    dependencies {
        implementation("org.burnoutcrew.composereorderable:reorderable:<latest_version>")
    }
  3. Initialize reorderable state with rememberReorderableLazyListState

    main

    To enable reordering in a LazyList, use the rememberReorderableLazyListState composable function. This function creates and remembers a ReorderableLazyListState object, which manages the drag-and-drop logic and scroll synchronization.

    Parameters

    • onMove: A callback triggered when an item is moved. It receives two ItemPosition objects representing the source and destination. It returns a Unit (typically used to trigger a state update in your list).
    • listState: The existing LazyListState used by your LazyColumn or LazyRow. Defaults to rememberLazyListState().
    • canDragOver: (Optional) A predicate to determine if a dragged item is allowed to hover over a specific target item. Receives draggedOver and dragging positions.
    • onDragEnd: (Optional) A callback triggered when the drag gesture finishes. It provides the startIndex and endIndex of the reordered items.
    • maxScrollPerFrame: The maximum amount of pixels the list can scroll per frame during a drag. Defaults to 20.dp.
    • dragCancelledAnimation: The animation used if a drag is cancelled. Defaults to SpringDragCancelledAnimation().
    val listState = rememberLazyListState()
    val reorderableState = rememberReorderableLazyListState(
        onMove = { from, to -> 
            // Handle the move in your data source
        },
        listState = listState,
        onDragEnd = { start, end ->
            // Handle the final result
        }
    )
  4. Customize drag animations and drag handles

    main

    You can customize the reordering behavior using the following parameters:

    • dragCancelledAnimation: Pass a custom animation implementation to rememberReorderableLazyGridState (or List state) to change how items behave when a drag is cancelled.
    • defaultDraggingModifier: Pass a custom Modifier to ReorderableItem to change the default dragging behavior for that item.
    • Drag Handles: To implement a specific drag handle, apply .detectReorderAfterLongPress(state) or .detectReorder(state) to a specific child composable inside the ReorderableItem instead of the parent list.

    Example of a Grid with a custom drag handle and disabled drag-cancelled animation:

    @Composable
    fun VerticalReorderGrid() {
        val data = remember { mutableStateOf(List(100) { "Item $it" }) }
        val state = rememberReorderableLazyGridState(
            dragCancelledAnimation = NoDragCancelledAnimation(),
            onMove = { from, to ->
                data.value = data.value.toMutableList().apply {
                    add(to.index, removeAt(from.index))
                }
            }
        )
        LazyVerticalGrid(
            columns = GridCells.Fixed(4),
            state = state.gridState,
            modifier = Modifier.reorderable(state)
        ) {
            items(data.value, { it }) { item ->
                ReorderableItem(state, key = item, defaultDraggingModifier = Modifier) { isDragging ->
                    Box(
                        modifier = Modifier
                            .aspectRatio(1f)
                            .background(MaterialTheme.colors.surface)
                    ) {
                        Text(
                            text = item,
                            modifier = Modifier.detectReorderAfterLongPress(state)
                        )
                    }
                }
            }
        }
    }
  5. Access LazyGridItemInfo properties via ReorderableLazyGridState

    main

    The ReorderableLazyGridState extends LazyGridItemInfo with helper properties to simplify coordinate and index lookups during reordering. These properties are available on the items within the grid's visible info.

    Available Properties on LazyGridItemInfo

    • left: The x-offset of the item.
    • right: The right edge of the item (offset.x + size.width).
    • top: The y-offset of the item.
    • bottom: The bottom edge of the item (offset.y + size.height).
    • width: The width of the item.
    • height: The height of the item.
    • itemIndex: The index of the item.
    • itemKey: The unique key of the item.

    Grid State Properties

    • visibleItemsInfo: Returns the list of currently visible LazyGridItemInfo objects.
    • viewportStartOffset: The start offset of the viewport.
    • viewportEndOffset: The end offset of the viewport.
    • firstVisibleItemIndex: The index of the first visible item.
    • firstVisibleItemScrollOffset: The scroll offset of the first visible item.
  6. Use ReorderableLazyListState to manage LazyList reordering

    main

    The ReorderableLazyListState class is the core engine for reordering. It extends ReorderableState<LazyListItemInfo> and provides specialized extensions for LazyListItemInfo to simplify coordinate calculations within the list viewport.

    Key Properties

    • listState: The underlying LazyListState.
    • visibleItemsInfo: Returns the list of currently visible LazyListItemInfo items.
    • isVerticalScroll: Boolean indicating if the list is oriented vertically.

    LazyListItemInfo Extensions

    When using this state, you can access the following properties directly on LazyListItemInfo objects to get their position relative to the viewport:

    • left, top, right, bottom: The bounding box coordinates.
    • width, height: The dimensions of the item.
    • itemIndex: The index of the item in the list.
    • itemKey: The unique key of the item.

    Methods

    • scrollToItem(index: Int, offset: Int): Suspends until the list is scrolled to the specified item and offset.
    • onDragStart(offsetX: Int, offsetY: Int): Initiates the drag logic. It automatically adjusts coordinates based on whether the list is vertical or horizontal.
    • findTargets(x: Int, y: Int, selected: LazyListItemInfo): Finds potential drop targets at the given coordinates.
    • chooseDropItem(draggedItemInfo: LazyListItemInfo?, items: List<LazyListItemInfo>, curX: Int, curY: Int): Determines which item should be the drop target based on the current drag position.
  7. Initialize reorderable state for LazyGrid with rememberReorderableLazyGridState

    main

    Use rememberReorderableLazyGridState to create a state object that manages reordering logic for a LazyGrid. This function handles the internal coroutine orchestration required for scrolling during drags and item movement.

    Parameters

    • onMove: A callback invoked when an item is moved from one position to another. It receives two ItemPosition objects: the source and the destination.
    • gridState: The underlying LazyGridState (defaults to a new rememberLazyGridState()).
    • canDragOver: An optional predicate to determine if a dragging item is allowed to move over a specific target item. It receives (draggedOver: ItemPosition, dragging: ItemPosition).
    • onDragEnd: An optional callback invoked when the drag gesture completes. It receives (startIndex: Int, endIndex: Int).
    • maxScrollPerFrame: The maximum amount of pixels the grid can scroll per frame during a drag (defaults to 20.dp).
    • dragCancelledAnimation: The animation used when a drag is cancelled (defaults to SpringDragCancelledAnimation()).
    val gridState = rememberLazyGridState()
    val reorderableState = rememberReorderableLazyGridState(
        onMove = { from, to ->
            // Handle the move logic in your data source
        },
        gridState = gridState,
        canDragOver = { draggedOver, dragging ->
            // Logic to allow/disallow dragging over certain items
            true
        },
        onDragEnd = { start, end ->
            // Logic when drag finishes
        }
    )