Skip to content

Keep the range-select anchor across taps in selection mode#366

Open
MiMoHo wants to merge 1 commit into
FossifyOrg:mainfrom
MiMoHo:fix/range-select-anchor
Open

Keep the range-select anchor across taps in selection mode#366
MiMoHo wants to merge 1 commit into
FossifyOrg:mainfrom
MiMoHo:fix/range-select-anchor

Conversation

@MiMoHo

@MiMoHo MiMoHo commented Jul 17, 2026

Copy link
Copy Markdown

Problem

Range selection via long-press only works across two consecutive long-presses. As soon as the user taps a row in between (to select or skip it), the range anchor is lost and the next long-press selects only its own single row.

Concretely, MyRecyclerViewAdapter.viewClicked() always reset lastLongPressedItem = -1 at the end — including when the tap happened inside selection mode. So the common gesture:

  1. tap a row to select it,
  2. long-press a row several positions later expecting the rows in between to be selected,

does not fill the range, because the tap in step 1 already cleared the anchor. The same applies to “select one, skip one, select the next, then long-press further”.

Fix

While in selection mode, remember the tapped row as the range anchor instead of clearing it. A following long-press then fills in every row between the last touched row and the long-pressed row (which is exactly what itemLongClicked() already does when an anchor exists). Outside selection mode the anchor is cleared as before, so normal single-tap navigation is unchanged.

fun viewClicked(any: Any) {
    if (actModeCallback.isSelectable) {
        val currentPosition = adapterPosition - positionOffset
        val isSelected = selectedKeys.contains(getItemSelectionKey(currentPosition))
        toggleItemSelection(!isSelected, currentPosition, true)
        lastLongPressedItem = currentPosition   // keep the tap as the range anchor
    } else {
        itemClick.invoke(any)
        lastLongPressedItem = -1
    }
}

Behaviour / edge cases

  • Not in selection mode: unchanged — itemClick fires and the anchor is cleared.
  • Non-selectable positions (section headers etc.): still skipped, because toggleItemSelection() returns early for !getIsItemSelectable(pos), so the range fill only affects selectable rows.
  • Drag-select (finger drag): unaffected — selectItemRange() resets lastLongPressedItem itself after a drag.
  • Direction: itemLongClicked() uses min/max, so ranges fill both upward and downward.
  • Deselect via tap: the anchor also moves to a row that was just deselected, so a following long-press ranges from there — which reads as “range from where I last touched”.

Testing

Verified by tracing every gesture path in viewClicked / viewLongClicked / itemLongClicked / toggleItemSelection / the drag path. A single-adapter behavioural change with no effect on non-selection navigation or drag-select.

Range selection via long-press only worked across two consecutive
long-presses: MyRecyclerViewAdapter.viewClicked always reset
lastLongPressedItem to -1, so tapping a row in between (to select or
skip it) cleared the anchor and the next long-press selected only its
own row.

Track the tapped row as the anchor while in selection mode, so
"tap a row, then long-press a later row" fills in every row between
them (headers/non-selectable positions are still skipped by
toggleItemSelection). Outside selection mode the anchor is cleared as
before.
@MiMoHo
MiMoHo requested a review from naveensingh as a code owner July 17, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant