diff options
author | android-build-prod (mdb) <android-build-team-robot@google.com> | 2020-08-19 02:16:57 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-08-19 02:16:57 +0000 |
commit | dedac03a1dce2342f54c90ff72f4890303cc3570 (patch) | |
tree | 77506be8d0a0ee38a554d2d49cb923ea999ef46c | |
parent | afc0c630a4c9a68dae618b377396d0bdbdd3a9e7 (diff) | |
parent | ef96e0ebc91580d379fcc9577a2beb6901eb0b62 (diff) | |
download | support-dedac03a1dce2342f54c90ff72f4890303cc3570.tar.gz |
Merge "Handle termination of disallow correctly." into snap-temp-L23500000669881336
9 files changed, 275 insertions, 150 deletions
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java index 41aca917aa1..1d7c9116109 100644 --- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java +++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/EventRouterTest.java @@ -17,6 +17,8 @@ package androidx.recyclerview.selection; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.view.MotionEvent; @@ -64,38 +66,37 @@ public class EventRouterTest { public void testDropsEventAfterDisallowCalled() { mRouter.onRequestDisallowInterceptTouchEvent(true); - mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.DOWN); - mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.DOWN); + mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.MOVE); + mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.MOVE); mListener.assertOnInterceptTouchEventCalled(0); mListener.assertOnTouchEventCalled(0); } @Test - public void testResetsDisallowOnUplEvents() { + public void testResetsDisallowOnDownEvents() { mRouter.onRequestDisallowInterceptTouchEvent(true); - mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.UP); mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.DOWN); - mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.DOWN); + mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.UP); + mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.UP); - // The FINGER/UP event that resets "disallow" is also dispatched to touch-event + // The FINGER/DOWN event that resets "disallow" is also dispatched to touch-event // listeners. So expect 2 calls. mListener.assertOnInterceptTouchEventCalled(2); mListener.assertOnTouchEventCalled(1); } @Test - public void testResetsDisallowOnCancelEvents() { + public void testResettable() { + // Only resettable when disallowIntercept has been requested. mRouter.onRequestDisallowInterceptTouchEvent(true); - mRouter.onInterceptTouchEvent(mRecyclerView, - TestEvents.builder().action(MotionEvent.ACTION_CANCEL).build()); - mRouter.onInterceptTouchEvent(mRecyclerView, TestEvents.Touch.DOWN); - mRouter.onTouchEvent(mRecyclerView, TestEvents.Touch.DOWN); + assertTrue(mRouter.isResetRequired()); - // The UNKNOWN/CANCEL event that resets "disallow" is not dispatched to any listeners - // (because there are no listeners registered for "UNKNOWN" tooltype. So expect 1 call. - mListener.assertOnInterceptTouchEventCalled(1); - mListener.assertOnTouchEventCalled(1); + ResetManager<?> mgr = new ResetManager<>(); + mgr.addResetHandler(mRouter); + mgr.getSelectionObserver().onSelectionCleared(); // Results in reset. + + assertFalse(mRouter.isResetRequired()); } private static final class TestOnItemTouchListener implements OnItemTouchListener { diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapterTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapterTest.java deleted file mode 100644 index 7c26ec6fb02..00000000000 --- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapterTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.recyclerview.selection; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.os.Looper; -import android.view.GestureDetector; -import android.view.MotionEvent; - -import androidx.recyclerview.selection.testing.TestEvents; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - - -@RunWith(AndroidJUnit4.class) -@SmallTest -public final class GestureDetectorOnItemTouchListenerAdapterTest { - - private GestureDetectorOnItemTouchListenerAdapter mAdapter; - private TestGestureDetector mDetector; - - @Before - public void setUp() { - if (Looper.myLooper() == null) { - Looper.prepare(); - } - mDetector = new TestGestureDetector( - ApplicationProvider.getApplicationContext(), - new GestureDetector.SimpleOnGestureListener() // do nothing listener - ); - mAdapter = new GestureDetectorOnItemTouchListenerAdapter(mDetector); - } - - @Test - public void testReflectsGestureDetectorReturnValue() { - mDetector.mReturnValue = true; - assertTrue(mAdapter.onInterceptTouchEvent(null, TestEvents.Mouse.SECONDARY_CLICK)); - - mDetector.mReturnValue = false; - assertFalse(mAdapter.onInterceptTouchEvent(null, TestEvents.Mouse.SECONDARY_CLICK)); - } - - @Test - public void testAdapterRespectsDisallowIntercept() { - mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.DOWN); - mDetector.assertOnTouchCalled(1); - - // After disallow intercept, no more calls to onTouch should be forwarded. - mAdapter.onRequestDisallowInterceptTouchEvent(true); - mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.MOVE); - mDetector.assertOnTouchCalled(1); - - // UP event should reset event handling, allowing forwarding of events again. - // Num touch events forwarded to GestureDetector should increase by 1 (for the DOWN) - mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.UP); - mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.DOWN); - mDetector.assertOnTouchCalled(2); - } - - private static final class TestGestureDetector extends GestureDetector { - - private final List<MotionEvent> mDetectorEvents = new ArrayList<>(); - boolean mReturnValue; - - TestGestureDetector(Context context, SimpleOnGestureListener listener) { - super(context, listener); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - mDetectorEvents.add(ev); - return mReturnValue; - } - - void assertOnTouchCalled(int expectedTimes) { - assertEquals(expectedTimes, mDetectorEvents.size()); - } - } - - ; -} diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorWrapperTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorWrapperTest.java new file mode 100644 index 00000000000..584bacb26ba --- /dev/null +++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/GestureDetectorWrapperTest.java @@ -0,0 +1,176 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.recyclerview.selection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.os.Looper; +import android.view.GestureDetector; +import android.view.MotionEvent; + +import androidx.recyclerview.selection.testing.TestEvents; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class GestureDetectorWrapperTest { + + private GestureDetectorWrapper mAdapter; + private TestGestureDetector mDetector; + private TestListener mListener; + + @Before + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mListener = new TestListener(); + mDetector = new TestGestureDetector( + ApplicationProvider.getApplicationContext(), + mListener); + + mAdapter = new GestureDetectorWrapper(mDetector); + } + + @Test + public void testReflectsGestureDetectorReturnValue() { + mListener.mNextReturnVal = true; + assertTrue(mAdapter.onInterceptTouchEvent(null, TestEvents.Touch.DOWN)); + + mListener.mNextReturnVal = false; + assertFalse(mAdapter.onInterceptTouchEvent(null, TestEvents.Touch.DOWN)); + } + + @Test + public void testRelaysActions() { + mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.DOWN); + mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.MOVE); + mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.UP); + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_DOWN); + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_MOVE); + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_UP); + + mDetector.assertOnTouchCalled(3); + } + + @Test + public void testRequestDisallowSendsFakeCancelEvent() { + mAdapter.onRequestDisallowInterceptTouchEvent(true); + + // The adapter sends a synthetic ACTION_CANCEL event to GD when disallow is called. + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_CANCEL); + mDetector.assertOnTouchCalled(1); + } + + @Test + public void testResettable() { + ResetManager<?> mgr = new ResetManager<>(); + mgr.addResetHandler(mAdapter); + mgr.getSelectionObserver().onSelectionCleared(); // Results in reset. + + // The adapter sends a synthetic ACTION_CANCEL event to GD when disallow is called. + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_CANCEL); + mDetector.assertOnTouchCalled(1); + } + + @Test + public void testRespectsDisallowIntercept() { + mAdapter.onRequestDisallowInterceptTouchEvent(true); + + mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.MOVE); + mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.UP); + // Sending DOWN would reset state, so we only test with MOVE and UP. + + mDetector.assertOnTouchCalled(0, MotionEvent.ACTION_MOVE); + mDetector.assertOnTouchCalled(0, MotionEvent.ACTION_UP); + + // Only the initial CANCEL event sent. Everything else ignored. + mDetector.assertOnTouchCalled(1); + } + + @Test + public void testResetsDisallowInterceptOnDown() { + mAdapter.onRequestDisallowInterceptTouchEvent(true); + // Synthetic CANCEL event used to coerce GestureDetector into resetting internal state. + // Issued whenever disallowIntercept = true + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_CANCEL); + + // Should reset DOWN + mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.DOWN); + mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.MOVE); + mAdapter.onInterceptTouchEvent(/* RecyclerView */ null, TestEvents.Touch.UP); + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_DOWN); + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_MOVE); + mDetector.assertOnTouchCalled(1, MotionEvent.ACTION_UP); + + mDetector.assertOnTouchCalled(4); // CANCEL + DOWN,MOVE,UP + } + + private static final class TestGestureDetector extends GestureDetector { + + private final List<MotionEvent> mDetectorEvents = new ArrayList<>(); + + TestGestureDetector(Context context, SimpleOnGestureListener listener) { + super(context, listener); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + mDetectorEvents.add(ev); + return super.onTouchEvent(ev); + } + + void assertOnTouchCalled(int expectedTimes) { + assertEquals(expectedTimes, mDetectorEvents.size()); + } + + void assertOnTouchCalled(int expectedTimes, int action) { + int total = 0; + for (MotionEvent e : mDetectorEvents) { + if (e.getAction() == action) { + total++; + } + } + assertEquals(expectedTimes, total); + } + } + + private static class TestListener extends GestureDetector.SimpleOnGestureListener { + boolean mNextReturnVal = false; + + public boolean onSingleTapUp(MotionEvent e) { + return mNextReturnVal; + } + + public boolean onDown(MotionEvent e) { + return mNextReturnVal; + } + } +} diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java index 2080ea75ca2..abbc75e526d 100644 --- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java +++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/ResetMangerTest.java @@ -44,7 +44,7 @@ public final class ResetMangerTest { @Before public void setUp() { - mManager = new ResetManager(); + mManager = new ResetManager<>(); mResettable1 = new TestResettable(true); mResettable2 = new TestResettable(true); mManager.addResetHandler(mResettable1); diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java index 9cb8aa0beb0..c2a454cb907 100644 --- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java +++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventRouter.java @@ -33,7 +33,7 @@ import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; * {@link RecyclerView#addOnItemTouchListener(OnItemTouchListener)}. Despite "Touch" * being in the name, it receives MotionEvents for all types of tools. */ -final class EventRouter implements OnItemTouchListener { +final class EventRouter implements OnItemTouchListener, Resettable { private final ToolHandlerRegistry<OnItemTouchListener> mDelegates; private boolean mDisallowIntercept; @@ -55,10 +55,8 @@ final class EventRouter implements OnItemTouchListener { @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { - // Reset disallow intercept when the event is Up or Cancel as described - // in https://developer.android.com/reference/android/widget/HorizontalScrollView - // #requestDisallowInterceptTouchEvent(boolean) - if (MotionEvents.isActionUp(e) || MotionEvents.isActionCancel(e)) { + // Reset disallow when the event is down as advised in http://b/139141511#comment20. + if (mDisallowIntercept && MotionEvents.isActionDown(e)) { mDisallowIntercept = false; } return !mDisallowIntercept && mDelegates.get(e).onInterceptTouchEvent(rv, e); @@ -73,10 +71,25 @@ final class EventRouter implements OnItemTouchListener { @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (!disallowIntercept) { + return; // Ignore as advised in http://b/139141511#comment20 + } + // Some types of views, such as HorizontalScrollView, may want // to take over the input stream. In this case they'll call this method // with disallowIntercept=true. mDisallowIntercept is reset on UP or CANCEL // events in onInterceptTouchEvent. mDisallowIntercept = disallowIntercept; } + + + @Override + public boolean isResetRequired() { + return mDisallowIntercept; + } + + @Override + public void reset() { + mDisallowIntercept = false; + } } diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapter.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorWrapper.java index 94ffd03d19d..2a51ec9483e 100644 --- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorOnItemTouchListenerAdapter.java +++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/GestureDetectorWrapper.java @@ -25,14 +25,15 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; /** - * Class allowing GestureDetector to listen directly to RecyclerView touch events. + * A wrapper class for GestureDetector allowing it interact with SelectionTracker + * and its dependencies (like RecyclerView) on terms more amenable to SelectionTracker. */ -final class GestureDetectorOnItemTouchListenerAdapter implements RecyclerView.OnItemTouchListener { +final class GestureDetectorWrapper implements RecyclerView.OnItemTouchListener, Resettable { private final GestureDetector mDetector; private boolean mDisallowIntercept; - GestureDetectorOnItemTouchListenerAdapter(@NonNull GestureDetector detector) { + GestureDetectorWrapper(@NonNull GestureDetector detector) { checkArgument(detector != null); mDetector = detector; @@ -40,12 +41,9 @@ final class GestureDetectorOnItemTouchListenerAdapter implements RecyclerView.On @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { - // Reset disallow intercept when the event is Up or Cancel as described - // in https://developer.android.com/reference/android/widget/HorizontalScrollView - // #requestDisallowInterceptTouchEvent(boolean) - if (MotionEvents.isActionUp(e) || MotionEvents.isActionCancel(e)) { + // Reset disallow when the event is down as advised in http://b/139141511#comment20. + if (mDisallowIntercept && MotionEvents.isActionDown(e)) { mDisallowIntercept = false; - return false; } // While the idea of "intercepting" an event stream isn't consistent @@ -62,10 +60,39 @@ final class GestureDetectorOnItemTouchListenerAdapter implements RecyclerView.On @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + if (!disallowIntercept) { + return; // Ignore as advised in http://b/139141511#comment20 + } + // Some types of views, such as HorizontalScrollView, may want // to take over the input stream. In this case they'll call this method // with disallowIntercept=true. mDisallowIntercept is reset on UP or CANCEL // events in onInterceptTouchEvent. mDisallowIntercept = disallowIntercept; + + + // GestureDetector may have internal state (such as timers) that can + // result in subsequent event handlers being called, even after + // we receive a request to disallow intercept (e.g. LONG_PRESS). + // For that reason we proactively reset GestureDetector. + sendCancelEvent(); + } + + @Override + public boolean isResetRequired() { + // Always resettable as we don't know the specifics of GD's internal state. + return true; + } + + @Override + public void reset() { + mDisallowIntercept = false; + sendCancelEvent(); + } + + private void sendCancelEvent() { + // GestureDetector does not provide a public affordance for resetting + // it's internal state, so we send it a synthetic ACTION_CANCEL event. + mDetector.onTouchEvent(MotionEvents.createCancelEvent()); } } diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java index aac3c0674d5..d2278a39b1d 100644 --- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java +++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/MotionEvents.java @@ -27,7 +27,8 @@ import androidx.annotation.NonNull; */ final class MotionEvents { - private MotionEvents() {} + private MotionEvents() { + } static boolean isMouseEvent(@NonNull MotionEvent e) { return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE; @@ -107,7 +108,6 @@ final class MotionEvents { /** * Returns true if the event is a drag event (which is presumbaly, but not * explicitly required to be a mouse event). - * @param e */ static boolean isPointerDragEvent(MotionEvent e) { return isPrimaryMouseButtonPressed(e) @@ -117,4 +117,15 @@ final class MotionEvents { private static boolean hasBit(int metaState, int bit) { return (metaState & bit) != 0; } + + static MotionEvent createCancelEvent() { + return MotionEvent.obtain( + 0, // down time + 1, // event time + MotionEvent.ACTION_CANCEL, + 0, // x + 0, // y + 0 // metaState + ); + } } diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java index 53d4b82ed38..8d0ac571146 100644 --- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java +++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/OperationMonitor.java @@ -52,7 +52,7 @@ public final class OperationMonitor { // Ideally OperationMonitor would implement Resettable // directly, but Metalava couldn't understand that // `OperationMonitor` was public API while `Resettable` was - // not. This is our klunkuy workaround. + // not. This is our clever workaround :) private final Resettable mResettable = new Resettable() { @Override diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java index dd045c66838..b8dc24aa15a 100644 --- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java +++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java @@ -734,19 +734,20 @@ public abstract class SelectionTracker<K> { GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter); // GestureSelectionHelper provides logic that interprets a combination - // of motions and gestures in order to provide gesture driven selection support - // when used in conjunction with RecyclerView. - final GestureSelectionHelper gestureHelper = GestureSelectionHelper.create( + // of motions and gestures in order to provide fluid "long-press and drag" + // finger driven selection support. + final GestureSelectionHelper gestureSelectionHelper = GestureSelectionHelper.create( tracker, mSelectionPredicate, mRecyclerView, scroller, mMonitor); // EventRouter receives events for RecyclerView, dispatching to handlers // registered by tool-type. EventRouter eventRouter = new EventRouter(); + GestureDetectorWrapper gestureDetectorWrapper = + new GestureDetectorWrapper(gestureDetector); // Finally hook the framework up to listening to RecycleView events. mRecyclerView.addOnItemTouchListener(eventRouter); - mRecyclerView.addOnItemTouchListener( - new GestureDetectorOnItemTouchListenerAdapter(gestureDetector)); + mRecyclerView.addOnItemTouchListener(gestureDetectorWrapper); // Reset manager listens for cancel events from RecyclerView. In response to that it // advises other classes it is time to reset state. @@ -756,20 +757,23 @@ public abstract class SelectionTracker<K> { // // 1. Monitor selection reset which can be invoked by clients in response // to back key press and some application lifecycle events. - // - // 2. Monitor ACTION_CANCEL events (which arrive exclusively - // via TOOL_TYPE_UNKNOWN). tracker.addObserver(resetMgr.getSelectionObserver()); + // ...and 2. Monitor ACTION_CANCEL events (which arrive exclusively + // via TOOL_TYPE_UNKNOWN). + // // CAUTION! Registering resetMgr directly with RecyclerView#addOnItemTouchListener // will not work as expected. Once EventRouter returns true, RecyclerView will // no longer dispatch any events to other listeners for the duration of the // stream, not even ACTION_CANCEL events. eventRouter.set(MotionEvent.TOOL_TYPE_UNKNOWN, resetMgr.getInputListener()); + // Finally register all of the Resettables. resetMgr.addResetHandler(tracker); resetMgr.addResetHandler(mMonitor.asResettable()); - resetMgr.addResetHandler(gestureHelper); + resetMgr.addResetHandler(gestureSelectionHelper); + resetMgr.addResetHandler(gestureDetectorWrapper); + resetMgr.addResetHandler(eventRouter); // But before you move on, there's more work to do. Event plumbing has been // installed, but we haven't registered any of our helpers or callbacks. @@ -821,7 +825,7 @@ public abstract class SelectionTracker<K> { @Override public void run() { if (mSelectionPredicate.canSelectMultiple()) { - gestureHelper.start(); + gestureSelectionHelper.start(); } } }, @@ -837,7 +841,7 @@ public abstract class SelectionTracker<K> { for (int toolType : mGestureToolTypes) { gestureRouter.register(toolType, touchHandler); - eventRouter.set(toolType, gestureHelper); + eventRouter.set(toolType, gestureSelectionHelper); } // Provides high level glue for binding mouse events and gestures |