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 17 package androidx.recyclerview.selection; 18 19 import static androidx.core.util.Preconditions.checkArgument; 20 21 import android.view.MotionEvent; 22 import android.view.View; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.Nullable; 26 import androidx.recyclerview.widget.GridLayoutManager; 27 import androidx.recyclerview.widget.LinearLayoutManager; 28 import androidx.recyclerview.widget.RecyclerView; 29 30 /** 31 * Provides a means of controlling when and where band selection can be initiated. 32 * 33 * <p> 34 * Two default implementations are provided: {@link EmptyArea}, and {@link NonDraggableArea}. 35 * 36 * @see SelectionTracker.Builder#withBandPredicate(BandPredicate) 37 */ 38 public abstract class BandPredicate { 39 40 /** 41 * @return true if band selection can be initiated in response to the {@link MotionEvent}. 42 */ 43 public abstract boolean canInitiate(MotionEvent e); 44 45 private static boolean hasSupportedLayoutManager(@NonNull RecyclerView recyclerView) { 46 RecyclerView.LayoutManager lm = recyclerView.getLayoutManager(); 47 return lm instanceof GridLayoutManager 48 || lm instanceof LinearLayoutManager; 49 } 50 51 /** 52 * A BandPredicate that allows initiation of band selection only in areas of RecyclerView 53 * that map to {@link RecyclerView#NO_POSITION}. In most cases, this will be the empty areas 54 * between views. 55 * 56 * <p> 57 * Use this implementation to permit band selection only in empty areas 58 * surrounding view items. But be advised that if there is no empy area around 59 * view items, band selection cannot be initiated. 60 */ 61 public static final class EmptyArea extends BandPredicate { 62 63 private final RecyclerView mRecyclerView; 64 65 /** 66 * @param recyclerView the owner RecyclerView 67 */ 68 public EmptyArea(@NonNull RecyclerView recyclerView) { 69 checkArgument(recyclerView != null); 70 71 mRecyclerView = recyclerView; 72 } 73 74 @Override 75 public boolean canInitiate(@NonNull MotionEvent e) { 76 if (!hasSupportedLayoutManager(mRecyclerView) 77 || mRecyclerView.hasPendingAdapterUpdates()) { 78 return false; 79 } 80 81 View itemView = mRecyclerView.findChildViewUnder(e.getX(), e.getY()); 82 int position = itemView != null 83 ? mRecyclerView.getChildAdapterPosition(itemView) 84 : RecyclerView.NO_POSITION; 85 86 return position == RecyclerView.NO_POSITION; 87 } 88 } 89 90 /** 91 * A BandPredicate that allows initiation of band selection in any area that is not 92 * draggable as determined by consulting 93 * {@link ItemDetailsLookup.ItemDetails#inDragRegion(MotionEvent)}. By default empty 94 * areas (those with a position that maps to {@link RecyclerView#NO_POSITION} 95 * are considered non-draggable. 96 * 97 * <p> 98 * Use this implementation in order to permit band selection in 99 * otherwise empty areas of a View. This is useful especially in 100 * list layouts where there is no empty space surrounding the list items, 101 * and individual list items may contain extra white space (like 102 * in a list of varying length words). 103 * 104 * @see ItemDetailsLookup.ItemDetails#inDragRegion(MotionEvent) 105 */ 106 public static final class NonDraggableArea extends BandPredicate { 107 108 private final RecyclerView mRecyclerView; 109 private final ItemDetailsLookup mDetailsLookup; 110 111 /** 112 * Creates a new instance. 113 * 114 * @param recyclerView the owner RecyclerView 115 * @param detailsLookup provides access to item details. 116 */ 117 public NonDraggableArea( 118 @NonNull RecyclerView recyclerView, @NonNull ItemDetailsLookup detailsLookup) { 119 120 checkArgument(recyclerView != null); 121 checkArgument(detailsLookup != null); 122 123 mRecyclerView = recyclerView; 124 mDetailsLookup = detailsLookup; 125 } 126 127 @Override 128 public boolean canInitiate(@NonNull MotionEvent e) { 129 if (!hasSupportedLayoutManager(mRecyclerView) 130 || mRecyclerView.hasPendingAdapterUpdates()) { 131 return false; 132 } 133 134 @Nullable ItemDetailsLookup.ItemDetails details = mDetailsLookup.getItemDetails(e); 135 return (details == null) || !details.inDragRegion(e); 136 } 137 } 138 } 139