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_LOW_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_BOTH_EDGE; 20 import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED; 21 22 import static android.support.v7.widget.RecyclerView.HORIZONTAL; 23 24 import android.view.View; 25 26 /** 27 * Maintains Window Alignment information of two axis. 28 */ 29 class WindowAlignment { 30 31 /** 32 * Maintains alignment information in one direction. 33 */ 34 public static class Axis { 35 /** 36 * mScrollCenter is used to calculate dynamic transformation based on how far a view 37 * is from the mScrollCenter. For example, the views with center close to mScrollCenter 38 * will be scaled up. 39 */ 40 private float mScrollCenter; 41 /** 42 * Right or bottom edge of last child. 43 */ 44 private int mMaxEdge; 45 /** 46 * Left or top edge of first child, typically should be zero. 47 */ 48 private int mMinEdge; 49 /** 50 * Max Scroll value 51 */ 52 private int mMaxScroll; 53 /** 54 * Min Scroll value 55 */ 56 private int mMinScroll; 57 58 private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE; 59 60 private int mWindowAlignmentOffset = 0; 61 62 private float mWindowAlignmentOffsetPercent = 50f; 63 64 private int mSize; 65 66 private int mPaddingLow; 67 68 private int mPaddingHigh; 69 70 private boolean mReversedFlow; 71 72 private String mName; // for debugging 73 Axis(String name)74 public Axis(String name) { 75 reset(); 76 mName = name; 77 } 78 getWindowAlignment()79 final public int getWindowAlignment() { 80 return mWindowAlignment; 81 } 82 setWindowAlignment(int windowAlignment)83 final public void setWindowAlignment(int windowAlignment) { 84 mWindowAlignment = windowAlignment; 85 } 86 getWindowAlignmentOffset()87 final public int getWindowAlignmentOffset() { 88 return mWindowAlignmentOffset; 89 } 90 setWindowAlignmentOffset(int offset)91 final public void setWindowAlignmentOffset(int offset) { 92 mWindowAlignmentOffset = offset; 93 } 94 setWindowAlignmentOffsetPercent(float percent)95 final public void setWindowAlignmentOffsetPercent(float percent) { 96 if ((percent < 0 || percent > 100) 97 && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) { 98 throw new IllegalArgumentException(); 99 } 100 mWindowAlignmentOffsetPercent = percent; 101 } 102 getWindowAlignmentOffsetPercent()103 final public float getWindowAlignmentOffsetPercent() { 104 return mWindowAlignmentOffsetPercent; 105 } 106 getScrollCenter()107 final public int getScrollCenter() { 108 return (int) mScrollCenter; 109 } 110 111 /** set minEdge, Integer.MIN_VALUE means unknown*/ setMinEdge(int minEdge)112 final public void setMinEdge(int minEdge) { 113 mMinEdge = minEdge; 114 } 115 getMinEdge()116 final public int getMinEdge() { 117 return mMinEdge; 118 } 119 120 /** set minScroll, Integer.MIN_VALUE means unknown*/ setMinScroll(int minScroll)121 final public void setMinScroll(int minScroll) { 122 mMinScroll = minScroll; 123 } 124 getMinScroll()125 final public int getMinScroll() { 126 return mMinScroll; 127 } 128 invalidateScrollMin()129 final public void invalidateScrollMin() { 130 mMinEdge = Integer.MIN_VALUE; 131 mMinScroll = Integer.MIN_VALUE; 132 } 133 134 /** update max edge, Integer.MAX_VALUE means unknown*/ setMaxEdge(int maxEdge)135 final public void setMaxEdge(int maxEdge) { 136 mMaxEdge = maxEdge; 137 } 138 getMaxEdge()139 final public int getMaxEdge() { 140 return mMaxEdge; 141 } 142 143 /** update max scroll, Integer.MAX_VALUE means unknown*/ setMaxScroll(int maxScroll)144 final public void setMaxScroll(int maxScroll) { 145 mMaxScroll = maxScroll; 146 } 147 getMaxScroll()148 final public int getMaxScroll() { 149 return mMaxScroll; 150 } 151 invalidateScrollMax()152 final public void invalidateScrollMax() { 153 mMaxEdge = Integer.MAX_VALUE; 154 mMaxScroll = Integer.MAX_VALUE; 155 } 156 updateScrollCenter(float scrollTarget)157 final public float updateScrollCenter(float scrollTarget) { 158 mScrollCenter = scrollTarget; 159 return scrollTarget; 160 } 161 reset()162 private void reset() { 163 mScrollCenter = Integer.MIN_VALUE; 164 mMinEdge = Integer.MIN_VALUE; 165 mMaxEdge = Integer.MAX_VALUE; 166 } 167 isMinUnknown()168 final public boolean isMinUnknown() { 169 return mMinEdge == Integer.MIN_VALUE; 170 } 171 isMaxUnknown()172 final public boolean isMaxUnknown() { 173 return mMaxEdge == Integer.MAX_VALUE; 174 } 175 setSize(int size)176 final public void setSize(int size) { 177 mSize = size; 178 } 179 getSize()180 final public int getSize() { 181 return mSize; 182 } 183 setPadding(int paddingLow, int paddingHigh)184 final public void setPadding(int paddingLow, int paddingHigh) { 185 mPaddingLow = paddingLow; 186 mPaddingHigh = paddingHigh; 187 } 188 getPaddingLow()189 final public int getPaddingLow() { 190 return mPaddingLow; 191 } 192 getPaddingHigh()193 final public int getPaddingHigh() { 194 return mPaddingHigh; 195 } 196 getClientSize()197 final public int getClientSize() { 198 return mSize - mPaddingLow - mPaddingHigh; 199 } 200 getSystemScrollPos(boolean isAtMin, boolean isAtMax)201 final public int getSystemScrollPos(boolean isAtMin, boolean isAtMax) { 202 return getSystemScrollPos((int) mScrollCenter, isAtMin, isAtMax); 203 } 204 getSystemScrollPos(int scrollCenter, boolean isAtMin, boolean isAtMax)205 final public int getSystemScrollPos(int scrollCenter, boolean isAtMin, boolean isAtMax) { 206 int middlePosition; 207 if (!mReversedFlow) { 208 if (mWindowAlignmentOffset >= 0) { 209 middlePosition = mWindowAlignmentOffset - mPaddingLow; 210 } else { 211 middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow; 212 } 213 if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) { 214 middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100); 215 } 216 } else { 217 if (mWindowAlignmentOffset >= 0) { 218 middlePosition = mSize - mWindowAlignmentOffset - mPaddingLow; 219 } else { 220 middlePosition = - mWindowAlignmentOffset - mPaddingLow; 221 } 222 if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) { 223 middlePosition -= (int) (mSize * mWindowAlignmentOffsetPercent / 100); 224 } 225 } 226 int clientSize = getClientSize(); 227 int afterMiddlePosition = clientSize - middlePosition; 228 boolean isMinUnknown = isMinUnknown(); 229 boolean isMaxUnknown = isMaxUnknown(); 230 if (!isMinUnknown && !isMaxUnknown && 231 (mWindowAlignment & WINDOW_ALIGN_BOTH_EDGE) == WINDOW_ALIGN_BOTH_EDGE) { 232 if (mMaxEdge - mMinEdge <= clientSize) { 233 // total children size is less than view port and we want to align 234 // both edge: align first child to start edge of view port 235 return mReversedFlow ? mMaxEdge - mPaddingLow - clientSize 236 : mMinEdge - mPaddingLow; 237 } 238 } 239 if (!isMinUnknown) { 240 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0 241 : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) 242 && (isAtMin || scrollCenter - mMinEdge <= middlePosition)) { 243 // scroll center is within half of view port size: align the start edge 244 // of first child to the start edge of view port 245 return mMinEdge - mPaddingLow; 246 } 247 } 248 if (!isMaxUnknown) { 249 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0 250 : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) 251 && (isAtMax || mMaxEdge - scrollCenter <= afterMiddlePosition)) { 252 // scroll center is very close to the end edge of view port : align the 253 // end edge of last children (plus expanded size) to view port's end 254 return mMaxEdge - mPaddingLow - clientSize; 255 } 256 } 257 // else put scroll center in middle of view port 258 return scrollCenter - middlePosition - mPaddingLow; 259 } 260 setReversedFlow(boolean reversedFlow)261 final public void setReversedFlow(boolean reversedFlow) { 262 mReversedFlow = reversedFlow; 263 } 264 265 @Override toString()266 public String toString() { 267 return "center: " + mScrollCenter + " min:" + mMinEdge + 268 " max:" + mMaxEdge; 269 } 270 271 } 272 273 private int mOrientation = HORIZONTAL; 274 275 final public Axis vertical = new Axis("vertical"); 276 277 final public Axis horizontal = new Axis("horizontal"); 278 279 private Axis mMainAxis = horizontal; 280 281 private Axis mSecondAxis = vertical; 282 mainAxis()283 final public Axis mainAxis() { 284 return mMainAxis; 285 } 286 secondAxis()287 final public Axis secondAxis() { 288 return mSecondAxis; 289 } 290 setOrientation(int orientation)291 final public void setOrientation(int orientation) { 292 mOrientation = orientation; 293 if (mOrientation == HORIZONTAL) { 294 mMainAxis = horizontal; 295 mSecondAxis = vertical; 296 } else { 297 mMainAxis = vertical; 298 mSecondAxis = horizontal; 299 } 300 } 301 getOrientation()302 final public int getOrientation() { 303 return mOrientation; 304 } 305 reset()306 final public void reset() { 307 mainAxis().reset(); 308 } 309 310 @Override toString()311 public String toString() { 312 return new StringBuffer().append("horizontal=") 313 .append(horizontal.toString()) 314 .append("; vertical=") 315 .append(vertical.toString()) 316 .toString(); 317 } 318 319 } 320