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