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
17package android.support.v7.widget;
18
19import android.graphics.Rect;
20import android.view.View;
21import 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 */
33public 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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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}