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 ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0 258 : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) { 259 if (!mReversedFlow ? isPreferKeylineOverLowEdge() 260 : isPreferKeylineOverHighEdge()) { 261 // if we prefer key line, might align max child to key line for minScroll 262 mMinScroll = Math.min(mMinScroll, 263 calculateScrollToKeyLine(maxChildViewCenter, keyLine)); 264 } else { 265 // don't over scroll max 266 mMaxScroll = Math.max(mMinScroll, mMaxScroll); 267 } 268 } else if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0 269 : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) { 270 if (!mReversedFlow ? isPreferKeylineOverHighEdge() 271 : isPreferKeylineOverLowEdge()) { 272 // if we prefer key line, might align min child to key line for maxScroll 273 mMaxScroll = Math.max(mMaxScroll, 274 calculateScrollToKeyLine(minChildViewCenter, keyLine)); 275 } else { 276 // don't over scroll min 277 mMinScroll = Math.min(mMinScroll, mMaxScroll); 278 } 279 } 280 } 281 } 282 283 /** 284 * Get scroll distance of align an item (depends on ALIGN_LOW_EDGE, ALIGN_HIGH_EDGE or the 285 * item should be aligned to key line). The scroll distance will be capped by 286 * {@link #getMinScroll()} and {@link #getMaxScroll()}. 287 */ getScroll(int viewCenter)288 public final int getScroll(int viewCenter) { 289 final int size = getSize(); 290 final int keyLine = calculateKeyline(); 291 final boolean isMinUnknown = isMinUnknown(); 292 final boolean isMaxUnknown = isMaxUnknown(); 293 if (!isMinUnknown) { 294 final int keyLineToMinEdge = keyLine - mPaddingMin; 295 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0 296 : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) 297 && (viewCenter - mMinEdge <= keyLineToMinEdge)) { 298 // view center is before key line: align the min edge (first child) to padding. 299 int alignToMin = mMinEdge - mPaddingMin; 300 // Also we need make sure don't over scroll 301 if (!isMaxUnknown && alignToMin > mMaxScroll) { 302 alignToMin = mMaxScroll; 303 } 304 return alignToMin; 305 } 306 } 307 if (!isMaxUnknown) { 308 final int keyLineToMaxEdge = size - keyLine - mPaddingMax; 309 if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0 310 : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) 311 && (mMaxEdge - viewCenter <= keyLineToMaxEdge)) { 312 // view center is after key line: align the max edge (last child) to padding. 313 int alignToMax = mMaxEdge - (size - mPaddingMax); 314 // Also we need make sure don't over scroll 315 if (!isMinUnknown && alignToMax < mMinScroll) { 316 alignToMax = mMinScroll; 317 } 318 return alignToMax; 319 } 320 } 321 // else put view center at key line. 322 return calculateScrollToKeyLine(viewCenter, keyLine); 323 } 324 setReversedFlow(boolean reversedFlow)325 public final void setReversedFlow(boolean reversedFlow) { 326 mReversedFlow = reversedFlow; 327 } 328 329 @Override toString()330 public String toString() { 331 return " min:" + mMinEdge + " " + mMinScroll + " max:" + mMaxEdge + " " + mMaxScroll; 332 } 333 334 } 335 336 private int mOrientation = HORIZONTAL; 337 338 public final Axis vertical = new Axis("vertical"); 339 340 public final Axis horizontal = new Axis("horizontal"); 341 342 private Axis mMainAxis = horizontal; 343 344 private Axis mSecondAxis = vertical; 345 mainAxis()346 public final Axis mainAxis() { 347 return mMainAxis; 348 } 349 secondAxis()350 public final Axis secondAxis() { 351 return mSecondAxis; 352 } 353 setOrientation(int orientation)354 public final void setOrientation(int orientation) { 355 mOrientation = orientation; 356 if (mOrientation == HORIZONTAL) { 357 mMainAxis = horizontal; 358 mSecondAxis = vertical; 359 } else { 360 mMainAxis = vertical; 361 mSecondAxis = horizontal; 362 } 363 } 364 getOrientation()365 public final int getOrientation() { 366 return mOrientation; 367 } 368 reset()369 public final void reset() { 370 mainAxis().reset(); 371 } 372 373 @Override toString()374 public String toString() { 375 return new StringBuffer().append("horizontal=") 376 .append(horizontal.toString()) 377 .append("; vertical=") 378 .append(vertical.toString()) 379 .toString(); 380 } 381 382 } 383