1/* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.recyclerview.selection; 18 19import static androidx.core.util.Preconditions.checkArgument; 20 21import android.content.Context; 22import android.os.Bundle; 23import android.os.Parcelable; 24import android.view.GestureDetector; 25import android.view.HapticFeedbackConstants; 26import android.view.MotionEvent; 27 28import androidx.annotation.DrawableRes; 29import androidx.annotation.NonNull; 30import androidx.annotation.Nullable; 31import androidx.recyclerview.widget.RecyclerView; 32import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; 33import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; 34 35import java.util.Set; 36 37/** 38 * SelectionTracker provides support for managing a selection of items in a RecyclerView instance. 39 * 40 * <p> 41 * This class provides support for managing a "primary" set of selected items, 42 * in addition to a "provisional" set of selected items using conventional 43 * {@link java.util.Collections}-like methods. 44 * 45 * <p> 46 * Create an instance of SelectionTracker using {@link Builder SelectionTracker.Builder}. 47 * 48 * <p> 49 * <b>Inspecting the current selection</b> 50 * 51 * <p> 52 * The underlying selection is described by the {@link Selection} class. 53 * 54 * <p> 55 * A live view of the current selection can be obtained using {@link #getSelection}. Changes made 56 * to the selection using SelectionTracker will be immediately reflected in this instance. 57 * 58 * <p> 59 * To obtain a stable snapshot of the selection use {@link #copySelection(MutableSelection)}. 60 * 61 * <p> 62 * Selection state for an individual item can be obtained using {@link #isSelected(Object)}. 63 * 64 * <p> 65 * <b>Provisional Selection</b> 66 * 67 * <p> 68 * Provisional selection exists to address issues where a transitory selection might 69 * momentarily intersect with a previously established selection resulting in a some 70 * or all of the established selection being erased. Such situations may arise 71 * when band selection is being performed in "additive" mode (e.g. SHIFT or CTRL is pressed 72 * on the keyboard prior to mouse down), or when there's an active gesture selection 73 * (which can be initiated by long pressing an unselected item while there is an 74 * existing selection). 75 * 76 * <p> 77 * A provisional selection can be abandoned, or merged into the primary selection. 78 * 79 * <p> 80 * <b>Enforcing selection policies</b> 81 * 82 * <p> 83 * Which items can be selected by the user is a matter of policy in an Application. 84 * Developers supply these policies by way of {@link SelectionPredicate}. 85 * 86 * @param <K> Selection key type. @see {@link StorageStrategy} for supported types. 87 */ 88public abstract class SelectionTracker<K> { 89 90 /** 91 * This value is included in the payload when SelectionTracker notifies RecyclerView 92 * of changes to selection. Look for this value in the {@code payload} 93 * Object argument supplied to 94 * {@link RecyclerView.Adapter#onBindViewHolder 95 * Adapter#onBindViewHolder}. 96 * If present the call is occurring in response to a selection state change. 97 * This would be a good opportunity to animate changes between unselected and selected state. 98 * When state is being restored, this argument will not be present. 99 */ 100 public static final String SELECTION_CHANGED_MARKER = "Selection-Changed"; 101 102 /** 103 * Adds {@code observer} to be notified when changes to selection occur. 104 * 105 * <p> 106 * Use an observer to track attributes about the selection and 107 * update the UI to reflect the state of the selection. For example, an author 108 * may use an observer to control the enabled status of menu items, 109 * or to initiate {@link android.view.ActionMode}. 110 */ 111 public abstract void addObserver(SelectionObserver observer); 112 113 /** @return true if has a selection */ 114 public abstract boolean hasSelection(); 115 116 /** 117 * Returns a Selection object that provides a live view on the current selection. 118 * 119 * @return The current selection. 120 * @see #copySelection(MutableSelection) on how to get a snapshot 121 * of the selection that will not reflect future changes 122 * to selection. 123 */ 124 public abstract Selection<K> getSelection(); 125 126 /** 127 * Updates {@code dest} to reflect the current selection. 128 */ 129 public abstract void copySelection(@NonNull MutableSelection<K> dest); 130 131 /** 132 * @return true if the item specified by its id is selected. Shorthand for 133 * {@code getSelection().contains(K)}. 134 */ 135 public abstract boolean isSelected(@Nullable K key); 136 137 /** 138 * Restores the selected state of specified items. Used in cases such as restore the selection 139 * after rotation etc. Provisional selection is not restored. 140 * 141 * <p> 142 * This affords clients the ability to restore selection from selection saved 143 * in Activity state. 144 * 145 * @see StorageStrategy details on selection state support. 146 * 147 * @param selection selection being restored. 148 */ 149 protected abstract void restoreSelection(@NonNull Selection<K> selection); 150 151 /** 152 * Clears both primary and provisional selections. 153 * 154 * @return true if primary selection changed. 155 */ 156 public abstract boolean clearSelection(); 157 158 /** 159 * Sets the selected state of the specified items if permitted after consulting 160 * SelectionPredicate. 161 */ 162 public abstract boolean setItemsSelected(@NonNull Iterable<K> keys, boolean selected); 163 164 /** 165 * Attempts to select an item. 166 * 167 * @return true if the item was selected. False if the item could not be selected, or was 168 * was already selected. 169 */ 170 public abstract boolean select(@NonNull K key); 171 172 /** 173 * Attempts to deselect an item. 174 * 175 * @return true if the item was deselected. False if the item could not be deselected, or was 176 * was already un-selected. 177 */ 178 public abstract boolean deselect(@NonNull K key); 179 180 abstract AdapterDataObserver getAdapterDataObserver(); 181 182 /** 183 * Attempts to establish a range selection at {@code position}, selecting the item 184 * at {@code position} if needed. 185 * 186 * @param position The "anchor" position for the range. Subsequent range operations 187 * (primarily keyboard and mouse based operations like SHIFT + click) 188 * work with the established anchor point to define selection ranges. 189 */ 190 abstract void startRange(int position); 191 192 /** 193 * Sets the end point for the active range selection. 194 * 195 * <p> 196 * This function should only be called when a range selection is active 197 * (see {@link #isRangeActive()}. Items in the range [anchor, end] will be 198 * selected after consulting SelectionPredicate. 199 * 200 * @param position The new end position for the selection range. 201 * @throws IllegalStateException if a range selection is not active. Range selection 202 * must have been started by a call to {@link #startRange(int)}. 203 */ 204 abstract void extendRange(int position); 205 206 /** 207 * Clears an in-progress range selection. Provisional range selection established 208 * using {@link #extendProvisionalRange(int)} will be cleared (unless 209 * {@link #mergeProvisionalSelection()} is called first.) 210 */ 211 abstract void endRange(); 212 213 /** 214 * @return Whether or not there is a current range selection active. 215 */ 216 abstract boolean isRangeActive(); 217 218 /** 219 * Establishes the "anchor" at which a selection range begins. This "anchor" is consulted 220 * when determining how to extend, and modify selection ranges. Calling this when a 221 * range selection is active will reset the range selection. 222 * 223 * TODO: Reconcile this with startRange. Maybe just docs need to be updated. 224 * 225 * @param position the anchor position. Must already be selected. 226 */ 227 abstract void anchorRange(int position); 228 229 /** 230 * Creates a provisional selection from anchor to {@code position}. 231 * 232 * @param position the end point. 233 */ 234 abstract void extendProvisionalRange(int position); 235 236 /** 237 * Sets the provisional selection, replacing any existing selection. 238 * @param newSelection 239 */ 240 abstract void setProvisionalSelection(@NonNull Set<K> newSelection); 241 242 /** 243 * Clears any existing provisional selection 244 */ 245 abstract void clearProvisionalSelection(); 246 247 /** 248 * Converts the provisional selection into primary selection, then clears 249 * provisional selection. 250 */ 251 abstract void mergeProvisionalSelection(); 252 253 /** 254 * Preserves selection, if any. Call this method from Activity#onSaveInstanceState 255 * 256 * @param state Bundle instance supplied to onSaveInstanceState. 257 */ 258 public abstract void onSaveInstanceState(@NonNull Bundle state); 259 260 /** 261 * Restores selection from previously saved state. Call this method from 262 * Activity#onCreate. 263 * 264 * @param state Bundle instance supplied to onCreate. 265 */ 266 public abstract void onRestoreInstanceState(@Nullable Bundle state); 267 268 /** 269 * Observer class providing access to information about Selection state changes. 270 * 271 * @param <K> Selection key type. @see {@link StorageStrategy} for supported types. 272 */ 273 public abstract static class SelectionObserver<K> { 274 275 /** 276 * Called when the state of an item has been changed. 277 */ 278 public void onItemStateChanged(@NonNull K key, boolean selected) { 279 } 280 281 /** 282 * Called when the underlying data set has changed. After this method is called 283 * SelectionTracker will traverse the existing selection, 284 * calling {@link #onItemStateChanged(K, boolean)} for each selected item, 285 * and deselecting any items that cannot be selected given the updated data-set 286 * (and after consulting SelectionPredicate). 287 */ 288 public void onSelectionRefresh() { 289 } 290 291 /** 292 * Called immediately after completion of any set of changes, excluding 293 * those resulting in calls to {@link #onSelectionRefresh()} and 294 * {@link #onSelectionRestored()}. 295 */ 296 public void onSelectionChanged() { 297 } 298 299 /** 300 * Called immediately after selection is restored. 301 * {@link #onItemStateChanged(K, boolean)} will *not* be called 302 * for individual items in the selection. 303 */ 304 public void onSelectionRestored() { 305 } 306 } 307 308 /** 309 * Implement SelectionPredicate to control when items can be selected or unselected. 310 * See {@link Builder#withSelectionPredicate(SelectionPredicate)}. 311 * 312 * @param <K> Selection key type. @see {@link StorageStrategy} for supported types. 313 */ 314 public abstract static class SelectionPredicate<K> { 315 316 /** 317 * Validates a change to selection for a specific key. 318 * 319 * @param key the item key 320 * @param nextState the next potential selected/unselected state 321 * @return true if the item at {@code id} can be set to {@code nextState}. 322 */ 323 public abstract boolean canSetStateForKey(@NonNull K key, boolean nextState); 324 325 /** 326 * Validates a change to selection for a specific position. If necessary 327 * use {@link ItemKeyProvider} to identy associated key. 328 * 329 * @param position the item position 330 * @param nextState the next potential selected/unselected state 331 * @return true if the item at {@code id} can be set to {@code nextState}. 332 */ 333 public abstract boolean canSetStateAtPosition(int position, boolean nextState); 334 335 /** 336 * Permits restriction to single selection mode. Single selection mode has 337 * unique behaviors in that it'll deselect an item already selected 338 * in order to select the new item. 339 * 340 * <p> 341 * In order to limit the number of items that can be selected, 342 * use {@link #canSetStateForKey(Object, boolean)} and 343 * {@link #canSetStateAtPosition(int, boolean)}. 344 * 345 * @return true if more than a single item can be selected. 346 */ 347 public abstract boolean canSelectMultiple(); 348 } 349 350 /** 351 * Builder is the primary mechanism for create a {@link SelectionTracker} that 352 * can be used with your RecyclerView. Once installed, users will be able to create and 353 * manipulate selection using a variety of intuitive techniques like tap, gesture, 354 * and mouse lasso. 355 * 356 * <p> 357 * Example usage: 358 * <pre>SelectionTracker<Uri> tracker = new SelectionTracker.Builder<>( 359 * "my-uri-selection", 360 * recyclerView, 361 * new DemoStableIdProvider(recyclerView.getAdapter()), 362 * new MyDetailsLookup(recyclerView), 363 * StorageStrategy.createParcelableStorage(Uri.class)) 364 * .build(); 365 *</pre> 366 * 367 * <p> 368 * <b>Restricting which items can be selected and limiting selection size</b> 369 * 370 * <p> 371 * {@link SelectionPredicate} provides a mechanism to restrict which Items can be selected, 372 * to limit the number of items that can be selected, as well as allowing the selection 373 * code to be placed into "single select" mode, which as the name indicates, constrains 374 * the selection size to a single item. 375 * 376 * <p>Configuring the tracker for single single selection support can be done 377 * by supplying {@link SelectionPredicates#createSelectSingleAnything()}. 378 * 379 * SelectionTracker<String> tracker = new SelectionTracker.Builder<>( 380 * "my-string-selection", 381 * recyclerView, 382 * new DemoStableIdProvider(recyclerView.getAdapter()), 383 * new MyDetailsLookup(recyclerView), 384 * StorageStrategy.createStringStorage()) 385 * .withSelectionPredicate(SelectionPredicates#createSelectSingleAnything()) 386 * .build(); 387 *</pre> 388 * <p> 389 * <b>Retaining state across Android lifecycle events</b> 390 * 391 * <p> 392 * Support for storage/persistence of selection must be configured and invoked manually 393 * owing to its reliance on Activity lifecycle events. 394 * Failure to include support for selection storage will result in the active selection 395 * being lost when the Activity receives a configuration change (e.g. rotation) 396 * or when the application process is destroyed by the OS to reclaim resources. 397 * 398 * <p> 399 * <b>Key Type</b> 400 * 401 * <p> 402 * Developers must decide on the key type used to identify selected items. Support 403 * is provided for three types: {@link Parcelable}, {@link String}, and {@link Long}. 404 * 405 * <p> 406 * {@link Parcelable}: Any Parcelable type can be used as the selection key. This is especially 407 * useful in conjunction with {@link android.net.Uri} as the Android URI implementation is both 408 * parcelable and makes for a natural stable selection key for values represented by 409 * the Android Content Provider framework. If items in your view are associated with 410 * stable {@code content://} uris, you should use Uri for your key type. 411 * 412 * <p> 413 * {@link String}: Use String when a string based stable identifier is available. 414 * 415 * <p> 416 * {@link Long}: Use Long when RecyclerView's long stable ids are 417 * already in use. It comes with some limitations, however, as access to stable ids 418 * at runtime is limited. Band selection support is not available when using the default 419 * long key storage implementation. See {@link StableIdKeyProvider} for details. 420 * 421 * <p> 422 * Usage: 423 * 424 * <pre> 425 * private SelectionTracker<Uri> mTracker; 426 * 427 * public void onCreate(Bundle savedInstanceState) { 428 * // See above for details on constructing a SelectionTracker instance. 429 * 430 * if (savedInstanceState != null) { 431 * mTracker.onRestoreInstanceState(savedInstanceState); 432 * } 433 * } 434 * 435 * protected void onSaveInstanceState(Bundle outState) { 436 * super.onSaveInstanceState(outState); 437 * mTracker.onSaveInstanceState(outState); 438 * } 439 * </pre> 440 * 441 * @param <K> Selection key type. Built in support is provided for {@link String}, 442 * {@link Long}, and {@link Parcelable}. {@link StorageStrategy} 443 * provides factory methods for each type: 444 * {@link StorageStrategy#createStringStorage()}, 445 * {@link StorageStrategy#createParcelableStorage(Class)}, 446 * {@link StorageStrategy#createLongStorage()} 447 */ 448 public static final class Builder<K> { 449 450 private final RecyclerView mRecyclerView; 451 private final RecyclerView.Adapter<?> mAdapter; 452 private final Context mContext; 453 private final String mSelectionId; 454 private final StorageStrategy<K> mStorage; 455 456 private SelectionPredicate<K> mSelectionPredicate = 457 SelectionPredicates.createSelectAnything(); 458 private OperationMonitor mMonitor = new OperationMonitor(); 459 private ItemKeyProvider<K> mKeyProvider; 460 private ItemDetailsLookup<K> mDetailsLookup; 461 462 private FocusDelegate<K> mFocusDelegate = FocusDelegate.dummy(); 463 464 private OnItemActivatedListener<K> mOnItemActivatedListener; 465 private OnDragInitiatedListener mOnDragInitiatedListener; 466 private OnContextClickListener mOnContextClickListener; 467 468 private BandPredicate mBandPredicate; 469 private int mBandOverlayId = R.drawable.selection_band_overlay; 470 471 private int[] mGestureToolTypes = new int[] { 472 MotionEvent.TOOL_TYPE_FINGER, 473 MotionEvent.TOOL_TYPE_UNKNOWN 474 }; 475 476 private int[] mPointerToolTypes = new int[] { 477 MotionEvent.TOOL_TYPE_MOUSE 478 }; 479 480 /** 481 * Creates a new SelectionTracker.Builder useful for configuring and creating 482 * a new SelectionTracker for use with your {@link RecyclerView}. 483 * 484 * @param selectionId A unique string identifying this selection in the context 485 * of the activity or fragment. 486 * @param recyclerView the owning RecyclerView 487 * @param keyProvider the source of selection keys 488 * @param detailsLookup the source of information about RecyclerView items. 489 * @param storage Strategy for type-safe storage of selection state in 490 * {@link Bundle}. 491 */ 492 public Builder( 493 @NonNull String selectionId, 494 @NonNull RecyclerView recyclerView, 495 @NonNull ItemKeyProvider<K> keyProvider, 496 @NonNull ItemDetailsLookup<K> detailsLookup, 497 @NonNull StorageStrategy<K> storage) { 498 499 checkArgument(selectionId != null); 500 checkArgument(!selectionId.trim().isEmpty()); 501 checkArgument(recyclerView != null); 502 503 mSelectionId = selectionId; 504 mRecyclerView = recyclerView; 505 mContext = recyclerView.getContext(); 506 mAdapter = recyclerView.getAdapter(); 507 508 checkArgument(mAdapter != null); 509 checkArgument(keyProvider != null); 510 checkArgument(detailsLookup != null); 511 checkArgument(storage != null); 512 513 mDetailsLookup = detailsLookup; 514 mKeyProvider = keyProvider; 515 mStorage = storage; 516 517 mBandPredicate = new BandPredicate.NonDraggableArea(mRecyclerView, detailsLookup); 518 } 519 520 /** 521 * Install selection predicate. 522 * 523 * @param predicate the predicate to be used. 524 * @return this 525 */ 526 public Builder<K> withSelectionPredicate( 527 @NonNull SelectionPredicate<K> predicate) { 528 529 checkArgument(predicate != null); 530 mSelectionPredicate = predicate; 531 return this; 532 } 533 534 /** 535 * Add operation monitor allowing access to information about active 536 * operations (like band selection and gesture selection). 537 * 538 * @param monitor the monitor to be used 539 * @return this 540 */ 541 public Builder<K> withOperationMonitor( 542 @NonNull OperationMonitor monitor) { 543 544 checkArgument(monitor != null); 545 mMonitor = monitor; 546 return this; 547 } 548 549 /** 550 * Add focus delegate to interact with selection related focus changes. 551 * 552 * @param delegate the delegate to be used 553 * @return this 554 */ 555 public Builder<K> withFocusDelegate(@NonNull FocusDelegate<K> delegate) { 556 checkArgument(delegate != null); 557 mFocusDelegate = delegate; 558 return this; 559 } 560 561 /** 562 * Adds an item activation listener. Respond to taps/enter/double-click on items. 563 * 564 * @param listener the listener to be used 565 * @return this 566 */ 567 public Builder<K> withOnItemActivatedListener( 568 @NonNull OnItemActivatedListener<K> listener) { 569 570 checkArgument(listener != null); 571 572 mOnItemActivatedListener = listener; 573 return this; 574 } 575 576 /** 577 * Adds a context click listener. Respond to right-click. 578 * 579 * @param listener the listener to be used 580 * @return this 581 */ 582 public Builder<K> withOnContextClickListener( 583 @NonNull OnContextClickListener listener) { 584 585 checkArgument(listener != null); 586 587 mOnContextClickListener = listener; 588 return this; 589 } 590 591 /** 592 * Adds a drag initiated listener. Add support for drag and drop. 593 * 594 * @param listener the listener to be used 595 * @return this 596 */ 597 public Builder<K> withOnDragInitiatedListener( 598 @NonNull OnDragInitiatedListener listener) { 599 600 checkArgument(listener != null); 601 602 mOnDragInitiatedListener = listener; 603 return this; 604 } 605 606 /** 607 * Replaces default tap and gesture tool-types. Defaults are: 608 * {@link MotionEvent#TOOL_TYPE_FINGER} and {@link MotionEvent#TOOL_TYPE_UNKNOWN}. 609 * 610 * @param toolTypes the tool types to be used 611 * @return this 612 */ 613 public Builder<K> withGestureTooltypes(int... toolTypes) { 614 mGestureToolTypes = toolTypes; 615 return this; 616 } 617 618 /** 619 * Replaces default band overlay. 620 * 621 * @param bandOverlayId 622 * @return this 623 */ 624 public Builder<K> withBandOverlay(@DrawableRes int bandOverlayId) { 625 mBandOverlayId = bandOverlayId; 626 return this; 627 } 628 629 /** 630 * Replaces default band predicate. 631 * @param bandPredicate 632 * @return this 633 */ 634 public Builder<K> withBandPredicate(@NonNull BandPredicate bandPredicate) { 635 checkArgument(bandPredicate != null); 636 637 mBandPredicate = bandPredicate; 638 return this; 639 } 640 641 /** 642 * Replaces default pointer tool-types. Pointer tools 643 * are associated with band selection, and certain 644 * drag and drop behaviors. Defaults are: 645 * {@link MotionEvent#TOOL_TYPE_MOUSE}. 646 * 647 * @param toolTypes the tool types to be used 648 * @return this 649 */ 650 public Builder<K> withPointerTooltypes(int... toolTypes) { 651 mPointerToolTypes = toolTypes; 652 return this; 653 } 654 655 /** 656 * Prepares and returns a SelectionTracker. 657 * 658 * @return this 659 */ 660 public SelectionTracker<K> build() { 661 662 SelectionTracker<K> tracker = new DefaultSelectionTracker<>( 663 mSelectionId, mKeyProvider, mSelectionPredicate, mStorage); 664 665 // Event glue between RecyclerView and SelectionTracker keeps the classes separate 666 // so that a SelectionTracker can be shared across RecyclerView instances that 667 // represent the same data in different ways. 668 EventBridge.install(mAdapter, tracker, mKeyProvider); 669 670 AutoScroller scroller = 671 new ViewAutoScroller(ViewAutoScroller.createScrollHost(mRecyclerView)); 672 673 // Setup basic input handling, with the touch handler as the default consumer 674 // of events. If mouse handling is configured as well, the mouse input 675 // related handlers will intercept mouse input events. 676 677 // GestureRouter is responsible for routing GestureDetector events 678 // to tool-type specific handlers. 679 GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>(); 680 GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter); 681 682 // TouchEventRouter takes its name from RecyclerView#OnItemTouchListener. 683 // Despite "Touch" being in the name, it receives events for all types of tools. 684 // This class is responsible for routing events to tool-type specific handlers, 685 // and if not handled by a handler, on to a GestureDetector for analysis. 686 TouchEventRouter eventRouter = new TouchEventRouter(gestureDetector); 687 688 // GestureSelectionHelper provides logic that interprets a combination 689 // of motions and gestures in order to provide gesture driven selection support 690 // when used in conjunction with RecyclerView. 691 final GestureSelectionHelper gestureHelper = 692 GestureSelectionHelper.create(tracker, mRecyclerView, scroller, mMonitor); 693 694 // Finally hook the framework up to listening to recycle view events. 695 mRecyclerView.addOnItemTouchListener(eventRouter); 696 697 // But before you move on, there's more work to do. Event plumbing has been 698 // installed, but we haven't registered any of our helpers or callbacks. 699 // Helpers contain predefined logic converting events into selection related events. 700 // Callbacks provide developers the ability to reponspond to other types of 701 // events (like "activate" a tapped item). This is broken up into two main 702 // suites, one for "touch" and one for "mouse", though both can and should (usually) 703 // be configured to handle other types of input (to satisfy user expectation).); 704 705 // Internally, the code doesn't permit nullable listeners, so we lazily 706 // initialize dummy instances if the developer didn't supply a real listener. 707 mOnDragInitiatedListener = (mOnDragInitiatedListener != null) 708 ? mOnDragInitiatedListener 709 : new OnDragInitiatedListener() { 710 @Override 711 public boolean onDragInitiated(@NonNull MotionEvent e) { 712 return false; 713 } 714 }; 715 716 mOnItemActivatedListener = (mOnItemActivatedListener != null) 717 ? mOnItemActivatedListener 718 : new OnItemActivatedListener<K>() { 719 @Override 720 public boolean onItemActivated( 721 @NonNull ItemDetailsLookup.ItemDetails<K> item, 722 @NonNull MotionEvent e) { 723 return false; 724 } 725 }; 726 727 mOnContextClickListener = (mOnContextClickListener != null) 728 ? mOnContextClickListener 729 : new OnContextClickListener() { 730 @Override 731 public boolean onContextClick(@NonNull MotionEvent e) { 732 return false; 733 } 734 }; 735 736 // Provides high level glue for binding touch events 737 // and gestures to selection framework. 738 TouchInputHandler<K> touchHandler = new TouchInputHandler<K>( 739 tracker, 740 mKeyProvider, 741 mDetailsLookup, 742 mSelectionPredicate, 743 new Runnable() { 744 @Override 745 public void run() { 746 if (mSelectionPredicate.canSelectMultiple()) { 747 gestureHelper.start(); 748 } 749 } 750 }, 751 mOnDragInitiatedListener, 752 mOnItemActivatedListener, 753 mFocusDelegate, 754 new Runnable() { 755 @Override 756 public void run() { 757 mRecyclerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 758 } 759 }); 760 761 for (int toolType : mGestureToolTypes) { 762 gestureRouter.register(toolType, touchHandler); 763 eventRouter.register(toolType, gestureHelper); 764 } 765 766 // Provides high level glue for binding mouse events and gestures 767 // to selection framework. 768 MouseInputHandler<K> mouseHandler = new MouseInputHandler<>( 769 tracker, 770 mKeyProvider, 771 mDetailsLookup, 772 mOnContextClickListener, 773 mOnItemActivatedListener, 774 mFocusDelegate); 775 776 for (int toolType : mPointerToolTypes) { 777 gestureRouter.register(toolType, mouseHandler); 778 } 779 780 @Nullable BandSelectionHelper bandHelper = null; 781 782 // Band selection not supported in single select mode, or when key access 783 // is limited to anything less than the entire corpus. 784 if (mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED) 785 && mSelectionPredicate.canSelectMultiple()) { 786 // BandSelectionHelper provides support for band selection on-top of a RecyclerView 787 // instance. Given the recycling nature of RecyclerView BandSelectionController 788 // necessarily models and caches list/grid information as the user's pointer 789 // interacts with the item in the RecyclerView. Selectable items that intersect 790 // with the band, both on and off screen, are selected. 791 bandHelper = BandSelectionHelper.create( 792 mRecyclerView, 793 scroller, 794 mBandOverlayId, 795 mKeyProvider, 796 tracker, 797 mSelectionPredicate, 798 mBandPredicate, 799 mFocusDelegate, 800 mMonitor); 801 } 802 803 OnItemTouchListener pointerEventHandler = new PointerDragEventInterceptor( 804 mDetailsLookup, mOnDragInitiatedListener, bandHelper); 805 806 for (int toolType : mPointerToolTypes) { 807 eventRouter.register(toolType, pointerEventHandler); 808 } 809 810 return tracker; 811 } 812 } 813} 814