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.graphics.Canvas; 22 import android.graphics.Point; 23 import android.graphics.Rect; 24 import android.graphics.drawable.Drawable; 25 import android.view.View; 26 27 import androidx.annotation.DrawableRes; 28 import androidx.core.content.ContextCompat; 29 import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate; 30 import androidx.recyclerview.widget.GridLayoutManager; 31 import androidx.recyclerview.widget.RecyclerView; 32 import androidx.recyclerview.widget.RecyclerView.ItemDecoration; 33 import androidx.recyclerview.widget.RecyclerView.OnScrollListener; 34 35 import org.jspecify.annotations.NonNull; 36 37 /** 38 * RecyclerView backed {@link BandSelectionHelper.BandHost}. 39 */ 40 final class DefaultBandHost<K> extends GridModel.GridHost<K> { 41 42 private static final Rect NILL_RECT = new Rect(0, 0, 0, 0); 43 44 private final RecyclerView mRecyclerView; 45 private final Drawable mBand; 46 private final ItemKeyProvider<K> mKeyProvider; 47 private final SelectionPredicate<K> mSelectionPredicate; 48 DefaultBandHost( @onNull RecyclerView recyclerView, @DrawableRes int bandOverlayId, @NonNull ItemKeyProvider<K> keyProvider, @NonNull SelectionPredicate<K> selectionPredicate)49 DefaultBandHost( 50 @NonNull RecyclerView recyclerView, 51 @DrawableRes int bandOverlayId, 52 @NonNull ItemKeyProvider<K> keyProvider, 53 @NonNull SelectionPredicate<K> selectionPredicate) { 54 55 checkArgument(recyclerView != null); 56 57 mRecyclerView = recyclerView; 58 mBand = ContextCompat.getDrawable(mRecyclerView.getContext(), bandOverlayId); 59 60 checkArgument(mBand != null); 61 checkArgument(keyProvider != null); 62 checkArgument(selectionPredicate != null); 63 64 mKeyProvider = keyProvider; 65 mSelectionPredicate = selectionPredicate; 66 67 mRecyclerView.addItemDecoration( 68 new ItemDecoration() { 69 @Override 70 public void onDrawOver( 71 Canvas canvas, 72 RecyclerView unusedParent, 73 RecyclerView.State unusedState) { 74 DefaultBandHost.this.onDrawBand(canvas); 75 } 76 }); 77 } 78 79 @Override createGridModel()80 GridModel<K> createGridModel() { 81 return new GridModel<>(this, mKeyProvider, mSelectionPredicate); 82 } 83 84 @Override getAdapterPositionAt(int index)85 int getAdapterPositionAt(int index) { 86 return mRecyclerView.getChildAdapterPosition(mRecyclerView.getChildAt(index)); 87 } 88 89 @Override addOnScrollListener(@onNull OnScrollListener listener)90 void addOnScrollListener(@NonNull OnScrollListener listener) { 91 mRecyclerView.addOnScrollListener(listener); 92 } 93 94 @Override removeOnScrollListener(@onNull OnScrollListener listener)95 void removeOnScrollListener(@NonNull OnScrollListener listener) { 96 mRecyclerView.removeOnScrollListener(listener); 97 } 98 99 @Override createAbsolutePoint(@onNull Point relativePoint)100 Point createAbsolutePoint(@NonNull Point relativePoint) { 101 return new Point(relativePoint.x + mRecyclerView.computeHorizontalScrollOffset(), 102 relativePoint.y + mRecyclerView.computeVerticalScrollOffset()); 103 } 104 105 @Override getAbsoluteRectForChildViewAt(int index)106 Rect getAbsoluteRectForChildViewAt(int index) { 107 final View child = mRecyclerView.getChildAt(index); 108 final Rect childRect = new Rect(); 109 child.getHitRect(childRect); 110 childRect.left += mRecyclerView.computeHorizontalScrollOffset(); 111 childRect.right += mRecyclerView.computeHorizontalScrollOffset(); 112 childRect.top += mRecyclerView.computeVerticalScrollOffset(); 113 childRect.bottom += mRecyclerView.computeVerticalScrollOffset(); 114 return childRect; 115 } 116 117 @Override getVisibleChildCount()118 int getVisibleChildCount() { 119 return mRecyclerView.getChildCount(); 120 } 121 122 @Override getColumnCount()123 int getColumnCount() { 124 RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); 125 if (layoutManager instanceof GridLayoutManager) { 126 return ((GridLayoutManager) layoutManager).getSpanCount(); 127 } 128 129 // Otherwise, it is a list with 1 column. 130 return 1; 131 } 132 133 @Override showBand(@onNull Rect rect)134 void showBand(@NonNull Rect rect) { 135 mBand.setBounds(rect); 136 // TODO: mRecyclerView.invalidateItemDecorations() should work, but it isn't currently. 137 // NOTE: That without invalidating rv, the band only gets updated 138 // when the pointer moves off a the item view into "NO_POSITION" territory. 139 mRecyclerView.invalidate(); 140 } 141 142 @Override hideBand()143 void hideBand() { 144 mBand.setBounds(NILL_RECT); 145 // TODO: mRecyclerView.invalidateItemDecorations() should work, but it isn't currently. 146 mRecyclerView.invalidate(); 147 } 148 149 @SuppressWarnings("WeakerAccess") /* synthetic access */ onDrawBand(@onNull Canvas c)150 void onDrawBand(@NonNull Canvas c) { 151 mBand.draw(c); 152 } 153 154 @Override hasView(int pos)155 boolean hasView(int pos) { 156 return mRecyclerView.findViewHolderForAdapterPosition(pos) != null; 157 } 158 } 159