blob: c6589d49c03e4d65692e247326bc9528d938b46d [file] [log] [blame]
/*
* 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 "horizontal=" + horizontal + "; vertical=" + vertical;
}
}