summaryrefslogtreecommitdiff
path: root/android/support/v17/leanback/widget/WindowAlignment.java
blob: 55fa75899f3ff39ead569b35aa1f5a93251756af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package android.support.v17.leanback.widget;

import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
import static android.support.v7.widget.RecyclerView.HORIZONTAL;

/**
 * Maintains Window Alignment information of two axis.
 */
class WindowAlignment {

    /**
     * Maintains alignment information in one direction.
     */
    public static class Axis {
        /**
         * Right or bottom edge of last child.
         */
        private int mMaxEdge;
        /**
         * Left or top edge of first child
         */
        private int mMinEdge;
        /**
         * Scroll distance to align last child, it defines limit of scroll.
         */
        private int mMaxScroll;
        /**
         * Scroll distance to align first child, it defines limit of scroll.
         */
        private int mMinScroll;

        static final int PF_KEYLINE_OVER_LOW_EDGE = 1;
        static final int PF_KEYLINE_OVER_HIGH_EDGE = 1 << 1;

        /**
         * By default we prefer low edge over keyline, prefer keyline over high edge.
         */
        private int mPreferredKeyLine = PF_KEYLINE_OVER_HIGH_EDGE;

        private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;

        private int mWindowAlignmentOffset = 0;

        private float mWindowAlignmentOffsetPercent = 50f;

        private int mSize;

        /**
         * Padding at the min edge, it is the left or top padding.
         */
        private int mPaddingMin;

        /**
         * Padding at the max edge, it is the right or bottom padding.
         */
        private int mPaddingMax;

        private boolean mReversedFlow;

        private String mName; // for debugging

        public Axis(String name) {
            reset();
            mName = name;
        }

        public final int getWindowAlignment() {
            return mWindowAlignment;
        }

        public final void setWindowAlignment(int windowAlignment) {
            mWindowAlignment = windowAlignment;
        }

        final void setPreferKeylineOverLowEdge(boolean keylineOverLowEdge) {
            mPreferredKeyLine = keylineOverLowEdge
                    ? mPreferredKeyLine | PF_KEYLINE_OVER_LOW_EDGE
                    : mPreferredKeyLine & ~PF_KEYLINE_OVER_LOW_EDGE;
        }

        final void setPreferKeylineOverHighEdge(boolean keylineOverHighEdge) {
            mPreferredKeyLine = keylineOverHighEdge
                    ? mPreferredKeyLine | PF_KEYLINE_OVER_HIGH_EDGE
                    : mPreferredKeyLine & ~PF_KEYLINE_OVER_HIGH_EDGE;
        }

        final boolean isPreferKeylineOverHighEdge() {
            return (mPreferredKeyLine & PF_KEYLINE_OVER_HIGH_EDGE) != 0;
        }

        final boolean isPreferKeylineOverLowEdge() {
            return (mPreferredKeyLine & PF_KEYLINE_OVER_LOW_EDGE) != 0;
        }

        public final int getWindowAlignmentOffset() {
            return mWindowAlignmentOffset;
        }

        public final void setWindowAlignmentOffset(int offset) {
            mWindowAlignmentOffset = offset;
        }

        public final void setWindowAlignmentOffsetPercent(float percent) {
            if ((percent < 0 || percent > 100)
                    && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
                throw new IllegalArgumentException();
            }
            mWindowAlignmentOffsetPercent = percent;
        }

        public final float getWindowAlignmentOffsetPercent() {
            return mWindowAlignmentOffsetPercent;
        }

        /**
         * Returns scroll distance to align min child.
         */
        public final int getMinScroll() {
            return mMinScroll;
        }

        public final void invalidateScrollMin() {
            mMinEdge = Integer.MIN_VALUE;
            mMinScroll = Integer.MIN_VALUE;
        }

        /**
         * Returns scroll distance to align max child.
         */
        public final int getMaxScroll() {
            return mMaxScroll;
        }

        public final void invalidateScrollMax() {
            mMaxEdge = Integer.MAX_VALUE;
            mMaxScroll = Integer.MAX_VALUE;
        }

        void reset() {
            mMinEdge = Integer.MIN_VALUE;
            mMaxEdge = Integer.MAX_VALUE;
        }

        public final boolean isMinUnknown() {
            return mMinEdge == Integer.MIN_VALUE;
        }

        public final boolean isMaxUnknown() {
            return mMaxEdge == Integer.MAX_VALUE;
        }

        public final void setSize(int size) {
            mSize = size;
        }

        public final int getSize() {
            return mSize;
        }

        public final void setPadding(int paddingMin, int paddingMax) {
            mPaddingMin = paddingMin;
            mPaddingMax = paddingMax;
        }

        public final int getPaddingMin() {
            return mPaddingMin;
        }

        public final int getPaddingMax() {
            return mPaddingMax;
        }

        public final int getClientSize() {
            return mSize - mPaddingMin - mPaddingMax;
        }

        final int calculateKeyline() {
            int keyLine;
            if (!mReversedFlow) {
                if (mWindowAlignmentOffset >= 0) {
                    keyLine = mWindowAlignmentOffset;
                } else {
                    keyLine = mSize + mWindowAlignmentOffset;
                }
                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
                    keyLine += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
                }
            } else {
                if (mWindowAlignmentOffset >= 0) {
                    keyLine = mSize - mWindowAlignmentOffset;
                } else {
                    keyLine = -mWindowAlignmentOffset;
                }
                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
                    keyLine -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
                }
            }
            return keyLine;
        }

        /**
         * Returns scroll distance to move viewCenterPosition to keyLine.
         */
        final int calculateScrollToKeyLine(int viewCenterPosition, int keyLine) {
            return viewCenterPosition - keyLine;
        }

        /**
         * Update {@link #getMinScroll()} and {@link #getMaxScroll()}
         */
        public final void updateMinMax(int minEdge, int maxEdge,
                int minChildViewCenter, int maxChildViewCenter) {
            mMinEdge = minEdge;
            mMaxEdge = maxEdge;
            final int clientSize = getClientSize();
            final int keyLine = calculateKeyline();
            final boolean isMinUnknown = isMinUnknown();
            final boolean isMaxUnknown = isMaxUnknown();
            if (!isMinUnknown) {
                if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
                        : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
                    // calculate scroll distance to move current mMinEdge to padding at min edge
                    mMinScroll = mMinEdge - mPaddingMin;
                } else  {
                    // calculate scroll distance to move min child center to key line
                    mMinScroll = calculateScrollToKeyLine(minChildViewCenter, keyLine);
                }
            }
            if (!isMaxUnknown) {
                if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
                    // calculate scroll distance to move current mMaxEdge to padding at max edge
                    mMaxScroll = mMaxEdge - mPaddingMin - clientSize;
                } else  {
                    // calculate scroll distance to move max child center to key line
                    mMaxScroll = calculateScrollToKeyLine(maxChildViewCenter, keyLine);
                }
            }
            if (!isMaxUnknown && !isMinUnknown) {
                if (!mReversedFlow) {
                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
                        if (isPreferKeylineOverLowEdge()) {
                            // if we prefer key line, might align max child to key line for
                            // minScroll
                            mMinScroll = Math.min(mMinScroll,
                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
                        }
                        // don't over scroll max
                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
                        if (isPreferKeylineOverHighEdge()) {
                            // if we prefer key line, might align min child to key line for
                            // maxScroll
                            mMaxScroll = Math.max(mMaxScroll,
                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
                        }
                        // don't over scroll min
                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
                    }
                } else {
                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
                        if (isPreferKeylineOverLowEdge()) {
                            // if we prefer key line, might align min child to key line for
                            // maxScroll
                            mMaxScroll = Math.max(mMaxScroll,
                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
                        }
                        // don't over scroll min
                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
                        if (isPreferKeylineOverHighEdge()) {
                            // if we prefer key line, might align max child to key line for
                            // minScroll
                            mMinScroll = Math.min(mMinScroll,
                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
                        }
                        // don't over scroll max
                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
                    }
                }
            }
        }

        /**
         * Get scroll distance of align an item (depends on ALIGN_LOW_EDGE, ALIGN_HIGH_EDGE or the
         * item should be aligned to key line). The scroll distance will be capped by
         * {@link #getMinScroll()} and {@link #getMaxScroll()}.
         */
        public final int getScroll(int viewCenter) {
            final int size = getSize();
            final int keyLine = calculateKeyline();
            final boolean isMinUnknown = isMinUnknown();
            final boolean isMaxUnknown = isMaxUnknown();
            if (!isMinUnknown) {
                final int keyLineToMinEdge = keyLine - mPaddingMin;
                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
                     : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
                        && (viewCenter - mMinEdge <= keyLineToMinEdge)) {
                    // view center is before key line: align the min edge (first child) to padding.
                    int alignToMin = mMinEdge - mPaddingMin;
                    // Also we need make sure don't over scroll
                    if (!isMaxUnknown && alignToMin > mMaxScroll) {
                        alignToMin = mMaxScroll;
                    }
                    return alignToMin;
                }
            }
            if (!isMaxUnknown) {
                final int keyLineToMaxEdge = size - keyLine - mPaddingMax;
                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
                        && (mMaxEdge - viewCenter <= keyLineToMaxEdge)) {
                    // view center is after key line: align the max edge (last child) to padding.
                    int alignToMax = mMaxEdge - (size - mPaddingMax);
                    // Also we need make sure don't over scroll
                    if (!isMinUnknown && alignToMax < mMinScroll) {
                        alignToMax = mMinScroll;
                    }
                    return alignToMax;
                }
            }
            // else put view center at key line.
            return calculateScrollToKeyLine(viewCenter, keyLine);
        }

        public final void setReversedFlow(boolean reversedFlow) {
            mReversedFlow = reversedFlow;
        }

        @Override
        public String toString() {
            return " min:" + mMinEdge + " " + mMinScroll + " max:" + mMaxEdge + " " + mMaxScroll;
        }

    }

    private int mOrientation = HORIZONTAL;

    public final Axis vertical = new Axis("vertical");

    public final Axis horizontal = new Axis("horizontal");

    private Axis mMainAxis = horizontal;

    private Axis mSecondAxis = vertical;

    public final Axis mainAxis() {
        return mMainAxis;
    }

    public final Axis secondAxis() {
        return mSecondAxis;
    }

    public final void setOrientation(int orientation) {
        mOrientation = orientation;
        if (mOrientation == HORIZONTAL) {
            mMainAxis = horizontal;
            mSecondAxis = vertical;
        } else {
            mMainAxis = vertical;
            mSecondAxis = horizontal;
        }
    }

    public final int getOrientation() {
        return mOrientation;
    }

    public final void reset() {
        mainAxis().reset();
    }

    @Override
    public String toString() {
        return new StringBuffer().append("horizontal=")
                .append(horizontal.toString())
                .append("; vertical=")
                .append(vertical.toString())
                .toString();
    }

}