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