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