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