Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package androidx.leanback.widget;
     16 
     17 import static androidx.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
     18 import static androidx.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
     19 import static androidx.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
     20 import static androidx.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
     21 import static androidx.recyclerview.widget.RecyclerView.HORIZONTAL;
     22 
     23 /**
     24  * Maintains Window Alignment information of two axis.
     25  */
     26 class WindowAlignment {
     27 
     28     /**
     29      * Maintains alignment information in one direction.
     30      */
     31     public static class Axis {
     32         /**
     33          * Right or bottom edge of last child.
     34          */
     35         private int mMaxEdge;
     36         /**
     37          * Left or top edge of first child
     38          */
     39         private int mMinEdge;
     40         /**
     41          * Scroll distance to align last child, it defines limit of scroll.
     42          */
     43         private int mMaxScroll;
     44         /**
     45          * Scroll distance to align first child, it defines limit of scroll.
     46          */
     47         private int mMinScroll;
     48 
     49         static final int PF_KEYLINE_OVER_LOW_EDGE = 1;
     50         static final int PF_KEYLINE_OVER_HIGH_EDGE = 1 << 1;
     51 
     52         /**
     53          * By default we prefer low edge over keyline, prefer keyline over high edge.
     54          */
     55         private int mPreferredKeyLine = PF_KEYLINE_OVER_HIGH_EDGE;
     56 
     57         private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;
     58 
     59         private int mWindowAlignmentOffset = 0;
     60 
     61         private float mWindowAlignmentOffsetPercent = 50f;
     62 
     63         private int mSize;
     64 
     65         /**
     66          * Padding at the min edge, it is the left or top padding.
     67          */
     68         private int mPaddingMin;
     69 
     70         /**
     71          * Padding at the max edge, it is the right or bottom padding.
     72          */
     73         private int mPaddingMax;
     74 
     75         private boolean mReversedFlow;
     76 
     77         private String mName; // for debugging
     78 
     79         public Axis(String name) {
     80             reset();
     81             mName = name;
     82         }
     83 
     84         public final int getWindowAlignment() {
     85             return mWindowAlignment;
     86         }
     87 
     88         public final void setWindowAlignment(int windowAlignment) {
     89             mWindowAlignment = windowAlignment;
     90         }
     91 
     92         final void setPreferKeylineOverLowEdge(boolean keylineOverLowEdge) {
     93             mPreferredKeyLine = keylineOverLowEdge
     94                     ? mPreferredKeyLine | PF_KEYLINE_OVER_LOW_EDGE
     95                     : mPreferredKeyLine & ~PF_KEYLINE_OVER_LOW_EDGE;
     96         }
     97 
     98         final void setPreferKeylineOverHighEdge(boolean keylineOverHighEdge) {
     99             mPreferredKeyLine = keylineOverHighEdge
    100                     ? mPreferredKeyLine | PF_KEYLINE_OVER_HIGH_EDGE
    101                     : mPreferredKeyLine & ~PF_KEYLINE_OVER_HIGH_EDGE;
    102         }
    103 
    104         final boolean isPreferKeylineOverHighEdge() {
    105             return (mPreferredKeyLine & PF_KEYLINE_OVER_HIGH_EDGE) != 0;
    106         }
    107 
    108         final boolean isPreferKeylineOverLowEdge() {
    109             return (mPreferredKeyLine & PF_KEYLINE_OVER_LOW_EDGE) != 0;
    110         }
    111 
    112         public final int getWindowAlignmentOffset() {
    113             return mWindowAlignmentOffset;
    114         }
    115 
    116         public final void setWindowAlignmentOffset(int offset) {
    117             mWindowAlignmentOffset = offset;
    118         }
    119 
    120         public final void setWindowAlignmentOffsetPercent(float percent) {
    121             if ((percent < 0 || percent > 100)
    122                     && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
    123                 throw new IllegalArgumentException();
    124             }
    125             mWindowAlignmentOffsetPercent = percent;
    126         }
    127 
    128         public final float getWindowAlignmentOffsetPercent() {
    129             return mWindowAlignmentOffsetPercent;
    130         }
    131 
    132         /**
    133          * Returns scroll distance to align min child.
    134          */
    135         public final int getMinScroll() {
    136             return mMinScroll;
    137         }
    138 
    139         public final void invalidateScrollMin() {
    140             mMinEdge = Integer.MIN_VALUE;
    141             mMinScroll = Integer.MIN_VALUE;
    142         }
    143 
    144         /**
    145          * Returns scroll distance to align max child.
    146          */
    147         public final int getMaxScroll() {
    148             return mMaxScroll;
    149         }
    150 
    151         public final void invalidateScrollMax() {
    152             mMaxEdge = Integer.MAX_VALUE;
    153             mMaxScroll = Integer.MAX_VALUE;
    154         }
    155 
    156         void reset() {
    157             mMinEdge = Integer.MIN_VALUE;
    158             mMaxEdge = Integer.MAX_VALUE;
    159         }
    160 
    161         public final boolean isMinUnknown() {
    162             return mMinEdge == Integer.MIN_VALUE;
    163         }
    164 
    165         public final boolean isMaxUnknown() {
    166             return mMaxEdge == Integer.MAX_VALUE;
    167         }
    168 
    169         public final void setSize(int size) {
    170             mSize = size;
    171         }
    172 
    173         public final int getSize() {
    174             return mSize;
    175         }
    176 
    177         public final void setPadding(int paddingMin, int paddingMax) {
    178             mPaddingMin = paddingMin;
    179             mPaddingMax = paddingMax;
    180         }
    181 
    182         public final int getPaddingMin() {
    183             return mPaddingMin;
    184         }
    185 
    186         public final int getPaddingMax() {
    187             return mPaddingMax;
    188         }
    189 
    190         public final int getClientSize() {
    191             return mSize - mPaddingMin - mPaddingMax;
    192         }
    193 
    194         final int calculateKeyline() {
    195             int keyLine;
    196             if (!mReversedFlow) {
    197                 if (mWindowAlignmentOffset >= 0) {
    198                     keyLine = mWindowAlignmentOffset;
    199                 } else {
    200                     keyLine = mSize + mWindowAlignmentOffset;
    201                 }
    202                 if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
    203                     keyLine += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
    204                 }
    205             } else {
    206                 if (mWindowAlignmentOffset >= 0) {
    207                     keyLine = mSize - mWindowAlignmentOffset;
    208                 } else {
    209                     keyLine = -mWindowAlignmentOffset;
    210                 }
    211                 if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
    212                     keyLine -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
    213                 }
    214             }
    215             return keyLine;
    216         }
    217 
    218         /**
    219          * Returns scroll distance to move viewCenterPosition to keyLine.
    220          */
    221         final int calculateScrollToKeyLine(int viewCenterPosition, int keyLine) {
    222             return viewCenterPosition - keyLine;
    223         }
    224 
    225         /**
    226          * Update {@link #getMinScroll()} and {@link #getMaxScroll()}
    227          */
    228         public final void updateMinMax(int minEdge, int maxEdge,
    229                 int minChildViewCenter, int maxChildViewCenter) {
    230             mMinEdge = minEdge;
    231             mMaxEdge = maxEdge;
    232             final int clientSize = getClientSize();
    233             final int keyLine = calculateKeyline();
    234             final boolean isMinUnknown = isMinUnknown();
    235             final boolean isMaxUnknown = isMaxUnknown();
    236             if (!isMinUnknown) {
    237                 if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
    238                         : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
    239                     // calculate scroll distance to move current mMinEdge to padding at min edge
    240                     mMinScroll = mMinEdge - mPaddingMin;
    241                 } else  {
    242                     // calculate scroll distance to move min child center to key line
    243                     mMinScroll = calculateScrollToKeyLine(minChildViewCenter, keyLine);
    244                 }
    245             }
    246             if (!isMaxUnknown) {
    247                 if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
    248                         : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
    249                     // calculate scroll distance to move current mMaxEdge to padding at max edge
    250                     mMaxScroll = mMaxEdge - mPaddingMin - clientSize;
    251                 } else  {
    252                     // calculate scroll distance to move max child center to key line
    253                     mMaxScroll = calculateScrollToKeyLine(maxChildViewCenter, keyLine);
    254                 }
    255             }
    256             if (!isMaxUnknown && !isMinUnknown) {
    257                 if (!mReversedFlow) {
    258                     if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
    259                         if (isPreferKeylineOverLowEdge()) {
    260                             // if we prefer key line, might align max child to key line for
    261                             // minScroll
    262                             mMinScroll = Math.min(mMinScroll,
    263                                     calculateScrollToKeyLine(maxChildViewCenter, keyLine));
    264                         }
    265                         // don't over scroll max
    266                         mMaxScroll = Math.max(mMinScroll, mMaxScroll);
    267                     } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
    268                         if (isPreferKeylineOverHighEdge()) {
    269                             // if we prefer key line, might align min child to key line for
    270                             // maxScroll
    271                             mMaxScroll = Math.max(mMaxScroll,
    272                                     calculateScrollToKeyLine(minChildViewCenter, keyLine));
    273                         }
    274                         // don't over scroll min
    275                         mMinScroll = Math.min(mMinScroll, mMaxScroll);
    276                     }
    277                 } else {
    278                     if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
    279                         if (isPreferKeylineOverLowEdge()) {
    280                             // if we prefer key line, might align min child to key line for
    281                             // maxScroll
    282                             mMaxScroll = Math.max(mMaxScroll,
    283                                     calculateScrollToKeyLine(minChildViewCenter, keyLine));
    284                         }
    285                         // don't over scroll min
    286                         mMinScroll = Math.min(mMinScroll, mMaxScroll);
    287                     } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
    288                         if (isPreferKeylineOverHighEdge()) {
    289                             // if we prefer key line, might align max child to key line for
    290                             // minScroll
    291                             mMinScroll = Math.min(mMinScroll,
    292                                     calculateScrollToKeyLine(maxChildViewCenter, keyLine));
    293                         }
    294                         // don't over scroll max
    295                         mMaxScroll = Math.max(mMinScroll, mMaxScroll);
    296                     }
    297                 }
    298             }
    299         }
    300 
    301         /**
    302          * Get scroll distance of align an item (depends on ALIGN_LOW_EDGE, ALIGN_HIGH_EDGE or the
    303          * item should be aligned to key line). The scroll distance will be capped by
    304          * {@link #getMinScroll()} and {@link #getMaxScroll()}.
    305          */
    306         public final int getScroll(int viewCenter) {
    307             final int size = getSize();
    308             final int keyLine = calculateKeyline();
    309             final boolean isMinUnknown = isMinUnknown();
    310             final boolean isMaxUnknown = isMaxUnknown();
    311             if (!isMinUnknown) {
    312                 final int keyLineToMinEdge = keyLine - mPaddingMin;
    313                 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
    314                      : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
    315                         && (viewCenter - mMinEdge <= keyLineToMinEdge)) {
    316                     // view center is before key line: align the min edge (first child) to padding.
    317                     int alignToMin = mMinEdge - mPaddingMin;
    318                     // Also we need make sure don't over scroll
    319                     if (!isMaxUnknown && alignToMin > mMaxScroll) {
    320                         alignToMin = mMaxScroll;
    321                     }
    322                     return alignToMin;
    323                 }
    324             }
    325             if (!isMaxUnknown) {
    326                 final int keyLineToMaxEdge = size - keyLine - mPaddingMax;
    327                 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
    328                         : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
    329                         && (mMaxEdge - viewCenter <= keyLineToMaxEdge)) {
    330                     // view center is after key line: align the max edge (last child) to padding.
    331                     int alignToMax = mMaxEdge - (size - mPaddingMax);
    332                     // Also we need make sure don't over scroll
    333                     if (!isMinUnknown && alignToMax < mMinScroll) {
    334                         alignToMax = mMinScroll;
    335                     }
    336                     return alignToMax;
    337                 }
    338             }
    339             // else put view center at key line.
    340             return calculateScrollToKeyLine(viewCenter, keyLine);
    341         }
    342 
    343         public final void setReversedFlow(boolean reversedFlow) {
    344             mReversedFlow = reversedFlow;
    345         }
    346 
    347         @Override
    348         public String toString() {
    349             return " min:" + mMinEdge + " " + mMinScroll + " max:" + mMaxEdge + " " + mMaxScroll;
    350         }
    351 
    352     }
    353 
    354     private int mOrientation = HORIZONTAL;
    355 
    356     public final Axis vertical = new Axis("vertical");
    357 
    358     public final Axis horizontal = new Axis("horizontal");
    359 
    360     private Axis mMainAxis = horizontal;
    361 
    362     private Axis mSecondAxis = vertical;
    363 
    364     public final Axis mainAxis() {
    365         return mMainAxis;
    366     }
    367 
    368     public final Axis secondAxis() {
    369         return mSecondAxis;
    370     }
    371 
    372     public final void setOrientation(int orientation) {
    373         mOrientation = orientation;
    374         if (mOrientation == HORIZONTAL) {
    375             mMainAxis = horizontal;
    376             mSecondAxis = vertical;
    377         } else {
    378             mMainAxis = vertical;
    379             mSecondAxis = horizontal;
    380         }
    381     }
    382 
    383     public final int getOrientation() {
    384         return mOrientation;
    385     }
    386 
    387     public final void reset() {
    388         mainAxis().reset();
    389     }
    390 
    391     @Override
    392     public String toString() {
    393         return "horizontal=" + horizontal + "; vertical=" + vertical;
    394     }
    395 
    396 }
    397