/* * 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 ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0 : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) { if (!mReversedFlow ? isPreferKeylineOverLowEdge() : isPreferKeylineOverHighEdge()) { // if we prefer key line, might align max child to key line for minScroll mMinScroll = Math.min(mMinScroll, calculateScrollToKeyLine(maxChildViewCenter, keyLine)); } else { // don't over scroll max mMaxScroll = Math.max(mMinScroll, mMaxScroll); } } else if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0 : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) { if (!mReversedFlow ? isPreferKeylineOverHighEdge() : isPreferKeylineOverLowEdge()) { // if we prefer key line, might align min child to key line for maxScroll mMaxScroll = Math.max(mMaxScroll, calculateScrollToKeyLine(minChildViewCenter, keyLine)); } else { // don't over scroll min mMinScroll = Math.min(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(); } }