1 /*
2  * Copyright 2018 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.widget;
18 
19 import android.graphics.Rect;
20 import android.view.View;
21 
22 /**
23  * Helper class for LayoutManagers to abstract measurements depending on the View's orientation.
24  * <p>
25  * It is developed to easily support vertical and horizontal orientations in a LayoutManager but
26  * can also be used to abstract calls around view bounds and child measurements with margins and
27  * decorations.
28  *
29  * @see #createHorizontalHelper(RecyclerView.LayoutManager)
30  * @see #createVerticalHelper(RecyclerView.LayoutManager)
31  */
32 public abstract class OrientationHelper {
33 
34     private static final int INVALID_SIZE = Integer.MIN_VALUE;
35 
36     protected final RecyclerView.LayoutManager mLayoutManager;
37 
38     public static final int HORIZONTAL = RecyclerView.HORIZONTAL;
39 
40     public static final int VERTICAL = RecyclerView.VERTICAL;
41 
42     private int mLastTotalSpace = INVALID_SIZE;
43 
44     final Rect mTmpRect = new Rect();
45 
OrientationHelper(RecyclerView.LayoutManager layoutManager)46     private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
47         mLayoutManager = layoutManager;
48     }
49 
50     /**
51      * Returns the {@link RecyclerView.LayoutManager LayoutManager} that
52      * is associated with this OrientationHelper.
53      */
getLayoutManager()54     public RecyclerView.LayoutManager getLayoutManager() {
55         return mLayoutManager;
56     }
57 
58     /**
59      * Call this method after onLayout method is complete if state is NOT pre-layout.
60      * This method records information like layout bounds that might be useful in the next layout
61      * calculations.
62      */
onLayoutComplete()63     public void onLayoutComplete() {
64         mLastTotalSpace = getTotalSpace();
65     }
66 
67     /**
68      * Returns the layout space change between the previous layout pass and current layout pass.
69      * <p>
70      * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
71      * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
72      * RecyclerView.State)} method.
73      *
74      * @return The difference between the current total space and previous layout's total space.
75      * @see #onLayoutComplete()
76      */
getTotalSpaceChange()77     public int getTotalSpaceChange() {
78         return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
79     }
80 
81     /**
82      * Returns the start of the view including its decoration and margin.
83      * <p>
84      * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
85      * decoration and 3px left margin, returned value will be 15px.
86      *
87      * @param view The view element to check
88      * @return The first pixel of the element
89      * @see #getDecoratedEnd(android.view.View)
90      */
getDecoratedStart(View view)91     public abstract int getDecoratedStart(View view);
92 
93     /**
94      * Returns the end of the view including its decoration and margin.
95      * <p>
96      * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
97      * decoration and 3px right margin, returned value will be 205.
98      *
99      * @param view The view element to check
100      * @return The last pixel of the element
101      * @see #getDecoratedStart(android.view.View)
102      */
getDecoratedEnd(View view)103     public abstract int getDecoratedEnd(View view);
104 
105     /**
106      * Returns the end of the View after its matrix transformations are applied to its layout
107      * position.
108      * <p>
109      * This method is useful when trying to detect the visible edge of a View.
110      * <p>
111      * It includes the decorations but does not include the margins.
112      *
113      * @param view The view whose transformed end will be returned
114      * @return The end of the View after its decor insets and transformation matrix is applied to
115      * its position
116      *
117      * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
118      */
getTransformedEndWithDecoration(View view)119     public abstract int getTransformedEndWithDecoration(View view);
120 
121     /**
122      * Returns the start of the View after its matrix transformations are applied to its layout
123      * position.
124      * <p>
125      * This method is useful when trying to detect the visible edge of a View.
126      * <p>
127      * It includes the decorations but does not include the margins.
128      *
129      * @param view The view whose transformed start will be returned
130      * @return The start of the View after its decor insets and transformation matrix is applied to
131      * its position
132      *
133      * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
134      */
getTransformedStartWithDecoration(View view)135     public abstract int getTransformedStartWithDecoration(View view);
136 
137     /**
138      * Returns the space occupied by this View in the current orientation including decorations and
139      * margins.
140      *
141      * @param view The view element to check
142      * @return Total space occupied by this view
143      * @see #getDecoratedMeasurementInOther(View)
144      */
getDecoratedMeasurement(View view)145     public abstract int getDecoratedMeasurement(View view);
146 
147     /**
148      * Returns the space occupied by this View in the perpendicular orientation including
149      * decorations and margins.
150      *
151      * @param view The view element to check
152      * @return Total space occupied by this view in the perpendicular orientation to current one
153      * @see #getDecoratedMeasurement(View)
154      */
getDecoratedMeasurementInOther(View view)155     public abstract int getDecoratedMeasurementInOther(View view);
156 
157     /**
158      * Returns the start position of the layout after the start padding is added.
159      *
160      * @return The very first pixel we can draw.
161      */
getStartAfterPadding()162     public abstract int getStartAfterPadding();
163 
164     /**
165      * Returns the end position of the layout after the end padding is removed.
166      *
167      * @return The end boundary for this layout.
168      */
getEndAfterPadding()169     public abstract int getEndAfterPadding();
170 
171     /**
172      * Returns the end position of the layout without taking padding into account.
173      *
174      * @return The end boundary for this layout without considering padding.
175      */
getEnd()176     public abstract int getEnd();
177 
178     /**
179      * Offsets all children's positions by the given amount.
180      *
181      * @param amount Value to add to each child's layout parameters
182      */
offsetChildren(int amount)183     public abstract void offsetChildren(int amount);
184 
185     /**
186      * Returns the total space to layout. This number is the difference between
187      * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
188      *
189      * @return Total space to layout children
190      */
getTotalSpace()191     public abstract int getTotalSpace();
192 
193     /**
194      * Offsets the child in this orientation.
195      *
196      * @param view   View to offset
197      * @param offset offset amount
198      */
offsetChild(View view, int offset)199     public abstract void offsetChild(View view, int offset);
200 
201     /**
202      * Returns the padding at the end of the layout. For horizontal helper, this is the right
203      * padding and for vertical helper, this is the bottom padding. This method does not check
204      * whether the layout is RTL or not.
205      *
206      * @return The padding at the end of the layout.
207      */
getEndPadding()208     public abstract int getEndPadding();
209 
210     /**
211      * Returns the MeasureSpec mode for the current orientation from the LayoutManager.
212      *
213      * @return The current measure spec mode.
214      *
215      * @see View.MeasureSpec
216      * @see RecyclerView.LayoutManager#getWidthMode()
217      * @see RecyclerView.LayoutManager#getHeightMode()
218      */
getMode()219     public abstract int getMode();
220 
221     /**
222      * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager.
223      *
224      * @return The current measure spec mode.
225      *
226      * @see View.MeasureSpec
227      * @see RecyclerView.LayoutManager#getWidthMode()
228      * @see RecyclerView.LayoutManager#getHeightMode()
229      */
getModeInOther()230     public abstract int getModeInOther();
231 
232     /**
233      * Creates an OrientationHelper for the given LayoutManager and orientation.
234      *
235      * @param layoutManager LayoutManager to attach to
236      * @param orientation   Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
237      * @return A new OrientationHelper
238      */
createOrientationHelper( RecyclerView.LayoutManager layoutManager, @RecyclerView.Orientation int orientation)239     public static OrientationHelper createOrientationHelper(
240             RecyclerView.LayoutManager layoutManager, @RecyclerView.Orientation int orientation) {
241         switch (orientation) {
242             case HORIZONTAL:
243                 return createHorizontalHelper(layoutManager);
244             case VERTICAL:
245                 return createVerticalHelper(layoutManager);
246         }
247         throw new IllegalArgumentException("invalid orientation");
248     }
249 
250     /**
251      * Creates a horizontal OrientationHelper for the given LayoutManager.
252      *
253      * @param layoutManager The LayoutManager to attach to.
254      * @return A new OrientationHelper
255      */
createHorizontalHelper( RecyclerView.LayoutManager layoutManager)256     public static OrientationHelper createHorizontalHelper(
257             RecyclerView.LayoutManager layoutManager) {
258         return new OrientationHelper(layoutManager) {
259             @Override
260             public int getEndAfterPadding() {
261                 return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
262             }
263 
264             @Override
265             public int getEnd() {
266                 return mLayoutManager.getWidth();
267             }
268 
269             @Override
270             public void offsetChildren(int amount) {
271                 mLayoutManager.offsetChildrenHorizontal(amount);
272             }
273 
274             @Override
275             public int getStartAfterPadding() {
276                 return mLayoutManager.getPaddingLeft();
277             }
278 
279             @Override
280             public int getDecoratedMeasurement(View view) {
281                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
282                         view.getLayoutParams();
283                 return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
284                         + params.rightMargin;
285             }
286 
287             @Override
288             public int getDecoratedMeasurementInOther(View view) {
289                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
290                         view.getLayoutParams();
291                 return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
292                         + params.bottomMargin;
293             }
294 
295             @Override
296             public int getDecoratedEnd(View view) {
297                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
298                         view.getLayoutParams();
299                 return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
300             }
301 
302             @Override
303             public int getDecoratedStart(View view) {
304                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
305                         view.getLayoutParams();
306                 return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
307             }
308 
309             @Override
310             public int getTransformedEndWithDecoration(View view) {
311                 mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
312                 return mTmpRect.right;
313             }
314 
315             @Override
316             public int getTransformedStartWithDecoration(View view) {
317                 mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
318                 return mTmpRect.left;
319             }
320 
321             @Override
322             public int getTotalSpace() {
323                 return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
324                         - mLayoutManager.getPaddingRight();
325             }
326 
327             @Override
328             public void offsetChild(View view, int offset) {
329                 view.offsetLeftAndRight(offset);
330             }
331 
332             @Override
333             public int getEndPadding() {
334                 return mLayoutManager.getPaddingRight();
335             }
336 
337             @Override
338             public int getMode() {
339                 return mLayoutManager.getWidthMode();
340             }
341 
342             @Override
343             public int getModeInOther() {
344                 return mLayoutManager.getHeightMode();
345             }
346         };
347     }
348 
349     /**
350      * Creates a vertical OrientationHelper for the given LayoutManager.
351      *
352      * @param layoutManager The LayoutManager to attach to.
353      * @return A new OrientationHelper
354      */
355     public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
356         return new OrientationHelper(layoutManager) {
357             @Override
358             public int getEndAfterPadding() {
359                 return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
360             }
361 
362             @Override
363             public int getEnd() {
364                 return mLayoutManager.getHeight();
365             }
366 
367             @Override
368             public void offsetChildren(int amount) {
369                 mLayoutManager.offsetChildrenVertical(amount);
370             }
371 
372             @Override
373             public int getStartAfterPadding() {
374                 return mLayoutManager.getPaddingTop();
375             }
376 
377             @Override
378             public int getDecoratedMeasurement(View view) {
379                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
380                         view.getLayoutParams();
381                 return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
382                         + params.bottomMargin;
383             }
384 
385             @Override
386             public int getDecoratedMeasurementInOther(View view) {
387                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
388                         view.getLayoutParams();
389                 return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
390                         + params.rightMargin;
391             }
392 
393             @Override
394             public int getDecoratedEnd(View view) {
395                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
396                         view.getLayoutParams();
397                 return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
398             }
399 
400             @Override
401             public int getDecoratedStart(View view) {
402                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
403                         view.getLayoutParams();
404                 return mLayoutManager.getDecoratedTop(view) - params.topMargin;
405             }
406 
407             @Override
408             public int getTransformedEndWithDecoration(View view) {
409                 mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
410                 return mTmpRect.bottom;
411             }
412 
413             @Override
414             public int getTransformedStartWithDecoration(View view) {
415                 mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
416                 return mTmpRect.top;
417             }
418 
419             @Override
420             public int getTotalSpace() {
421                 return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
422                         - mLayoutManager.getPaddingBottom();
423             }
424 
425             @Override
426             public void offsetChild(View view, int offset) {
427                 view.offsetTopAndBottom(offset);
428             }
429 
430             @Override
431             public int getEndPadding() {
432                 return mLayoutManager.getPaddingBottom();
433             }
434 
435             @Override
436             public int getMode() {
437                 return mLayoutManager.getHeightMode();
438             }
439 
440             @Override
441             public int getModeInOther() {
442                 return mLayoutManager.getWidthMode();
443             }
444         };
445     }
446 }
447