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