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