Home | History | Annotate | Download | only in widget
      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 
     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 }
    440