1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.recyclerview.widget; 18 19 import android.graphics.Rect; 20 import android.view.View; 21 22 /** 23 * Helper class for LayoutManagers to abstract measurements depending on the View's orientation. 24 * <p> 25 * It is developed to easily support vertical and horizontal orientations in a LayoutManager but 26 * can also be used to abstract calls around view bounds and child measurements with margins and 27 * decorations. 28 * 29 * @see #createHorizontalHelper(RecyclerView.LayoutManager) 30 * @see #createVerticalHelper(RecyclerView.LayoutManager) 31 */ 32 public abstract class OrientationHelper { 33 34 private static final int INVALID_SIZE = Integer.MIN_VALUE; 35 36 protected final RecyclerView.LayoutManager mLayoutManager; 37 38 public static final int HORIZONTAL = RecyclerView.HORIZONTAL; 39 40 public static final int VERTICAL = RecyclerView.VERTICAL; 41 42 private int mLastTotalSpace = INVALID_SIZE; 43 44 final Rect mTmpRect = new Rect(); 45 OrientationHelper(RecyclerView.LayoutManager layoutManager)46 private OrientationHelper(RecyclerView.LayoutManager layoutManager) { 47 mLayoutManager = layoutManager; 48 } 49 50 /** 51 * Returns the {@link RecyclerView.LayoutManager LayoutManager} that 52 * is associated with this OrientationHelper. 53 */ getLayoutManager()54 public RecyclerView.LayoutManager getLayoutManager() { 55 return mLayoutManager; 56 } 57 58 /** 59 * Call this method after onLayout method is complete if state is NOT pre-layout. 60 * This method records information like layout bounds that might be useful in the next layout 61 * calculations. 62 */ onLayoutComplete()63 public void onLayoutComplete() { 64 mLastTotalSpace = getTotalSpace(); 65 } 66 67 /** 68 * Returns the layout space change between the previous layout pass and current layout pass. 69 * <p> 70 * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's 71 * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler, 72 * RecyclerView.State)} method. 73 * 74 * @return The difference between the current total space and previous layout's total space. 75 * @see #onLayoutComplete() 76 */ getTotalSpaceChange()77 public int getTotalSpaceChange() { 78 return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace; 79 } 80 81 /** 82 * Returns the start of the view including its decoration and margin. 83 * <p> 84 * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left 85 * decoration and 3px left margin, returned value will be 15px. 86 * 87 * @param view The view element to check 88 * @return The first pixel of the element 89 * @see #getDecoratedEnd(android.view.View) 90 */ getDecoratedStart(View view)91 public abstract int getDecoratedStart(View view); 92 93 /** 94 * Returns the end of the view including its decoration and margin. 95 * <p> 96 * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right 97 * decoration and 3px right margin, returned value will be 205. 98 * 99 * @param view The view element to check 100 * @return The last pixel of the element 101 * @see #getDecoratedStart(android.view.View) 102 */ getDecoratedEnd(View view)103 public abstract int getDecoratedEnd(View view); 104 105 /** 106 * Returns the end of the View after its matrix transformations are applied to its layout 107 * position. 108 * <p> 109 * This method is useful when trying to detect the visible edge of a View. 110 * <p> 111 * It includes the decorations but does not include the margins. 112 * 113 * @param view The view whose transformed end will be returned 114 * @return The end of the View after its decor insets and transformation matrix is applied to 115 * its position 116 * 117 * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) 118 */ getTransformedEndWithDecoration(View view)119 public abstract int getTransformedEndWithDecoration(View view); 120 121 /** 122 * Returns the start of the View after its matrix transformations are applied to its layout 123 * position. 124 * <p> 125 * This method is useful when trying to detect the visible edge of a View. 126 * <p> 127 * It includes the decorations but does not include the margins. 128 * 129 * @param view The view whose transformed start will be returned 130 * @return The start of the View after its decor insets and transformation matrix is applied to 131 * its position 132 * 133 * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) 134 */ getTransformedStartWithDecoration(View view)135 public abstract int getTransformedStartWithDecoration(View view); 136 137 /** 138 * Returns the space occupied by this View in the current orientation including decorations and 139 * margins. 140 * 141 * @param view The view element to check 142 * @return Total space occupied by this view 143 * @see #getDecoratedMeasurementInOther(View) 144 */ getDecoratedMeasurement(View view)145 public abstract int getDecoratedMeasurement(View view); 146 147 /** 148 * Returns the space occupied by this View in the perpendicular orientation including 149 * decorations and margins. 150 * 151 * @param view The view element to check 152 * @return Total space occupied by this view in the perpendicular orientation to current one 153 * @see #getDecoratedMeasurement(View) 154 */ getDecoratedMeasurementInOther(View view)155 public abstract int getDecoratedMeasurementInOther(View view); 156 157 /** 158 * Returns the start position of the layout after the start padding is added. 159 * 160 * @return The very first pixel we can draw. 161 */ getStartAfterPadding()162 public abstract int getStartAfterPadding(); 163 164 /** 165 * Returns the end position of the layout after the end padding is removed. 166 * 167 * @return The end boundary for this layout. 168 */ getEndAfterPadding()169 public abstract int getEndAfterPadding(); 170 171 /** 172 * Returns the end position of the layout without taking padding into account. 173 * 174 * @return The end boundary for this layout without considering padding. 175 */ getEnd()176 public abstract int getEnd(); 177 178 /** 179 * Offsets all children's positions by the given amount. 180 * 181 * @param amount Value to add to each child's layout parameters 182 */ offsetChildren(int amount)183 public abstract void offsetChildren(int amount); 184 185 /** 186 * Returns the total space to layout. This number is the difference between 187 * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}. 188 * 189 * @return Total space to layout children 190 */ getTotalSpace()191 public abstract int getTotalSpace(); 192 193 /** 194 * Offsets the child in this orientation. 195 * 196 * @param view View to offset 197 * @param offset offset amount 198 */ offsetChild(View view, int offset)199 public abstract void offsetChild(View view, int offset); 200 201 /** 202 * Returns the padding at the end of the layout. For horizontal helper, this is the right 203 * padding and for vertical helper, this is the bottom padding. This method does not check 204 * whether the layout is RTL or not. 205 * 206 * @return The padding at the end of the layout. 207 */ getEndPadding()208 public abstract int getEndPadding(); 209 210 /** 211 * Returns the MeasureSpec mode for the current orientation from the LayoutManager. 212 * 213 * @return The current measure spec mode. 214 * 215 * @see View.MeasureSpec 216 * @see RecyclerView.LayoutManager#getWidthMode() 217 * @see RecyclerView.LayoutManager#getHeightMode() 218 */ getMode()219 public abstract int getMode(); 220 221 /** 222 * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager. 223 * 224 * @return The current measure spec mode. 225 * 226 * @see View.MeasureSpec 227 * @see RecyclerView.LayoutManager#getWidthMode() 228 * @see RecyclerView.LayoutManager#getHeightMode() 229 */ getModeInOther()230 public abstract int getModeInOther(); 231 232 /** 233 * Creates an OrientationHelper for the given LayoutManager and orientation. 234 * 235 * @param layoutManager LayoutManager to attach to 236 * @param orientation Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL} 237 * @return A new OrientationHelper 238 */ createOrientationHelper( RecyclerView.LayoutManager layoutManager, @RecyclerView.Orientation int orientation)239 public static OrientationHelper createOrientationHelper( 240 RecyclerView.LayoutManager layoutManager, @RecyclerView.Orientation int orientation) { 241 switch (orientation) { 242 case HORIZONTAL: 243 return createHorizontalHelper(layoutManager); 244 case VERTICAL: 245 return createVerticalHelper(layoutManager); 246 } 247 throw new IllegalArgumentException("invalid orientation"); 248 } 249 250 /** 251 * Creates a horizontal OrientationHelper for the given LayoutManager. 252 * 253 * @param layoutManager The LayoutManager to attach to. 254 * @return A new OrientationHelper 255 */ createHorizontalHelper( RecyclerView.LayoutManager layoutManager)256 public static OrientationHelper createHorizontalHelper( 257 RecyclerView.LayoutManager layoutManager) { 258 return new OrientationHelper(layoutManager) { 259 @Override 260 public int getEndAfterPadding() { 261 return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight(); 262 } 263 264 @Override 265 public int getEnd() { 266 return mLayoutManager.getWidth(); 267 } 268 269 @Override 270 public void offsetChildren(int amount) { 271 mLayoutManager.offsetChildrenHorizontal(amount); 272 } 273 274 @Override 275 public int getStartAfterPadding() { 276 return mLayoutManager.getPaddingLeft(); 277 } 278 279 @Override 280 public int getDecoratedMeasurement(View view) { 281 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 282 view.getLayoutParams(); 283 return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin 284 + params.rightMargin; 285 } 286 287 @Override 288 public int getDecoratedMeasurementInOther(View view) { 289 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 290 view.getLayoutParams(); 291 return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin 292 + params.bottomMargin; 293 } 294 295 @Override 296 public int getDecoratedEnd(View view) { 297 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 298 view.getLayoutParams(); 299 return mLayoutManager.getDecoratedRight(view) + params.rightMargin; 300 } 301 302 @Override 303 public int getDecoratedStart(View view) { 304 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 305 view.getLayoutParams(); 306 return mLayoutManager.getDecoratedLeft(view) - params.leftMargin; 307 } 308 309 @Override 310 public int getTransformedEndWithDecoration(View view) { 311 mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); 312 return mTmpRect.right; 313 } 314 315 @Override 316 public int getTransformedStartWithDecoration(View view) { 317 mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); 318 return mTmpRect.left; 319 } 320 321 @Override 322 public int getTotalSpace() { 323 return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft() 324 - mLayoutManager.getPaddingRight(); 325 } 326 327 @Override 328 public void offsetChild(View view, int offset) { 329 view.offsetLeftAndRight(offset); 330 } 331 332 @Override 333 public int getEndPadding() { 334 return mLayoutManager.getPaddingRight(); 335 } 336 337 @Override 338 public int getMode() { 339 return mLayoutManager.getWidthMode(); 340 } 341 342 @Override 343 public int getModeInOther() { 344 return mLayoutManager.getHeightMode(); 345 } 346 }; 347 } 348 349 /** 350 * Creates a vertical OrientationHelper for the given LayoutManager. 351 * 352 * @param layoutManager The LayoutManager to attach to. 353 * @return A new OrientationHelper 354 */ 355 public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) { 356 return new OrientationHelper(layoutManager) { 357 @Override 358 public int getEndAfterPadding() { 359 return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom(); 360 } 361 362 @Override 363 public int getEnd() { 364 return mLayoutManager.getHeight(); 365 } 366 367 @Override 368 public void offsetChildren(int amount) { 369 mLayoutManager.offsetChildrenVertical(amount); 370 } 371 372 @Override 373 public int getStartAfterPadding() { 374 return mLayoutManager.getPaddingTop(); 375 } 376 377 @Override 378 public int getDecoratedMeasurement(View view) { 379 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 380 view.getLayoutParams(); 381 return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin 382 + params.bottomMargin; 383 } 384 385 @Override 386 public int getDecoratedMeasurementInOther(View view) { 387 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 388 view.getLayoutParams(); 389 return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin 390 + params.rightMargin; 391 } 392 393 @Override 394 public int getDecoratedEnd(View view) { 395 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 396 view.getLayoutParams(); 397 return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin; 398 } 399 400 @Override 401 public int getDecoratedStart(View view) { 402 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) 403 view.getLayoutParams(); 404 return mLayoutManager.getDecoratedTop(view) - params.topMargin; 405 } 406 407 @Override 408 public int getTransformedEndWithDecoration(View view) { 409 mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); 410 return mTmpRect.bottom; 411 } 412 413 @Override 414 public int getTransformedStartWithDecoration(View view) { 415 mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); 416 return mTmpRect.top; 417 } 418 419 @Override 420 public int getTotalSpace() { 421 return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() 422 - mLayoutManager.getPaddingBottom(); 423 } 424 425 @Override 426 public void offsetChild(View view, int offset) { 427 view.offsetTopAndBottom(offset); 428 } 429 430 @Override 431 public int getEndPadding() { 432 return mLayoutManager.getPaddingBottom(); 433 } 434 435 @Override 436 public int getMode() { 437 return mLayoutManager.getHeightMode(); 438 } 439 440 @Override 441 public int getModeInOther() { 442 return mLayoutManager.getWidthMode(); 443 } 444 }; 445 } 446 } 447