1 /*
2  * Copyright (C) 2014 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 android.support.v7.widget;
18 
19 import android.view.View;
20 import android.widget.LinearLayout;
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 = LinearLayout.HORIZONTAL;
39 
40     public static final int VERTICAL = LinearLayout.VERTICAL;
41 
42     private int mLastTotalSpace = INVALID_SIZE;
43 
OrientationHelper(RecyclerView.LayoutManager layoutManager)44     private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
45         mLayoutManager = layoutManager;
46     }
47 
48     /**
49      * Call this method after onLayout method is complete if state is NOT pre-layout.
50      * This method records information like layout bounds that might be useful in the next layout
51      * calculations.
52      */
onLayoutComplete()53     public void onLayoutComplete() {
54         mLastTotalSpace = getTotalSpace();
55     }
56 
57     /**
58      * Returns the layout space change between the previous layout pass and current layout pass.
59      * <p>
60      * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
61      * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
62      * RecyclerView.State)} method.
63      *
64      * @return The difference between the current total space and previous layout's total space.
65      * @see #onLayoutComplete()
66      */
getTotalSpaceChange()67     public int getTotalSpaceChange() {
68         return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
69     }
70 
71     /**
72      * Returns the start of the view including its decoration and margin.
73      * <p>
74      * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
75      * decoration and 3px left margin, returned value will be 15px.
76      *
77      * @param view The view element to check
78      * @return The first pixel of the element
79      * @see #getDecoratedEnd(android.view.View)
80      */
getDecoratedStart(View view)81     public abstract int getDecoratedStart(View view);
82 
83     /**
84      * Returns the end of the view including its decoration and margin.
85      * <p>
86      * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
87      * decoration and 3px right margin, returned value will be 205.
88      *
89      * @param view The view element to check
90      * @return The last pixel of the element
91      * @see #getDecoratedStart(android.view.View)
92      */
getDecoratedEnd(View view)93     public abstract int getDecoratedEnd(View view);
94 
95     /**
96      * Returns the space occupied by this View in the current orientation including decorations and
97      * margins.
98      *
99      * @param view The view element to check
100      * @return Total space occupied by this view
101      * @see #getDecoratedMeasurementInOther(View)
102      */
getDecoratedMeasurement(View view)103     public abstract int getDecoratedMeasurement(View view);
104 
105     /**
106      * Returns the space occupied by this View in the perpendicular orientation including
107      * decorations and margins.
108      *
109      * @param view The view element to check
110      * @return Total space occupied by this view in the perpendicular orientation to current one
111      * @see #getDecoratedMeasurement(View)
112      */
getDecoratedMeasurementInOther(View view)113     public abstract int getDecoratedMeasurementInOther(View view);
114 
115     /**
116      * Returns the start position of the layout after the start padding is added.
117      *
118      * @return The very first pixel we can draw.
119      */
getStartAfterPadding()120     public abstract int getStartAfterPadding();
121 
122     /**
123      * Returns the end position of the layout after the end padding is removed.
124      *
125      * @return The end boundary for this layout.
126      */
getEndAfterPadding()127     public abstract int getEndAfterPadding();
128 
129     /**
130      * Returns the end position of the layout without taking padding into account.
131      *
132      * @return The end boundary for this layout without considering padding.
133      */
getEnd()134     public abstract int getEnd();
135 
136     /**
137      * Offsets all children's positions by the given amount.
138      *
139      * @param amount Value to add to each child's layout parameters
140      */
offsetChildren(int amount)141     public abstract void offsetChildren(int amount);
142 
143     /**
144      * Returns the total space to layout. This number is the difference between
145      * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
146      *
147      * @return Total space to layout children
148      */
getTotalSpace()149     public abstract int getTotalSpace();
150 
151     /**
152      * Offsets the child in this orientation.
153      *
154      * @param view   View to offset
155      * @param offset offset amount
156      */
offsetChild(View view, int offset)157     public abstract void offsetChild(View view, int offset);
158 
159     /**
160      * Returns the padding at the end of the layout. For horizontal helper, this is the right
161      * padding and for vertical helper, this is the bottom padding. This method does not check
162      * whether the layout is RTL or not.
163      *
164      * @return The padding at the end of the layout.
165      */
getEndPadding()166     public abstract int getEndPadding();
167 
168     /**
169      * Creates an OrientationHelper for the given LayoutManager and orientation.
170      *
171      * @param layoutManager LayoutManager to attach to
172      * @param orientation   Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
173      * @return A new OrientationHelper
174      */
createOrientationHelper( RecyclerView.LayoutManager layoutManager, int orientation)175     public static OrientationHelper createOrientationHelper(
176             RecyclerView.LayoutManager layoutManager, int orientation) {
177         switch (orientation) {
178             case HORIZONTAL:
179                 return createHorizontalHelper(layoutManager);
180             case VERTICAL:
181                 return createVerticalHelper(layoutManager);
182         }
183         throw new IllegalArgumentException("invalid orientation");
184     }
185 
186     /**
187      * Creates a horizontal OrientationHelper for the given LayoutManager.
188      *
189      * @param layoutManager The LayoutManager to attach to.
190      * @return A new OrientationHelper
191      */
createHorizontalHelper( RecyclerView.LayoutManager layoutManager)192     public static OrientationHelper createHorizontalHelper(
193             RecyclerView.LayoutManager layoutManager) {
194         return new OrientationHelper(layoutManager) {
195             @Override
196             public int getEndAfterPadding() {
197                 return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
198             }
199 
200             @Override
201             public int getEnd() {
202                 return mLayoutManager.getWidth();
203             }
204 
205             @Override
206             public void offsetChildren(int amount) {
207                 mLayoutManager.offsetChildrenHorizontal(amount);
208             }
209 
210             @Override
211             public int getStartAfterPadding() {
212                 return mLayoutManager.getPaddingLeft();
213             }
214 
215             @Override
216             public int getDecoratedMeasurement(View view) {
217                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
218                         view.getLayoutParams();
219                 return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
220                         + params.rightMargin;
221             }
222 
223             @Override
224             public int getDecoratedMeasurementInOther(View view) {
225                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
226                         view.getLayoutParams();
227                 return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
228                         + params.bottomMargin;
229             }
230 
231             @Override
232             public int getDecoratedEnd(View view) {
233                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
234                         view.getLayoutParams();
235                 return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
236             }
237 
238             @Override
239             public int getDecoratedStart(View view) {
240                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
241                         view.getLayoutParams();
242                 return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
243             }
244 
245             @Override
246             public int getTotalSpace() {
247                 return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
248                         - mLayoutManager.getPaddingRight();
249             }
250 
251             @Override
252             public void offsetChild(View view, int offset) {
253                 view.offsetLeftAndRight(offset);
254             }
255 
256             @Override
257             public int getEndPadding() {
258                 return mLayoutManager.getPaddingRight();
259             }
260         };
261     }
262 
263     /**
264      * Creates a vertical OrientationHelper for the given LayoutManager.
265      *
266      * @param layoutManager The LayoutManager to attach to.
267      * @return A new OrientationHelper
268      */
269     public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
270         return new OrientationHelper(layoutManager) {
271             @Override
272             public int getEndAfterPadding() {
273                 return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
274             }
275 
276             @Override
277             public int getEnd() {
278                 return mLayoutManager.getHeight();
279             }
280 
281             @Override
282             public void offsetChildren(int amount) {
283                 mLayoutManager.offsetChildrenVertical(amount);
284             }
285 
286             @Override
287             public int getStartAfterPadding() {
288                 return mLayoutManager.getPaddingTop();
289             }
290 
291             @Override
292             public int getDecoratedMeasurement(View view) {
293                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
294                         view.getLayoutParams();
295                 return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
296                         + params.bottomMargin;
297             }
298 
299             @Override
300             public int getDecoratedMeasurementInOther(View view) {
301                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
302                         view.getLayoutParams();
303                 return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
304                         + params.rightMargin;
305             }
306 
307             @Override
308             public int getDecoratedEnd(View view) {
309                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
310                         view.getLayoutParams();
311                 return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
312             }
313 
314             @Override
315             public int getDecoratedStart(View view) {
316                 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
317                         view.getLayoutParams();
318                 return mLayoutManager.getDecoratedTop(view) - params.topMargin;
319             }
320 
321             @Override
322             public int getTotalSpace() {
323                 return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
324                         - mLayoutManager.getPaddingBottom();
325             }
326 
327             @Override
328             public void offsetChild(View view, int offset) {
329                 view.offsetTopAndBottom(offset);
330             }
331 
332             @Override
333             public int getEndPadding() {
334                 return mLayoutManager.getPaddingBottom();
335             }
336         };
337     }
338 }