Updated recyclerview-selection: to version 1.1.0-alpha05

pull/156/head
M66B 5 years ago
parent 9ef900cfc5
commit d7ac4f486b

@ -134,7 +134,7 @@ dependencies {
// https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview // https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview
// https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview-selection // https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview-selection
implementation "androidx.recyclerview:recyclerview:$recyclerview_version" implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
//implementation "androidx.recyclerview:recyclerview-selection:1.1.0-alpha01" //implementation "androidx.recyclerview:recyclerview-selection:1.1.0-alpha05"
// https://mvnrepository.com/artifact/androidx.coordinatorlayout/coordinatorlayout // https://mvnrepository.com/artifact/androidx.coordinatorlayout/coordinatorlayout
implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorlayout_version" implementation "androidx.coordinatorlayout:coordinatorlayout:$coordinatorlayout_version"

@ -348,7 +348,7 @@ public class DefaultSelectionTracker<K> extends SelectionTracker<K> {
} }
@Override @Override
AdapterDataObserver getAdapterDataObserver() { protected AdapterDataObserver getAdapterDataObserver() {
return mAdapterObserver; return mAdapterObserver;
} }

@ -19,7 +19,6 @@ package androidx.recyclerview.selection;
import static androidx.core.util.Preconditions.checkArgument; import static androidx.core.util.Preconditions.checkArgument;
import static androidx.core.util.Preconditions.checkState; import static androidx.core.util.Preconditions.checkState;
import android.graphics.Point;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@ -27,6 +26,7 @@ import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
@ -41,12 +41,11 @@ final class GestureSelectionHelper implements OnItemTouchListener {
private static final String TAG = "GestureSelectionHelper"; private static final String TAG = "GestureSelectionHelper";
private final SelectionTracker<?> mSelectionMgr; private final SelectionTracker<?> mSelectionMgr;
private final ItemDetailsLookup<?> mDetailsLookup; private final SelectionTracker.SelectionPredicate<?> mSelectionPredicate;
private final AutoScroller mScroller; private final AutoScroller mScroller;
private final ViewDelegate mView; private final ViewDelegate mView;
private final OperationMonitor mLock; private final OperationMonitor mLock;
private int mLastStartedItemPos = RecyclerView.NO_POSITION;
private boolean mStarted = false; private boolean mStarted = false;
/** /**
@ -55,19 +54,19 @@ final class GestureSelectionHelper implements OnItemTouchListener {
*/ */
GestureSelectionHelper( GestureSelectionHelper(
@NonNull SelectionTracker<?> selectionTracker, @NonNull SelectionTracker<?> selectionTracker,
@NonNull ItemDetailsLookup<?> detailsLookup, @NonNull SelectionPredicate<?> selectionPredicate,
@NonNull ViewDelegate view, @NonNull ViewDelegate view,
@NonNull AutoScroller scroller, @NonNull AutoScroller scroller,
@NonNull OperationMonitor lock) { @NonNull OperationMonitor lock) {
checkArgument(selectionTracker != null); checkArgument(selectionTracker != null);
checkArgument(detailsLookup != null); checkArgument(selectionPredicate != null);
checkArgument(view != null); checkArgument(view != null);
checkArgument(scroller != null); checkArgument(scroller != null);
checkArgument(lock != null); checkArgument(lock != null);
mSelectionMgr = selectionTracker; mSelectionMgr = selectionTracker;
mDetailsLookup = detailsLookup; mSelectionPredicate = selectionPredicate;
mView = view; mView = view;
mScroller = scroller; mScroller = scroller;
mLock = lock; mLock = lock;
@ -78,16 +77,9 @@ final class GestureSelectionHelper implements OnItemTouchListener {
*/ */
void start() { void start() {
checkState(!mStarted); checkState(!mStarted);
// See: b/70518185. It appears start() is being called via onLongPress
// even though we never received an intial handleInterceptedDownEvent
// where we would usually initialize mLastStartedItemPos.
if (mLastStartedItemPos == RecyclerView.NO_POSITION) {
Log.w(TAG, "Illegal state. Can't start without valid mLastStartedItemPos.");
return;
}
// Partner code in MotionInputHandler ensures items // Partner code in MotionInputHandler ensures items
// are selected and range established prior to // are selected and range anchor initialized prior to
// start being called. // start being called.
// Verify the truth of that statement here // Verify the truth of that statement here
// to make the implicit coupling less of a time bomb. // to make the implicit coupling less of a time bomb.
@ -102,20 +94,14 @@ final class GestureSelectionHelper implements OnItemTouchListener {
@Override @Override
/** @hide */ /** @hide */
public boolean onInterceptTouchEvent(@NonNull RecyclerView unused, @NonNull MotionEvent e) { public boolean onInterceptTouchEvent(@NonNull RecyclerView unused, @NonNull MotionEvent e) {
if (MotionEvents.isMouseEvent(e)) { switch (e.getActionMasked()) {
if (Shared.DEBUG) Log.w(TAG, "Unexpected Mouse event. Check configuration."); case MotionEvent.ACTION_MOVE:
} case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// TODO(b/109808552): It seems that mLastStartedItemPos should likely be set as a method return mStarted;
// parameter in start(). default:
if (e.getActionMasked() == MotionEvent.ACTION_DOWN) { return false;
if (mDetailsLookup.getItemDetails(e) != null) {
mLastStartedItemPos = mView.getItemUnder(e);
}
} }
// See handleTouch(MotionEvent) javadoc for explanation as to why this is correct.
return handleTouch(e);
} }
@Override @Override
@ -146,6 +132,14 @@ final class GestureSelectionHelper implements OnItemTouchListener {
return false; return false;
} }
if (!mSelectionMgr.isRangeActive()) {
Log.e(TAG,
"Internal state of GestureSelectionHelper out of sync w/ SelectionTracker "
+ "(isRangeActive is false). Ignoring event and resetting state.");
endSelection();
return false;
}
switch (e.getActionMasked()) { switch (e.getActionMasked()) {
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
handleMoveEvent(e); handleMoveEvent(e);
@ -172,9 +166,6 @@ final class GestureSelectionHelper implements OnItemTouchListener {
private void handleUpEvent() { private void handleUpEvent() {
mSelectionMgr.mergeProvisionalSelection(); mSelectionMgr.mergeProvisionalSelection();
endSelection(); endSelection();
if (mLastStartedItemPos != RecyclerView.NO_POSITION) {
mSelectionMgr.startRange(mLastStartedItemPos);
}
} }
// Called when ACTION_CANCEL event is to be handled. // Called when ACTION_CANCEL event is to be handled.
@ -188,7 +179,6 @@ final class GestureSelectionHelper implements OnItemTouchListener {
private void endSelection() { private void endSelection() {
checkState(mStarted); checkState(mStarted);
mLastStartedItemPos = RecyclerView.NO_POSITION;
mStarted = false; mStarted = false;
mScroller.reset(); mScroller.reset();
mLock.stop(); mLock.stop();
@ -197,14 +187,12 @@ final class GestureSelectionHelper implements OnItemTouchListener {
// Call when an intercepted ACTION_MOVE event is passed down. // Call when an intercepted ACTION_MOVE event is passed down.
// At this point, we are sure user wants to gesture multi-select. // At this point, we are sure user wants to gesture multi-select.
private void handleMoveEvent(@NonNull MotionEvent e) { private void handleMoveEvent(@NonNull MotionEvent e) {
Point lastInterceptedPoint = MotionEvents.getOrigin(e);
int lastGlidedItemPos = mView.getLastGlidedItemPosition(e); int lastGlidedItemPos = mView.getLastGlidedItemPosition(e);
if (lastGlidedItemPos != RecyclerView.NO_POSITION) { if (mSelectionPredicate.canSetStateAtPosition(lastGlidedItemPos, true)) {
extendSelection(lastGlidedItemPos); extendSelection(lastGlidedItemPos);
} }
mScroller.scroll(lastInterceptedPoint); mScroller.scroll(MotionEvents.getOrigin(e));
} }
// It's possible for events to go over the top/bottom of the RecyclerView. // It's possible for events to go over the top/bottom of the RecyclerView.
@ -232,14 +220,14 @@ final class GestureSelectionHelper implements OnItemTouchListener {
*/ */
static GestureSelectionHelper create( static GestureSelectionHelper create(
@NonNull SelectionTracker<?> selectionMgr, @NonNull SelectionTracker<?> selectionMgr,
@NonNull ItemDetailsLookup<?> detailsLookup, @NonNull SelectionPredicate<?> selectionPredicate,
@NonNull RecyclerView recyclerView, @NonNull RecyclerView recyclerView,
@NonNull AutoScroller scroller, @NonNull AutoScroller scroller,
@NonNull OperationMonitor lock) { @NonNull OperationMonitor lock) {
return new GestureSelectionHelper( return new GestureSelectionHelper(
selectionMgr, selectionMgr,
detailsLookup, selectionPredicate,
new RecyclerViewDelegate(recyclerView), new RecyclerViewDelegate(recyclerView),
scroller, scroller,
lock); lock);

@ -33,7 +33,7 @@ import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -84,7 +84,7 @@ final class GridModel<K> {
// Array passed to registered OnSelectionChangedListeners. One array is created and reused // Array passed to registered OnSelectionChangedListeners. One array is created and reused
// throughout the lifetime of the object. // throughout the lifetime of the object.
private final Set<K> mSelection = new HashSet<>(); private final Set<K> mSelection = new LinkedHashSet<>();
// The current pointer (in absolute positioning from the top of the view). // The current pointer (in absolute positioning from the top of the view).
private Point mPointer; private Point mPointer;
@ -259,7 +259,7 @@ final class GridModel<K> {
private void updateModel() { private void updateModel() {
RelativePoint old = mRelPointer; RelativePoint old = mRelPointer;
mRelPointer = createRelativePoint(mPointer); mRelPointer = createRelativePoint(mPointer);
if (old != null && mRelPointer.equals(old)) { if (mRelPointer.equals(old)) {
return; return;
} }

@ -16,10 +16,13 @@
package androidx.recyclerview.selection; package androidx.recyclerview.selection;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.view.MotionEvent; import android.view.MotionEvent;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
/** /**
@ -67,8 +70,10 @@ public abstract class ItemDetailsLookup<K> {
/** /**
* @return true if there is an item w/ a stable ID at the event coordinates. * @return true if there is an item w/ a stable ID at the event coordinates.
* @hide
*/ */
final boolean overItemWithSelectionKey(@NonNull MotionEvent e) { @RestrictTo(LIBRARY_GROUP)
protected boolean overItemWithSelectionKey(@NonNull MotionEvent e) {
return overItem(e) && hasSelectionKey(getItemDetails(e)); return overItem(e) && hasSelectionKey(getItemDetails(e));
} }

@ -33,18 +33,10 @@ final class MotionEvents {
return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE; return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
} }
static boolean isTouchEvent(@NonNull MotionEvent e) {
return e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER;
}
static boolean isActionMove(@NonNull MotionEvent e) { static boolean isActionMove(@NonNull MotionEvent e) {
return e.getActionMasked() == MotionEvent.ACTION_MOVE; return e.getActionMasked() == MotionEvent.ACTION_MOVE;
} }
static boolean isActionDown(@NonNull MotionEvent e) {
return e.getActionMasked() == MotionEvent.ACTION_DOWN;
}
static boolean isActionUp(@NonNull MotionEvent e) { static boolean isActionUp(@NonNull MotionEvent e) {
return e.getActionMasked() == MotionEvent.ACTION_UP; return e.getActionMasked() == MotionEvent.ACTION_UP;
} }

@ -88,7 +88,7 @@ abstract class MotionInputHandler<K> extends SimpleOnGestureListener {
mFocusDelegate.focusItem(details); mFocusDelegate.focusItem(details);
} }
final boolean isRangeExtension(@NonNull MotionEvent e) { final boolean shouldExtendRange(@NonNull MotionEvent e) {
return MotionEvents.isShiftKeyPressed(e) return MotionEvents.isShiftKeyPressed(e)
&& mSelectionTracker.isRangeActive() && mSelectionTracker.isRangeActive()
// Without full corpus access we can't reliably implement range // Without full corpus access we can't reliably implement range

@ -126,7 +126,7 @@ final class MouseInputHandler<K> extends MotionInputHandler<K> {
checkState(mSelectionTracker.hasSelection()); checkState(mSelectionTracker.hasSelection());
checkArgument(item != null); checkArgument(item != null);
if (isRangeExtension(e)) { if (shouldExtendRange(e)) {
extendSelectionRange(item); extendSelectionRange(item);
} else { } else {
if (shouldClearSelection(e, item)) { if (shouldClearSelection(e, item)) {

@ -19,9 +19,9 @@ package androidx.recyclerview.selection;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -65,8 +65,8 @@ public class Selection<K> implements Iterable<K> {
final Set<K> mProvisionalSelection; final Set<K> mProvisionalSelection;
Selection() { Selection() {
mSelection = new HashSet<>(); mSelection = new LinkedHashSet<>();
mProvisionalSelection = new HashSet<>(); mProvisionalSelection = new LinkedHashSet<>();
} }
/** /**
@ -74,7 +74,7 @@ public class Selection<K> implements Iterable<K> {
*/ */
Selection(@NonNull Set<K> selection) { Selection(@NonNull Set<K> selection) {
mSelection = selection; mSelection = selection;
mProvisionalSelection = new HashSet<>(); mProvisionalSelection = new LinkedHashSet<>();
} }
/** /**
@ -117,7 +117,7 @@ public class Selection<K> implements Iterable<K> {
* @return Map of ids added or removed. Added ids have a value of true, removed are false. * @return Map of ids added or removed. Added ids have a value of true, removed are false.
*/ */
Map<K, Boolean> setProvisionalSelection(@NonNull Set<K> newSelection) { Map<K, Boolean> setProvisionalSelection(@NonNull Set<K> newSelection) {
Map<K, Boolean> delta = new HashMap<>(); Map<K, Boolean> delta = new LinkedHashMap<>();
for (K key: mProvisionalSelection) { for (K key: mProvisionalSelection) {
// Mark each item that used to be in the provisional selection // Mark each item that used to be in the provisional selection
@ -128,7 +128,7 @@ public class Selection<K> implements Iterable<K> {
} }
for (K key: mSelection) { for (K key: mSelection) {
// Mark each item that used to be in the selection but is unsaved and not in the new // Mark each item that in the selection but is not in the new
// provisional selection. // provisional selection.
if (!newSelection.contains(key)) { if (!newSelection.contains(key)) {
delta.put(key, false); delta.put(key, false);

@ -16,6 +16,7 @@
package androidx.recyclerview.selection; package androidx.recyclerview.selection;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.core.util.Preconditions.checkArgument; import static androidx.core.util.Preconditions.checkArgument;
import android.content.Context; import android.content.Context;
@ -28,6 +29,7 @@ import android.view.MotionEvent;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
@ -177,7 +179,9 @@ public abstract class SelectionTracker<K> {
*/ */
public abstract boolean deselect(@NonNull K key); public abstract boolean deselect(@NonNull K key);
abstract AdapterDataObserver getAdapterDataObserver(); /** @hide */
@RestrictTo(LIBRARY_GROUP)
protected abstract AdapterDataObserver getAdapterDataObserver();
/** /**
* Attempts to establish a range selection at {@code position}, selecting the item * Attempts to establish a range selection at {@code position}, selecting the item
@ -186,8 +190,10 @@ public abstract class SelectionTracker<K> {
* @param position The "anchor" position for the range. Subsequent range operations * @param position The "anchor" position for the range. Subsequent range operations
* (primarily keyboard and mouse based operations like SHIFT + click) * (primarily keyboard and mouse based operations like SHIFT + click)
* work with the established anchor point to define selection ranges. * work with the established anchor point to define selection ranges.
* @hide
*/ */
abstract void startRange(int position); @RestrictTo(LIBRARY_GROUP)
public abstract void startRange(int position);
/** /**
* Sets the end point for the active range selection. * Sets the end point for the active range selection.
@ -200,20 +206,26 @@ public abstract class SelectionTracker<K> {
* @param position The new end position for the selection range. * @param position The new end position for the selection range.
* @throws IllegalStateException if a range selection is not active. Range selection * @throws IllegalStateException if a range selection is not active. Range selection
* must have been started by a call to {@link #startRange(int)}. * must have been started by a call to {@link #startRange(int)}.
* @hide
*/ */
abstract void extendRange(int position); @RestrictTo(LIBRARY_GROUP)
public abstract void extendRange(int position);
/** /**
* Clears an in-progress range selection. Provisional range selection established * Clears an in-progress range selection. Provisional range selection established
* using {@link #extendProvisionalRange(int)} will be cleared (unless * using {@link #extendProvisionalRange(int)} will be cleared (unless
* {@link #mergeProvisionalSelection()} is called first.) * {@link #mergeProvisionalSelection()} is called first.)
* @hide
*/ */
abstract void endRange(); @RestrictTo(LIBRARY_GROUP)
public abstract void endRange();
/** /**
* @return Whether or not there is a current range selection active. * @return Whether or not there is a current range selection active.
* @hide
*/ */
abstract boolean isRangeActive(); @RestrictTo(LIBRARY_GROUP)
public abstract boolean isRangeActive();
/** /**
* Establishes the "anchor" at which a selection range begins. This "anchor" is consulted * Establishes the "anchor" at which a selection range begins. This "anchor" is consulted
@ -223,32 +235,42 @@ public abstract class SelectionTracker<K> {
* TODO: Reconcile this with startRange. Maybe just docs need to be updated. * TODO: Reconcile this with startRange. Maybe just docs need to be updated.
* *
* @param position the anchor position. Must already be selected. * @param position the anchor position. Must already be selected.
* @hide
*/ */
abstract void anchorRange(int position); @RestrictTo(LIBRARY_GROUP)
public abstract void anchorRange(int position);
/** /**
* Creates a provisional selection from anchor to {@code position}. * Creates a provisional selection from anchor to {@code position}.
* *
* @param position the end point. * @param position the end point.
* @hide
*/ */
abstract void extendProvisionalRange(int position); @RestrictTo(LIBRARY_GROUP)
protected abstract void extendProvisionalRange(int position);
/** /**
* Sets the provisional selection, replacing any existing selection. * Sets the provisional selection, replacing any existing selection.
* @param newSelection * @param newSelection
* @hide
*/ */
abstract void setProvisionalSelection(@NonNull Set<K> newSelection); @RestrictTo(LIBRARY_GROUP)
protected abstract void setProvisionalSelection(@NonNull Set<K> newSelection);
/** /**
* Clears any existing provisional selection * Clears any existing provisional selection
* @hide
*/ */
abstract void clearProvisionalSelection(); @RestrictTo(LIBRARY_GROUP)
protected abstract void clearProvisionalSelection();
/** /**
* Converts the provisional selection into primary selection, then clears * Converts the provisional selection into primary selection, then clears
* provisional selection. * provisional selection.
* @hide
*/ */
abstract void mergeProvisionalSelection(); @RestrictTo(LIBRARY_GROUP)
protected abstract void mergeProvisionalSelection();
/** /**
* Preserves selection, if any. Call this method from Activity#onSaveInstanceState * Preserves selection, if any. Call this method from Activity#onSaveInstanceState
@ -689,7 +711,7 @@ public abstract class SelectionTracker<K> {
// of motions and gestures in order to provide gesture driven selection support // of motions and gestures in order to provide gesture driven selection support
// when used in conjunction with RecyclerView. // when used in conjunction with RecyclerView.
final GestureSelectionHelper gestureHelper = GestureSelectionHelper.create( final GestureSelectionHelper gestureHelper = GestureSelectionHelper.create(
tracker, mDetailsLookup, mRecyclerView, scroller, mMonitor); tracker, mSelectionPredicate, mRecyclerView, scroller, mMonitor);
// Finally hook the framework up to listening to recycle view events. // Finally hook the framework up to listening to recycle view events.
mRecyclerView.addOnItemTouchListener(eventRouter); mRecyclerView.addOnItemTouchListener(eventRouter);

@ -16,17 +16,18 @@
package androidx.recyclerview.selection; package androidx.recyclerview.selection;
import static androidx.recyclerview.selection.Shared.DEBUG;
import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener; import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
import java.util.HashMap;
import java.util.Map;
/** /**
* An {@link ItemKeyProvider} that provides stable ids by way of cached * An {@link ItemKeyProvider} that provides stable ids by way of cached
* {@link RecyclerView.Adapter} stable ids. Items enter the cache as they are laid out by * {@link RecyclerView.Adapter} stable ids. Items enter the cache as they are laid out by
@ -39,8 +40,10 @@ import java.util.Map;
*/ */
public final class StableIdKeyProvider extends ItemKeyProvider<Long> { public final class StableIdKeyProvider extends ItemKeyProvider<Long> {
private static final String TAG = "StableIdKeyProvider";
private final SparseArray<Long> mPositionToKey = new SparseArray<>(); private final SparseArray<Long> mPositionToKey = new SparseArray<>();
private final Map<Long, Integer> mKeyToPosition = new HashMap<Long, Integer>(); private final LongSparseArray<Integer> mKeyToPosition = new LongSparseArray<>();
private final RecyclerView mRecyclerView; private final RecyclerView mRecyclerView;
/** /**
@ -76,6 +79,12 @@ public final class StableIdKeyProvider extends ItemKeyProvider<Long> {
@SuppressWarnings("WeakerAccess") /* synthetic access */ @SuppressWarnings("WeakerAccess") /* synthetic access */
void onAttached(@NonNull View view) { void onAttached(@NonNull View view) {
RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view); RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view);
if (holder == null) {
if (DEBUG) {
Log.w(TAG, "Unable to find ViewHolder for View. Ignoring onAttached event.");
}
return;
}
int position = holder.getAdapterPosition(); int position = holder.getAdapterPosition();
long id = holder.getItemId(); long id = holder.getItemId();
if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) { if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
@ -87,6 +96,12 @@ public final class StableIdKeyProvider extends ItemKeyProvider<Long> {
@SuppressWarnings("WeakerAccess") /* synthetic access */ @SuppressWarnings("WeakerAccess") /* synthetic access */
void onDetached(@NonNull View view) { void onDetached(@NonNull View view) {
RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view); RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view);
if (holder == null) {
if (DEBUG) {
Log.w(TAG, "Unable to find ViewHolder for View. Ignoring onDetached event.");
}
return;
}
int position = holder.getAdapterPosition(); int position = holder.getAdapterPosition();
long id = holder.getItemId(); long id = holder.getItemId();
if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) { if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
@ -102,9 +117,6 @@ public final class StableIdKeyProvider extends ItemKeyProvider<Long> {
@Override @Override
public int getPosition(@NonNull Long key) { public int getPosition(@NonNull Long key) {
if (mKeyToPosition.containsKey(key)) { return mKeyToPosition.get(key, RecyclerView.NO_POSITION);
return mKeyToPosition.get(key);
}
return RecyclerView.NO_POSITION;
} }
} }

@ -88,7 +88,7 @@ final class TouchInputHandler<K> extends MotionInputHandler<K> {
} }
if (mSelectionTracker.hasSelection()) { if (mSelectionTracker.hasSelection()) {
if (isRangeExtension(e)) { if (shouldExtendRange(e)) {
extendSelectionRange(item); extendSelectionRange(item);
} else if (mSelectionTracker.isSelected(item.getSelectionKey())) { } else if (mSelectionTracker.isSelected(item.getSelectionKey())) {
mSelectionTracker.deselect(item.getSelectionKey()); mSelectionTracker.deselect(item.getSelectionKey());
@ -126,32 +126,26 @@ final class TouchInputHandler<K> extends MotionInputHandler<K> {
boolean handled = false; boolean handled = false;
if (isRangeExtension(e)) { if (shouldExtendRange(e)) {
extendSelectionRange(item); extendSelectionRange(item);
handled = true; mHapticPerformer.run();
} else { } else {
if (!mSelectionTracker.isSelected(item.getSelectionKey()) if (mSelectionTracker.isSelected(item.getSelectionKey())) {
&& mSelectionPredicate.canSetStateForKey(item.getSelectionKey(), true)) { // Long press on existing selected item initiates drag/drop.
// If we cannot select it, we didn't apply anchoring - therefore should not
// start gesture selection
if (selectItem(item)) {
// And finally if the item was selected && we can select multiple
// we kick off gesture selection.
if (mSelectionPredicate.canSelectMultiple()) {
mGestureStarter.run();
}
handled = true;
}
} else {
// We only initiate drag and drop on long press for touch to allow regular
// touch-based scrolling
mOnDragInitiatedListener.onDragInitiated(e); mOnDragInitiatedListener.onDragInitiated(e);
handled = true; mHapticPerformer.run();
} else if (mSelectionPredicate.canSetStateForKey(item.getSelectionKey(), true)
&& selectItem(item)) {
// And finally if the item was selected && we can select multiple
// we kick off gesture selection.
// NOTE: isRangeActive should ALWAYS be true at this point, but there have
// been reports indicating that assumption isn't correct. So we explicitly
// check isRangeActive.
if (mSelectionPredicate.canSelectMultiple() && mSelectionTracker.isRangeActive()) {
mGestureStarter.run();
}
mHapticPerformer.run();
} }
} }
if (handled) {
mHapticPerformer.run();
}
} }
} }

@ -116,9 +116,9 @@
* <b>Include Selection in Activity lifecycle events</b> * <b>Include Selection in Activity lifecycle events</b>
* *
* <p> * <p>
* In order to preserve state the author must the selection library in handling * In order to preserve state, the author must include the selection library in the handling
* of Activity lifecycle events. See SelectionTracker#onSaveInstanceState * of Activity lifecycle events. See SelectionTracker#onSaveInstanceState and
* and SelectionTracker#onRestoreInstanceState. * SelectionTracker#onRestoreInstanceState.
* *
* <p>A unique selection id must be supplied to * <p>A unique selection id must be supplied to
* {@link androidx.recyclerview.selection.SelectionTracker.Builder SelectionTracker.Builder} * {@link androidx.recyclerview.selection.SelectionTracker.Builder SelectionTracker.Builder}

Loading…
Cancel
Save