• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.support.v17.leanback.widget;
16 
17 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
18 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
19 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
20 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
21 import static android.support.v7.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 
Axis(String name)79         public Axis(String name) {
80             reset();
81             mName = name;
82         }
83 
getWindowAlignment()84         public final int getWindowAlignment() {
85             return mWindowAlignment;
86         }
87 
setWindowAlignment(int windowAlignment)88         public final void setWindowAlignment(int windowAlignment) {
89             mWindowAlignment = windowAlignment;
90         }
91 
setPreferKeylineOverLowEdge(boolean keylineOverLowEdge)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 
setPreferKeylineOverHighEdge(boolean keylineOverHighEdge)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 
isPreferKeylineOverHighEdge()104         final boolean isPreferKeylineOverHighEdge() {
105             return (mPreferredKeyLine & PF_KEYLINE_OVER_HIGH_EDGE) != 0;
106         }
107 
isPreferKeylineOverLowEdge()108         final boolean isPreferKeylineOverLowEdge() {
109             return (mPreferredKeyLine & PF_KEYLINE_OVER_LOW_EDGE) != 0;
110         }
111 
getWindowAlignmentOffset()112         public final int getWindowAlignmentOffset() {
113             return mWindowAlignmentOffset;
114         }
115 
setWindowAlignmentOffset(int offset)116         public final void setWindowAlignmentOffset(int offset) {
117             mWindowAlignmentOffset = offset;
118         }
119 
setWindowAlignmentOffsetPercent(float percent)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 
getWindowAlignmentOffsetPercent()128         public final float getWindowAlignmentOffsetPercent() {
129             return mWindowAlignmentOffsetPercent;
130         }
131 
132         /**
133          * Returns scroll distance to align min child.
134          */
getMinScroll()135         public final int getMinScroll() {
136             return mMinScroll;
137         }
138 
invalidateScrollMin()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          */
getMaxScroll()147         public final int getMaxScroll() {
148             return mMaxScroll;
149         }
150 
invalidateScrollMax()151         public final void invalidateScrollMax() {
152             mMaxEdge = Integer.MAX_VALUE;
153             mMaxScroll = Integer.MAX_VALUE;
154         }
155 
reset()156         void reset() {
157             mMinEdge = Integer.MIN_VALUE;
158             mMaxEdge = Integer.MAX_VALUE;
159         }
160 
isMinUnknown()161         public final boolean isMinUnknown() {
162             return mMinEdge == Integer.MIN_VALUE;
163         }
164 
isMaxUnknown()165         public final boolean isMaxUnknown() {
166             return mMaxEdge == Integer.MAX_VALUE;
167         }
168 
setSize(int size)169         public final void setSize(int size) {
170             mSize = size;
171         }
172 
getSize()173         public final int getSize() {
174             return mSize;
175         }
176 
setPadding(int paddingMin, int paddingMax)177         public final void setPadding(int paddingMin, int paddingMax) {
178             mPaddingMin = paddingMin;
179             mPaddingMax = paddingMax;
180         }
181 
getPaddingMin()182         public final int getPaddingMin() {
183             return mPaddingMin;
184         }
185 
getPaddingMax()186         public final int getPaddingMax() {
187             return mPaddingMax;
188         }
189 
getClientSize()190         public final int getClientSize() {
191             return mSize - mPaddingMin - mPaddingMax;
192         }
193 
calculateKeyline()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          */
calculateScrollToKeyLine(int viewCenterPosition, int keyLine)221         final int calculateScrollToKeyLine(int viewCenterPosition, int keyLine) {
222             return viewCenterPosition - keyLine;
223         }
224 
225         /**
226          * Update {@link #getMinScroll()} and {@link #getMaxScroll()}
227          */
updateMinMax(int minEdge, int maxEdge, int minChildViewCenter, int maxChildViewCenter)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          */
getScroll(int viewCenter)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 
setReversedFlow(boolean reversedFlow)343         public final void setReversedFlow(boolean reversedFlow) {
344             mReversedFlow = reversedFlow;
345         }
346 
347         @Override
toString()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 
mainAxis()364     public final Axis mainAxis() {
365         return mMainAxis;
366     }
367 
secondAxis()368     public final Axis secondAxis() {
369         return mSecondAxis;
370     }
371 
setOrientation(int orientation)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 
getOrientation()383     public final int getOrientation() {
384         return mOrientation;
385     }
386 
reset()387     public final void reset() {
388         mainAxis().reset();
389     }
390 
391     @Override
toString()392     public String toString() {
393         return new StringBuffer().append("horizontal=")
394                 .append(horizontal.toString())
395                 .append("; vertical=")
396                 .append(vertical.toString())
397                 .toString();
398     }
399 
400 }
401