1 /* 2 * Copyright 2021 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 package androidx.leanback.widget; 17 18 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; 19 20 import android.annotation.SuppressLint; 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.graphics.Rect; 24 import android.util.AttributeSet; 25 import android.view.Gravity; 26 import android.view.KeyEvent; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.animation.Interpolator; 30 31 import androidx.annotation.RestrictTo; 32 import androidx.recyclerview.widget.RecyclerView; 33 import androidx.recyclerview.widget.SimpleItemAnimator; 34 35 import org.jspecify.annotations.NonNull; 36 import org.jspecify.annotations.Nullable; 37 38 /** 39 * An abstract base class for vertically and horizontally scrolling lists. The items come 40 * from the {@link RecyclerView.Adapter} associated with this view. 41 * Do not directly use this class, use {@link VerticalGridView} and {@link HorizontalGridView}. 42 * The class is not intended to be subclassed other than {@link VerticalGridView} and 43 * {@link HorizontalGridView}. 44 */ 45 public abstract class BaseGridView extends RecyclerView { 46 47 /** 48 * Always keep focused item at a aligned position. Developer can use 49 * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned. 50 * In this mode, the last focused position will be remembered and restored when focus 51 * is back to the view. 52 * 53 */ 54 @RestrictTo(LIBRARY_GROUP_PREFIX) 55 public static final int FOCUS_SCROLL_ALIGNED = 0; 56 57 /** 58 * Scroll to make the focused item inside client area. 59 * 60 */ 61 @RestrictTo(LIBRARY_GROUP_PREFIX) 62 public static final int FOCUS_SCROLL_ITEM = 1; 63 64 /** 65 * Scroll a page of items when focusing to item outside the client area. 66 * The page size matches the client area size of RecyclerView. 67 * 68 */ 69 @RestrictTo(LIBRARY_GROUP_PREFIX) 70 public static final int FOCUS_SCROLL_PAGE = 2; 71 72 /** 73 * The first item is aligned with the low edge of the viewport. When 74 * navigating away from the first item, the focus item is aligned to a key line location. 75 * <p> 76 * For HorizontalGridView, low edge refers to getPaddingLeft() when RTL is false or 77 * getWidth() - getPaddingRight() when RTL is true. 78 * For VerticalGridView, low edge refers to getPaddingTop(). 79 * <p> 80 * The key line location is calculated by "windowAlignOffset" and 81 * "windowAlignOffsetPercent"; if neither of these two is defined, the 82 * default value is 1/2 of the size. 83 * <p> 84 * Note if there are very few items between low edge and key line, use 85 * {@link #setWindowAlignmentPreferKeyLineOverLowEdge(boolean)} to control whether you prefer 86 * to align the items to key line or low edge. Default is preferring low edge. 87 */ 88 public static final int WINDOW_ALIGN_LOW_EDGE = 1; 89 90 /** 91 * The last item is aligned with the high edge of the viewport when 92 * navigating to the end of list. When navigating away from the end, the 93 * focus item is aligned to a key line location. 94 * <p> 95 * For HorizontalGridView, high edge refers to getWidth() - getPaddingRight() when RTL is false 96 * or getPaddingLeft() when RTL is true. 97 * For VerticalGridView, high edge refers to getHeight() - getPaddingBottom(). 98 * <p> 99 * The key line location is calculated by "windowAlignOffset" and 100 * "windowAlignOffsetPercent"; if neither of these two is defined, the 101 * default value is 1/2 of the size. 102 * <p> 103 * Note if there are very few items between high edge and key line, use 104 * {@link #setWindowAlignmentPreferKeyLineOverHighEdge(boolean)} to control whether you prefer 105 * to align the items to key line or high edge. Default is preferring key line. 106 */ 107 public static final int WINDOW_ALIGN_HIGH_EDGE = 1 << 1; 108 109 /** 110 * The first item and last item are aligned with the two edges of the 111 * viewport. When navigating in the middle of list, the focus maintains a 112 * key line location. 113 * <p> 114 * The key line location is calculated by "windowAlignOffset" and 115 * "windowAlignOffsetPercent"; if neither of these two is defined, the 116 * default value is 1/2 of the size. 117 */ 118 public static final int WINDOW_ALIGN_BOTH_EDGE = 119 WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE; 120 121 /** 122 * The focused item always stays in a key line location. 123 * <p> 124 * The key line location is calculated by "windowAlignOffset" and 125 * "windowAlignOffsetPercent"; if neither of these two is defined, the 126 * default value is 1/2 of the size. 127 */ 128 public static final int WINDOW_ALIGN_NO_EDGE = 0; 129 130 /** 131 * Value indicates that percent is not used. 132 */ 133 public static final float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1; 134 135 /** 136 * Value indicates that percent is not used. 137 */ 138 public static final float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = 139 ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED; 140 141 /** 142 * Dont save states of any child views. 143 */ 144 public static final int SAVE_NO_CHILD = 0; 145 146 /** 147 * Only save on screen child views, the states are lost when they become off screen. 148 */ 149 public static final int SAVE_ON_SCREEN_CHILD = 1; 150 151 /** 152 * Save on screen views plus save off screen child views states up to 153 * {@link #getSaveChildrenLimitNumber()}. 154 */ 155 public static final int SAVE_LIMITED_CHILD = 2; 156 157 /** 158 * Save on screen views plus save off screen child views without any limitation. 159 * This might cause out of memory, only use it when you are dealing with limited data. 160 */ 161 public static final int SAVE_ALL_CHILD = 3; 162 163 private static final int PFLAG_RETAIN_FOCUS_FOR_CHILD = 1; 164 165 /** 166 * Defines behavior of duration and interpolator for smoothScrollBy(). 167 */ 168 public interface SmoothScrollByBehavior { 169 /** 170 * Defines duration in milliseconds of smoothScrollBy(). 171 * 172 * @param dx x distance in pixels. 173 * @param dy y distance in pixels. 174 * @return Duration in milliseconds or UNDEFINED_DURATION for default value. 175 */ configSmoothScrollByDuration(int dx, int dy)176 int configSmoothScrollByDuration(int dx, int dy); 177 178 /** 179 * Defines interpolator of smoothScrollBy(). 180 * 181 * @param dx x distance in pixels. 182 * @param dy y distance in pixels. 183 * @return Interpolator to be used or null for default interpolator. 184 */ configSmoothScrollByInterpolator(int dx, int dy)185 @Nullable Interpolator configSmoothScrollByInterpolator(int dx, int dy); 186 } 187 188 /** 189 * Listener for intercepting touch dispatch events. 190 */ 191 public interface OnTouchInterceptListener { 192 /** 193 * Returns true if the touch dispatch event should be consumed. 194 */ onInterceptTouchEvent(@onNull MotionEvent event)195 boolean onInterceptTouchEvent(@NonNull MotionEvent event); 196 } 197 198 /** 199 * Listener for intercepting generic motion dispatch events. 200 */ 201 public interface OnMotionInterceptListener { 202 /** 203 * Returns true if the touch dispatch event should be consumed. 204 */ onInterceptMotionEvent(@onNull MotionEvent event)205 boolean onInterceptMotionEvent(@NonNull MotionEvent event); 206 } 207 208 /** 209 * Listener for intercepting key dispatch events. 210 */ 211 public interface OnKeyInterceptListener { 212 /** 213 * Returns true if the key dispatch event should be consumed. 214 */ onInterceptKeyEvent(@onNull KeyEvent event)215 boolean onInterceptKeyEvent(@NonNull KeyEvent event); 216 } 217 218 /** 219 * Listener for intercepting unhandled key events. 220 */ 221 public interface OnUnhandledKeyListener { 222 /** 223 * Returns true if the key event should be consumed. 224 */ onUnhandledKey(@onNull KeyEvent event)225 boolean onUnhandledKey(@NonNull KeyEvent event); 226 } 227 228 /** 229 * Interface for receiving notification when BaseGridView has completed a full layout 230 * calculation. 231 */ 232 public interface OnLayoutCompletedListener { 233 234 /** 235 * Called after a full layout calculation is finished. 236 * 237 * @param state Transient state of RecyclerView 238 */ onLayoutCompleted(RecyclerView.@onNull State state)239 void onLayoutCompleted(RecyclerView.@NonNull State state); 240 } 241 242 GridLayoutManager mLayoutManager; 243 244 private SmoothScrollByBehavior mSmoothScrollByBehavior; 245 246 /** 247 * Animate layout changes from a child resizing or adding/removing a child. 248 */ 249 private boolean mAnimateChildLayout = true; 250 251 private boolean mHasOverlappingRendering = true; 252 253 private RecyclerView.ItemAnimator mSavedItemAnimator; 254 255 private OnTouchInterceptListener mOnTouchInterceptListener; 256 private OnMotionInterceptListener mOnMotionInterceptListener; 257 private OnKeyInterceptListener mOnKeyInterceptListener; 258 private OnUnhandledKeyListener mOnUnhandledKeyListener; 259 260 /** 261 * Number of items to prefetch when first coming on screen with new data. 262 */ 263 int mInitialPrefetchItemCount = 4; 264 265 private int mPrivateFlag; 266 BaseGridView(Context context, AttributeSet attrs, int defStyle)267 BaseGridView(Context context, AttributeSet attrs, int defStyle) { 268 super(context, attrs, defStyle); 269 mLayoutManager = new GridLayoutManager(this); 270 setLayoutManager(mLayoutManager); 271 // leanback LayoutManager already restores focus inside onLayoutChildren(). 272 setPreserveFocusAfterLayout(false); 273 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 274 setHasFixedSize(true); 275 setChildrenDrawingOrderEnabled(true); 276 setWillNotDraw(true); 277 setOverScrollMode(View.OVER_SCROLL_NEVER); 278 // Disable change animation by default on leanback. 279 // Change animation will create a new view and cause undesired 280 // focus animation between the old view and new view. 281 ((SimpleItemAnimator) getItemAnimator()).setSupportsChangeAnimations(false); 282 super.addRecyclerListener(new RecyclerView.RecyclerListener() { 283 @Override 284 public void onViewRecycled(RecyclerView.@NonNull ViewHolder holder) { 285 mLayoutManager.onChildRecycled(holder); 286 } 287 }); 288 } 289 290 @SuppressLint("CustomViewStyleable") initBaseGridViewAttributes(Context context, AttributeSet attrs)291 void initBaseGridViewAttributes(Context context, AttributeSet attrs) { 292 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView); 293 boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false); 294 boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false); 295 mLayoutManager.setFocusOutAllowed(throughFront, throughEnd); 296 boolean throughSideStart = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideStart, true); 297 boolean throughSideEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideEnd, true); 298 mLayoutManager.setFocusOutSideAllowed(throughSideStart, throughSideEnd); 299 mLayoutManager.setVerticalSpacing( 300 a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_verticalSpacing, 301 a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0))); 302 mLayoutManager.setHorizontalSpacing( 303 a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_horizontalSpacing, 304 a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0))); 305 if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) { 306 setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY)); 307 } 308 a.recycle(); 309 } 310 311 /** 312 * Sets the strategy used to scroll in response to item focus changing: 313 * <ul> 314 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 315 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 316 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 317 * </ul> 318 * 319 */ 320 @RestrictTo(LIBRARY_GROUP_PREFIX) setFocusScrollStrategy(int scrollStrategy)321 public void setFocusScrollStrategy(int scrollStrategy) { 322 if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM 323 && scrollStrategy != FOCUS_SCROLL_PAGE) { 324 throw new IllegalArgumentException("Invalid scrollStrategy"); 325 } 326 mLayoutManager.setFocusScrollStrategy(scrollStrategy); 327 requestLayout(); 328 } 329 330 /** 331 * Returns the strategy used to scroll in response to item focus changing. 332 * <ul> 333 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 334 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 335 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 336 * </ul> 337 * 338 */ 339 @RestrictTo(LIBRARY_GROUP_PREFIX) getFocusScrollStrategy()340 public int getFocusScrollStrategy() { 341 return mLayoutManager.getFocusScrollStrategy(); 342 } 343 344 /** 345 * Sets the method for focused item alignment in the view. 346 * 347 * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE}, 348 * {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or 349 * {@link #WINDOW_ALIGN_NO_EDGE}. 350 */ setWindowAlignment(int windowAlignment)351 public void setWindowAlignment(int windowAlignment) { 352 mLayoutManager.setWindowAlignment(windowAlignment); 353 requestLayout(); 354 } 355 356 /** 357 * Returns the method for focused item alignment in the view. 358 * 359 * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE}, 360 * {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}. 361 */ getWindowAlignment()362 public int getWindowAlignment() { 363 return mLayoutManager.getWindowAlignment(); 364 } 365 366 /** 367 * Sets whether prefer key line over low edge when {@link #WINDOW_ALIGN_LOW_EDGE} is used. 368 * When true, if there are very few items between low edge and key line, align items to key 369 * line instead of align items to low edge. 370 * Default value is false (aka prefer align to low edge). 371 * 372 * @param preferKeyLineOverLowEdge True to prefer key line over low edge, false otherwise. 373 */ setWindowAlignmentPreferKeyLineOverLowEdge(boolean preferKeyLineOverLowEdge)374 public void setWindowAlignmentPreferKeyLineOverLowEdge(boolean preferKeyLineOverLowEdge) { 375 mLayoutManager.mWindowAlignment.mainAxis() 376 .setPreferKeylineOverLowEdge(preferKeyLineOverLowEdge); 377 requestLayout(); 378 } 379 380 381 /** 382 * Returns whether prefer key line over high edge when {@link #WINDOW_ALIGN_HIGH_EDGE} is used. 383 * When true, if there are very few items between high edge and key line, align items to key 384 * line instead of align items to high edge. 385 * Default value is true (aka prefer align to key line). 386 * 387 * @param preferKeyLineOverHighEdge True to prefer key line over high edge, false otherwise. 388 */ setWindowAlignmentPreferKeyLineOverHighEdge(boolean preferKeyLineOverHighEdge)389 public void setWindowAlignmentPreferKeyLineOverHighEdge(boolean preferKeyLineOverHighEdge) { 390 mLayoutManager.mWindowAlignment.mainAxis() 391 .setPreferKeylineOverHighEdge(preferKeyLineOverHighEdge); 392 requestLayout(); 393 } 394 395 /** 396 * Returns whether prefer key line over low edge when {@link #WINDOW_ALIGN_LOW_EDGE} is used. 397 * When true, if there are very few items between low edge and key line, align items to key 398 * line instead of align items to low edge. 399 * Default value is false (aka prefer align to low edge). 400 * 401 * @return True to prefer key line over low edge, false otherwise. 402 */ isWindowAlignmentPreferKeyLineOverLowEdge()403 public boolean isWindowAlignmentPreferKeyLineOverLowEdge() { 404 return mLayoutManager.mWindowAlignment.mainAxis().isPreferKeylineOverLowEdge(); 405 } 406 407 408 /** 409 * Returns whether prefer key line over high edge when {@link #WINDOW_ALIGN_HIGH_EDGE} is used. 410 * When true, if there are very few items between high edge and key line, align items to key 411 * line instead of align items to high edge. 412 * Default value is true (aka prefer align to key line). 413 * 414 * @return True to prefer key line over high edge, false otherwise. 415 */ isWindowAlignmentPreferKeyLineOverHighEdge()416 public boolean isWindowAlignmentPreferKeyLineOverHighEdge() { 417 return mLayoutManager.mWindowAlignment.mainAxis().isPreferKeylineOverHighEdge(); 418 } 419 420 421 /** 422 * Sets the offset in pixels for window alignment key line. 423 * 424 * @param offset The number of pixels to offset. If the offset is positive, 425 * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE}); 426 * if the offset is negative, the absolute value is distance from high 427 * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}). 428 * Default value is 0. 429 */ setWindowAlignmentOffset(int offset)430 public void setWindowAlignmentOffset(int offset) { 431 mLayoutManager.setWindowAlignmentOffset(offset); 432 requestLayout(); 433 } 434 435 /** 436 * Returns the offset in pixels for window alignment key line. 437 * 438 * @return The number of pixels to offset. If the offset is positive, 439 * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE}); 440 * if the offset is negative, the absolute value is distance from high 441 * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}). 442 * Default value is 0. 443 */ getWindowAlignmentOffset()444 public int getWindowAlignmentOffset() { 445 return mLayoutManager.getWindowAlignmentOffset(); 446 } 447 448 /** 449 * Sets the offset percent for window alignment key line in addition to {@link 450 * #getWindowAlignmentOffset()}. 451 * 452 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 453 * width from low edge. Use 454 * {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 455 * Default value is 50. 456 */ setWindowAlignmentOffsetPercent(float offsetPercent)457 public void setWindowAlignmentOffsetPercent(float offsetPercent) { 458 mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent); 459 requestLayout(); 460 } 461 462 /** 463 * Returns the offset percent for window alignment key line in addition to 464 * {@link #getWindowAlignmentOffset()}. 465 * 466 * @return Percentage to offset. E.g., 40 means 40% of the width from the 467 * low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if 468 * disabled. Default value is 50. 469 */ getWindowAlignmentOffsetPercent()470 public float getWindowAlignmentOffsetPercent() { 471 return mLayoutManager.getWindowAlignmentOffsetPercent(); 472 } 473 474 /** 475 * Sets number of pixels to the end of low edge. Supports right to left layout direction. 476 * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet} 477 * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}. 478 * 479 * @param offset In left to right or vertical case, it's the offset added to left/top edge. 480 * In right to left case, it's the offset subtracted from right edge. 481 */ setItemAlignmentOffset(int offset)482 public void setItemAlignmentOffset(int offset) { 483 mLayoutManager.setItemAlignmentOffset(offset); 484 requestLayout(); 485 } 486 487 /** 488 * Returns number of pixels to the end of low edge. Supports right to left layout direction. In 489 * left to right or vertical case, it's the offset added to left/top edge. In right to left 490 * case, it's the offset subtracted from right edge. 491 * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet} 492 * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}. 493 * 494 * @return The number of pixels to the end of low edge. 495 */ getItemAlignmentOffset()496 public int getItemAlignmentOffset() { 497 return mLayoutManager.getItemAlignmentOffset(); 498 } 499 500 /** 501 * Sets whether applies padding to item alignment when {@link #getItemAlignmentOffsetPercent()} 502 * is 0 or 100. 503 * <p>When true: 504 * Applies start/top padding if {@link #getItemAlignmentOffsetPercent()} is 0. 505 * Applies end/bottom padding if {@link #getItemAlignmentOffsetPercent()} is 100. 506 * Does not apply padding if {@link #getItemAlignmentOffsetPercent()} is neither 0 nor 100. 507 * </p> 508 * <p>When false: does not apply padding</p> 509 */ setItemAlignmentOffsetWithPadding(boolean withPadding)510 public void setItemAlignmentOffsetWithPadding(boolean withPadding) { 511 mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding); 512 requestLayout(); 513 } 514 515 /** 516 * Returns true if applies padding to item alignment when 517 * {@link #getItemAlignmentOffsetPercent()} is 0 or 100; returns false otherwise. 518 * <p>When true: 519 * Applies start/top padding when {@link #getItemAlignmentOffsetPercent()} is 0. 520 * Applies end/bottom padding when {@link #getItemAlignmentOffsetPercent()} is 100. 521 * Does not apply padding if {@link #getItemAlignmentOffsetPercent()} is neither 0 nor 100. 522 * </p> 523 * <p>When false: does not apply padding</p> 524 */ isItemAlignmentOffsetWithPadding()525 public boolean isItemAlignmentOffsetWithPadding() { 526 return mLayoutManager.isItemAlignmentOffsetWithPadding(); 527 } 528 529 /** 530 * Sets the offset percent for item alignment in addition to {@link 531 * #getItemAlignmentOffset()}. 532 * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet} 533 * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}. 534 * 535 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 536 * width from the low edge. Use 537 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 538 */ setItemAlignmentOffsetPercent(float offsetPercent)539 public void setItemAlignmentOffsetPercent(float offsetPercent) { 540 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 541 requestLayout(); 542 } 543 544 /** 545 * Returns the offset percent for item alignment in addition to {@link 546 * #getItemAlignmentOffset()}. 547 * 548 * @return Percentage to offset. E.g., 40 means 40% of the width from the 549 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 550 * disabled. Default value is 50. 551 */ getItemAlignmentOffsetPercent()552 public float getItemAlignmentOffsetPercent() { 553 return mLayoutManager.getItemAlignmentOffsetPercent(); 554 } 555 556 /** 557 * Sets the id of the view to align with. Use {@link android.view.View#NO_ID} (default) 558 * for the root {@link RecyclerView.ViewHolder#itemView}. 559 * Item alignment settings on BaseGridView are if {@link ItemAlignmentFacet} 560 * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}. 561 */ setItemAlignmentViewId(int viewId)562 public void setItemAlignmentViewId(int viewId) { 563 mLayoutManager.setItemAlignmentViewId(viewId); 564 } 565 566 /** 567 * Returns the id of the view to align with, or {@link android.view.View#NO_ID} for the root 568 * {@link RecyclerView.ViewHolder#itemView}. 569 * 570 * @return The id of the view to align with, or {@link android.view.View#NO_ID} for the root 571 * {@link RecyclerView.ViewHolder#itemView}. 572 */ getItemAlignmentViewId()573 public int getItemAlignmentViewId() { 574 return mLayoutManager.getItemAlignmentViewId(); 575 } 576 577 /** 578 * Sets the spacing in pixels between two child items. 579 * 580 * @deprecated use {@link #setItemSpacing(int)} 581 */ 582 @Deprecated setItemMargin(int margin)583 public void setItemMargin(int margin) { 584 setItemSpacing(margin); 585 } 586 587 /** 588 * Sets the vertical and horizontal spacing in pixels between two child items. 589 * 590 * @param spacing Vertical and horizontal spacing in pixels between two child items. 591 */ setItemSpacing(int spacing)592 public void setItemSpacing(int spacing) { 593 mLayoutManager.setItemSpacing(spacing); 594 requestLayout(); 595 } 596 597 /** 598 * Sets the spacing in pixels between two child items vertically. 599 * 600 * @deprecated Use {@link #setVerticalSpacing(int)} 601 */ 602 @Deprecated setVerticalMargin(int margin)603 public void setVerticalMargin(int margin) { 604 setVerticalSpacing(margin); 605 } 606 607 /** 608 * Returns the spacing in pixels between two child items vertically. 609 * 610 * @deprecated Use {@link #getVerticalSpacing()} 611 */ 612 @Deprecated getVerticalMargin()613 public int getVerticalMargin() { 614 return mLayoutManager.getVerticalSpacing(); 615 } 616 617 /** 618 * Sets the spacing in pixels between two child items horizontally. 619 * 620 * @deprecated Use {@link #setHorizontalSpacing(int)} 621 */ 622 @Deprecated setHorizontalMargin(int margin)623 public void setHorizontalMargin(int margin) { 624 setHorizontalSpacing(margin); 625 } 626 627 /** 628 * Returns the spacing in pixels between two child items horizontally. 629 * 630 * @deprecated Use {@link #getHorizontalSpacing()} 631 */ 632 @Deprecated getHorizontalMargin()633 public int getHorizontalMargin() { 634 return mLayoutManager.getHorizontalSpacing(); 635 } 636 637 /** 638 * Sets the vertical spacing in pixels between two child items. 639 * 640 * @param spacing Vertical spacing between two child items. 641 */ setVerticalSpacing(int spacing)642 public void setVerticalSpacing(int spacing) { 643 mLayoutManager.setVerticalSpacing(spacing); 644 requestLayout(); 645 } 646 647 /** 648 * Returns the vertical spacing in pixels between two child items. 649 * 650 * @return The vertical spacing in pixels between two child items. 651 */ getVerticalSpacing()652 public int getVerticalSpacing() { 653 return mLayoutManager.getVerticalSpacing(); 654 } 655 656 /** 657 * Sets the horizontal spacing in pixels between two child items. 658 * 659 * @param spacing Horizontal spacing in pixels between two child items. 660 */ setHorizontalSpacing(int spacing)661 public void setHorizontalSpacing(int spacing) { 662 mLayoutManager.setHorizontalSpacing(spacing); 663 requestLayout(); 664 } 665 666 /** 667 * Returns the horizontal spacing in pixels between two child items. 668 * 669 * @return The Horizontal spacing in pixels between two child items. 670 */ getHorizontalSpacing()671 public int getHorizontalSpacing() { 672 return mLayoutManager.getHorizontalSpacing(); 673 } 674 675 /** 676 * Registers a callback to be invoked when an item in BaseGridView has 677 * been laid out. 678 * 679 * @param listener The listener to be invoked. 680 */ setOnChildLaidOutListener(@ullable OnChildLaidOutListener listener)681 public void setOnChildLaidOutListener(@Nullable OnChildLaidOutListener listener) { 682 mLayoutManager.setOnChildLaidOutListener(listener); 683 } 684 685 /** 686 * Registers a callback to be invoked when an item in BaseGridView has 687 * been selected. Note that the listener may be invoked when there is a 688 * layout pending on the view, affording the listener an opportunity to 689 * adjust the upcoming layout based on the selection state. 690 * 691 * @param listener The listener to be invoked. 692 */ 693 @SuppressLint("ReferencesDeprecated") 694 @SuppressWarnings("deprecation") setOnChildSelectedListener(@ullable OnChildSelectedListener listener)695 public void setOnChildSelectedListener(@Nullable OnChildSelectedListener listener) { 696 mLayoutManager.setOnChildSelectedListener(listener); 697 } 698 699 /** 700 * Registers a callback to be invoked when the BaseGridView completes a full layout calculation. 701 * 702 * @param listener The listener to be invoked. 703 */ addOnLayoutCompletedListener(@onNull OnLayoutCompletedListener listener)704 public final void addOnLayoutCompletedListener(@NonNull OnLayoutCompletedListener listener) { 705 mLayoutManager.addOnLayoutCompletedListener(listener); 706 } 707 708 /** 709 * Removes a callback to be invoked when the BaseGridView completes a full layout calculation. 710 * 711 * @param listener The listener to be invoked. 712 */ removeOnLayoutCompletedListener(@onNull OnLayoutCompletedListener listener)713 public final void removeOnLayoutCompletedListener(@NonNull OnLayoutCompletedListener listener) { 714 mLayoutManager.removeOnLayoutCompletedListener(listener); 715 } 716 717 /** 718 * Registers a callback to be invoked when an item in BaseGridView has 719 * been selected. Note that the listener may be invoked when there is a 720 * layout pending on the view, affording the listener an opportunity to 721 * adjust the upcoming layout based on the selection state. 722 * This method will clear all existing listeners added by 723 * {@link #addOnChildViewHolderSelectedListener}. 724 * 725 * @param listener The listener to be invoked. 726 */ setOnChildViewHolderSelectedListener( @ullable OnChildViewHolderSelectedListener listener)727 public void setOnChildViewHolderSelectedListener( 728 @Nullable OnChildViewHolderSelectedListener listener) { 729 mLayoutManager.setOnChildViewHolderSelectedListener(listener); 730 } 731 732 /** 733 * Registers a callback to be invoked when an item in BaseGridView has 734 * been selected. Note that the listener may be invoked when there is a 735 * layout pending on the view, affording the listener an opportunity to 736 * adjust the upcoming layout based on the selection state. 737 * 738 * @param listener The listener to be invoked. 739 */ addOnChildViewHolderSelectedListener( @onNull OnChildViewHolderSelectedListener listener)740 public void addOnChildViewHolderSelectedListener( 741 @NonNull OnChildViewHolderSelectedListener listener) { 742 mLayoutManager.addOnChildViewHolderSelectedListener(listener); 743 } 744 745 /** 746 * Remove the callback invoked when an item in BaseGridView has been selected. 747 * 748 * @param listener The listener to be removed. 749 */ removeOnChildViewHolderSelectedListener( @onNull OnChildViewHolderSelectedListener listener)750 public void removeOnChildViewHolderSelectedListener( 751 @NonNull OnChildViewHolderSelectedListener listener) { 752 mLayoutManager.removeOnChildViewHolderSelectedListener(listener); 753 } 754 755 /** 756 * Changes the selected item immediately without animation. 757 */ setSelectedPosition(int position)758 public void setSelectedPosition(int position) { 759 mLayoutManager.setSelection(position, 0); 760 } 761 762 /** 763 * Changes the selected item and/or subposition immediately without animation. 764 * 765 */ 766 @RestrictTo(LIBRARY_GROUP_PREFIX) setSelectedPositionWithSub(int position, int subposition)767 public void setSelectedPositionWithSub(int position, int subposition) { 768 mLayoutManager.setSelectionWithSub(position, subposition, 0); 769 } 770 771 /** 772 * Changes the selected item immediately without animation, scrollExtra is 773 * applied in primary scroll direction. The scrollExtra will be kept until 774 * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call. 775 */ setSelectedPosition(int position, int scrollExtra)776 public void setSelectedPosition(int position, int scrollExtra) { 777 mLayoutManager.setSelection(position, scrollExtra); 778 } 779 780 /** 781 * Changes the selected item and/or subposition immediately without animation, scrollExtra is 782 * applied in primary scroll direction. The scrollExtra will be kept until 783 * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call. 784 * 785 */ 786 @RestrictTo(LIBRARY_GROUP_PREFIX) setSelectedPositionWithSub(int position, int subposition, int scrollExtra)787 public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) { 788 mLayoutManager.setSelectionWithSub(position, subposition, scrollExtra); 789 } 790 791 /** 792 * Changes the selected item and run an animation to scroll to the target 793 * position. 794 * 795 * @param position Adapter position of the item to select. 796 */ setSelectedPositionSmooth(int position)797 public void setSelectedPositionSmooth(int position) { 798 mLayoutManager.setSelectionSmooth(position); 799 } 800 801 /** 802 * Changes the selected item and/or subposition, runs an animation to scroll to the target 803 * position. 804 * 805 */ 806 @RestrictTo(LIBRARY_GROUP_PREFIX) setSelectedPositionSmoothWithSub(int position, int subposition)807 public void setSelectedPositionSmoothWithSub(int position, int subposition) { 808 mLayoutManager.setSelectionSmoothWithSub(position, subposition); 809 } 810 811 /** 812 * Perform a task on ViewHolder at given position after smooth scrolling to it. 813 * 814 * @param position Position of item in adapter. 815 * @param task Task to executed on the ViewHolder at a given position. 816 */ 817 @SuppressWarnings("deprecation") setSelectedPositionSmooth(final int position, final @Nullable ViewHolderTask task)818 public void setSelectedPositionSmooth(final int position, final @Nullable ViewHolderTask task) { 819 if (task != null) { 820 RecyclerView.ViewHolder vh = findViewHolderForPosition(position); 821 if (vh == null || hasPendingAdapterUpdates()) { 822 addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { 823 @Override 824 public void onChildViewHolderSelected(@NonNull RecyclerView parent, 825 RecyclerView.ViewHolder child, int selectedPosition, int subposition) { 826 if (selectedPosition == position) { 827 removeOnChildViewHolderSelectedListener(this); 828 task.run(child); 829 } 830 } 831 }); 832 } else { 833 task.run(vh); 834 } 835 } 836 setSelectedPositionSmooth(position); 837 } 838 839 /** 840 * Perform a task on ViewHolder at given position after scroll to it. 841 * 842 * @param position Position of item in adapter. 843 * @param task Task to executed on the ViewHolder at a given position. 844 */ 845 @SuppressWarnings("deprecation") setSelectedPosition(final int position, final @Nullable ViewHolderTask task)846 public void setSelectedPosition(final int position, final @Nullable ViewHolderTask task) { 847 if (task != null) { 848 RecyclerView.ViewHolder vh = findViewHolderForPosition(position); 849 if (vh == null || hasPendingAdapterUpdates()) { 850 addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { 851 @Override 852 public void onChildViewHolderSelectedAndPositioned(@NonNull RecyclerView parent, 853 RecyclerView.ViewHolder child, int selectedPosition, int subposition) { 854 if (selectedPosition == position) { 855 removeOnChildViewHolderSelectedListener(this); 856 task.run(child); 857 } 858 } 859 }); 860 } else { 861 task.run(vh); 862 } 863 } 864 setSelectedPosition(position); 865 } 866 867 /** 868 * Returns the adapter position of selected item. 869 * 870 * @return The adapter position of selected item. 871 */ getSelectedPosition()872 public int getSelectedPosition() { 873 return mLayoutManager.getSelection(); 874 } 875 876 /** 877 * Returns the sub selected item position started from zero. An item can have 878 * multiple {@link ItemAlignmentFacet}s provided by {@link RecyclerView.ViewHolder} 879 * or {@link FacetProviderAdapter}. Zero is returned when no {@link ItemAlignmentFacet} 880 * is defined. 881 * 882 */ 883 @RestrictTo(LIBRARY_GROUP_PREFIX) getSelectedSubPosition()884 public int getSelectedSubPosition() { 885 return mLayoutManager.getSubSelection(); 886 } 887 888 /** 889 * Sets whether ItemAnimator should run when a child changes size or when adding 890 * or removing a child. 891 * 892 * @param animateChildLayout True to enable ItemAnimator, false to disable. 893 */ setAnimateChildLayout(boolean animateChildLayout)894 public void setAnimateChildLayout(boolean animateChildLayout) { 895 if (mAnimateChildLayout != animateChildLayout) { 896 mAnimateChildLayout = animateChildLayout; 897 if (!mAnimateChildLayout) { 898 mSavedItemAnimator = getItemAnimator(); 899 super.setItemAnimator(null); 900 } else { 901 super.setItemAnimator(mSavedItemAnimator); 902 } 903 } 904 } 905 906 /** 907 * Returns true if an animation will run when a child changes size or when 908 * adding or removing a child. 909 * 910 * @return True if ItemAnimator is enabled, false otherwise. 911 */ isChildLayoutAnimated()912 public boolean isChildLayoutAnimated() { 913 return mAnimateChildLayout; 914 } 915 916 /** 917 * Sets the gravity used for child view positioning. Defaults to 918 * GRAVITY_TOP|GRAVITY_START. 919 * 920 * @param gravity See {@link android.view.Gravity} 921 */ setGravity(int gravity)922 public void setGravity(int gravity) { 923 mLayoutManager.setGravity(gravity); 924 requestLayout(); 925 } 926 927 @Override setLayoutManager(RecyclerView.@ullable LayoutManager layout)928 public void setLayoutManager(RecyclerView.@Nullable LayoutManager layout) { 929 if (layout == null) { 930 super.setLayoutManager(null); 931 if (mLayoutManager != null) { 932 mLayoutManager.setGridView(null); 933 } 934 mLayoutManager = null; 935 return; 936 } 937 938 mLayoutManager = (GridLayoutManager) layout; 939 mLayoutManager.setGridView(this); 940 super.setLayoutManager(layout); 941 } 942 943 @Override onRequestFocusInDescendants(int direction, @Nullable Rect previouslyFocusedRect)944 public boolean onRequestFocusInDescendants(int direction, 945 @Nullable Rect previouslyFocusedRect) { 946 if ((mPrivateFlag & PFLAG_RETAIN_FOCUS_FOR_CHILD) == PFLAG_RETAIN_FOCUS_FOR_CHILD) { 947 // dont focus to child if GridView itself retains focus for child 948 return false; 949 } 950 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 951 previouslyFocusedRect); 952 } 953 954 /** 955 * Returns the x/y offsets to final position from current position if the view 956 * is selected. 957 * 958 * @param view The view to get offsets. 959 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y. 960 */ getViewSelectedOffsets(@onNull View view, int @NonNull [] offsets)961 public void getViewSelectedOffsets(@NonNull View view, int @NonNull [] offsets) { 962 mLayoutManager.getViewSelectedOffsets(view, offsets); 963 } 964 965 @Override getChildDrawingOrder(int childCount, int i)966 public int getChildDrawingOrder(int childCount, int i) { 967 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 968 } 969 isChildrenDrawingOrderEnabledInternal()970 final boolean isChildrenDrawingOrderEnabledInternal() { 971 return isChildrenDrawingOrderEnabled(); 972 } 973 974 @Override focusSearch(int direction)975 public @Nullable View focusSearch(int direction) { 976 if (isFocused()) { 977 // focusSearch(int) is called when GridView itself is focused. 978 // Calling focusSearch(view, int) to get next sibling of current selected child. 979 View view = mLayoutManager.findViewByPosition(mLayoutManager.getSelection()); 980 if (view != null) { 981 return focusSearch(view, direction); 982 } 983 } 984 // otherwise, go to mParent to perform focusSearch 985 return super.focusSearch(direction); 986 } 987 988 @Override onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)989 protected void onFocusChanged(boolean gainFocus, int direction, 990 @Nullable Rect previouslyFocusedRect) { 991 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 992 mLayoutManager.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 993 } 994 995 /** 996 * Disables or enables focus search. 997 * 998 * @param disabled True to disable focus search, false to enable. 999 */ setFocusSearchDisabled(boolean disabled)1000 public final void setFocusSearchDisabled(boolean disabled) { 1001 // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment 1002 // re-gain focus after a BACK key pressed, so block children focus during transition. 1003 setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS : FOCUS_AFTER_DESCENDANTS); 1004 mLayoutManager.setFocusSearchDisabled(disabled); 1005 } 1006 1007 /** 1008 * Returns true if focus search is disabled. 1009 * 1010 * @return True if focus search is disabled. 1011 */ isFocusSearchDisabled()1012 public final boolean isFocusSearchDisabled() { 1013 return mLayoutManager.isFocusSearchDisabled(); 1014 } 1015 1016 /** 1017 * Enables or disables layout. All children will be removed when layout is 1018 * disabled. 1019 * 1020 * @param layoutEnabled True to enable layout, false otherwise. 1021 */ setLayoutEnabled(boolean layoutEnabled)1022 public void setLayoutEnabled(boolean layoutEnabled) { 1023 mLayoutManager.setLayoutEnabled(layoutEnabled); 1024 } 1025 1026 /** 1027 * Changes and overrides children's visibility. 1028 * 1029 * @param visibility See {@link View#getVisibility()}. 1030 */ setChildrenVisibility(int visibility)1031 public void setChildrenVisibility(int visibility) { 1032 mLayoutManager.setChildrenVisibility(visibility); 1033 } 1034 1035 /** 1036 * Enables or disables pruning of children. Disable is useful during transition. 1037 * 1038 * @param pruneChild True to prune children out side visible area, false to enable. 1039 */ setPruneChild(boolean pruneChild)1040 public void setPruneChild(boolean pruneChild) { 1041 mLayoutManager.setPruneChild(pruneChild); 1042 } 1043 1044 /** 1045 * Enables or disables scrolling. Disable is useful during transition. 1046 * 1047 * @param scrollEnabled True to enable scroll, false to disable. 1048 */ setScrollEnabled(boolean scrollEnabled)1049 public void setScrollEnabled(boolean scrollEnabled) { 1050 mLayoutManager.setScrollEnabled(scrollEnabled); 1051 } 1052 1053 /** 1054 * Returns true if scrolling is enabled, false otherwise. 1055 * 1056 * @return True if scrolling is enabled, false otherwise. 1057 */ isScrollEnabled()1058 public boolean isScrollEnabled() { 1059 return mLayoutManager.isScrollEnabled(); 1060 } 1061 1062 /** 1063 * Returns true if the view at the given position has a same row sibling 1064 * in front of it. This will return true if first item view is not created. 1065 * 1066 * @param position Position in adapter. 1067 * @return True if the view at the given position has a same row sibling in front of it. 1068 */ hasPreviousViewInSameRow(int position)1069 public boolean hasPreviousViewInSameRow(int position) { 1070 return mLayoutManager.hasPreviousViewInSameRow(position); 1071 } 1072 1073 /** 1074 * Enables or disables the default "focus draw at last" order rule. Default is enabled. 1075 * 1076 * @param enabled True to draw the selected child at last, false otherwise. 1077 */ setFocusDrawingOrderEnabled(boolean enabled)1078 public void setFocusDrawingOrderEnabled(boolean enabled) { 1079 super.setChildrenDrawingOrderEnabled(enabled); 1080 } 1081 1082 /** 1083 * Returns true if draws selected child at last, false otherwise. Default is enabled. 1084 * 1085 * @return True if draws selected child at last, false otherwise. 1086 */ isFocusDrawingOrderEnabled()1087 public boolean isFocusDrawingOrderEnabled() { 1088 return super.isChildrenDrawingOrderEnabled(); 1089 } 1090 1091 /** 1092 * Sets the touch intercept listener. 1093 * 1094 * @param listener The touch intercept listener. 1095 */ setOnTouchInterceptListener(@ullable OnTouchInterceptListener listener)1096 public void setOnTouchInterceptListener(@Nullable OnTouchInterceptListener listener) { 1097 mOnTouchInterceptListener = listener; 1098 } 1099 1100 /** 1101 * Sets the generic motion intercept listener. 1102 * 1103 * @param listener The motion intercept listener. 1104 */ setOnMotionInterceptListener(@ullable OnMotionInterceptListener listener)1105 public void setOnMotionInterceptListener(@Nullable OnMotionInterceptListener listener) { 1106 mOnMotionInterceptListener = listener; 1107 } 1108 1109 /** 1110 * Sets the key intercept listener. 1111 * 1112 * @param listener The key intercept listener. 1113 */ setOnKeyInterceptListener(@ullable OnKeyInterceptListener listener)1114 public void setOnKeyInterceptListener(@Nullable OnKeyInterceptListener listener) { 1115 mOnKeyInterceptListener = listener; 1116 } 1117 1118 /** 1119 * Sets the unhandled key listener. 1120 * 1121 * @param listener The unhandled key intercept listener. 1122 */ setOnUnhandledKeyListener(@ullable OnUnhandledKeyListener listener)1123 public void setOnUnhandledKeyListener(@Nullable OnUnhandledKeyListener listener) { 1124 mOnUnhandledKeyListener = listener; 1125 } 1126 1127 /** 1128 * Returns the unhandled key listener. 1129 * 1130 * @return The unhandled key listener. 1131 */ getOnUnhandledKeyListener()1132 public @Nullable OnUnhandledKeyListener getOnUnhandledKeyListener() { 1133 return mOnUnhandledKeyListener; 1134 } 1135 1136 @Override dispatchKeyEvent(@onNull KeyEvent event)1137 public boolean dispatchKeyEvent(@NonNull KeyEvent event) { 1138 if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) { 1139 return true; 1140 } 1141 if (super.dispatchKeyEvent(event)) { 1142 return true; 1143 } 1144 return mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event); 1145 } 1146 1147 @Override dispatchTouchEvent(@onNull MotionEvent event)1148 public boolean dispatchTouchEvent(@NonNull MotionEvent event) { 1149 if (mOnTouchInterceptListener != null) { 1150 if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) { 1151 return true; 1152 } 1153 } 1154 return super.dispatchTouchEvent(event); 1155 } 1156 1157 @Override dispatchGenericFocusedEvent(@onNull MotionEvent event)1158 protected boolean dispatchGenericFocusedEvent(@NonNull MotionEvent event) { 1159 if (mOnMotionInterceptListener != null) { 1160 if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) { 1161 return true; 1162 } 1163 } 1164 return super.dispatchGenericFocusedEvent(event); 1165 } 1166 1167 /** 1168 * Returns the policy for saving children. 1169 * 1170 * @return policy, one of {@link #SAVE_NO_CHILD} 1171 * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 1172 */ getSaveChildrenPolicy()1173 public final int getSaveChildrenPolicy() { 1174 return mLayoutManager.mChildrenStates.getSavePolicy(); 1175 } 1176 1177 /** 1178 * Returns the limit used when when {@link #getSaveChildrenPolicy()} is 1179 * {@link #SAVE_LIMITED_CHILD} 1180 */ getSaveChildrenLimitNumber()1181 public final int getSaveChildrenLimitNumber() { 1182 return mLayoutManager.mChildrenStates.getLimitNumber(); 1183 } 1184 1185 /** 1186 * Sets the policy for saving children. 1187 * 1188 * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD} 1189 * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 1190 */ setSaveChildrenPolicy(int savePolicy)1191 public final void setSaveChildrenPolicy(int savePolicy) { 1192 mLayoutManager.mChildrenStates.setSavePolicy(savePolicy); 1193 } 1194 1195 /** 1196 * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}. 1197 */ setSaveChildrenLimitNumber(int limitNumber)1198 public final void setSaveChildrenLimitNumber(int limitNumber) { 1199 mLayoutManager.mChildrenStates.setLimitNumber(limitNumber); 1200 } 1201 1202 @Override hasOverlappingRendering()1203 public boolean hasOverlappingRendering() { 1204 return mHasOverlappingRendering; 1205 } 1206 setHasOverlappingRendering(boolean hasOverlapping)1207 public void setHasOverlappingRendering(boolean hasOverlapping) { 1208 mHasOverlappingRendering = hasOverlapping; 1209 } 1210 1211 /** 1212 * Notify layout manager that layout directionality has been updated 1213 */ 1214 @Override onRtlPropertiesChanged(int layoutDirection)1215 public void onRtlPropertiesChanged(int layoutDirection) { 1216 if (mLayoutManager != null) { 1217 mLayoutManager.onRtlPropertiesChanged(layoutDirection); 1218 } 1219 } 1220 1221 /** 1222 * Sets pixels of extra space for layout child in invisible area. 1223 * 1224 * @param extraLayoutSpace Pixels of extra space for layout invisible child. 1225 * Must be bigger or equals to 0. 1226 */ 1227 @RestrictTo(LIBRARY_GROUP_PREFIX) setExtraLayoutSpace(int extraLayoutSpace)1228 public void setExtraLayoutSpace(int extraLayoutSpace) { 1229 mLayoutManager.setExtraLayoutSpace(extraLayoutSpace); 1230 } 1231 1232 /** 1233 * Returns pixels of extra space for layout child in invisible area. 1234 * 1235 */ 1236 @RestrictTo(LIBRARY_GROUP_PREFIX) getExtraLayoutSpace()1237 public int getExtraLayoutSpace() { 1238 return mLayoutManager.getExtraLayoutSpace(); 1239 } 1240 1241 /** 1242 * Temporarily slide out child views to bottom (for VerticalGridView) or end 1243 * (for HorizontalGridView). Layout and scrolling will be suppressed until 1244 * {@link #animateIn()} is called. 1245 */ animateOut()1246 public void animateOut() { 1247 mLayoutManager.slideOut(); 1248 } 1249 1250 /** 1251 * Undo animateOut() and slide in child views. 1252 */ animateIn()1253 public void animateIn() { 1254 mLayoutManager.slideIn(); 1255 } 1256 1257 @Override scrollToPosition(int position)1258 public void scrollToPosition(int position) { 1259 // dont abort the animateOut() animation, just record the position 1260 if (mLayoutManager.isSlidingChildViews()) { 1261 mLayoutManager.setSelectionWithSub(position, 0, 0); 1262 return; 1263 } 1264 super.scrollToPosition(position); 1265 } 1266 1267 @Override smoothScrollToPosition(int position)1268 public void smoothScrollToPosition(int position) { 1269 // dont abort the animateOut() animation, just record the position 1270 if (mLayoutManager.isSlidingChildViews()) { 1271 mLayoutManager.setSelectionWithSub(position, 0, 0); 1272 return; 1273 } 1274 super.smoothScrollToPosition(position); 1275 } 1276 1277 /** 1278 * Set custom behavior for smoothScrollBy(). 1279 * 1280 * @param behavior Custom behavior of SmoothScrollBy(). Null for default behavior. 1281 */ setSmoothScrollByBehavior(@ullable SmoothScrollByBehavior behavior)1282 public final void setSmoothScrollByBehavior(@Nullable SmoothScrollByBehavior behavior) { 1283 mSmoothScrollByBehavior = behavior; 1284 } 1285 1286 /** 1287 * Returns custom behavior for smoothScrollBy(). 1288 * 1289 * @return Custom behavior for SmoothScrollBy(). Null for default behavior. 1290 */ getSmoothScrollByBehavior()1291 public @Nullable SmoothScrollByBehavior getSmoothScrollByBehavior() { 1292 return mSmoothScrollByBehavior; 1293 } 1294 1295 @Override smoothScrollBy(int dx, int dy)1296 public void smoothScrollBy(int dx, int dy) { 1297 if (mSmoothScrollByBehavior != null) { 1298 smoothScrollBy(dx, dy, 1299 mSmoothScrollByBehavior.configSmoothScrollByInterpolator(dx, dy), 1300 mSmoothScrollByBehavior.configSmoothScrollByDuration(dx, dy)); 1301 } else { 1302 smoothScrollBy(dx, dy, null, UNDEFINED_DURATION); 1303 } 1304 } 1305 1306 @Override smoothScrollBy(int dx, int dy, @Nullable Interpolator interpolator)1307 public void smoothScrollBy(int dx, int dy, @Nullable Interpolator interpolator) { 1308 if (mSmoothScrollByBehavior != null) { 1309 smoothScrollBy(dx, dy, 1310 interpolator, 1311 mSmoothScrollByBehavior.configSmoothScrollByDuration(dx, dy)); 1312 } else { 1313 smoothScrollBy(dx, dy, interpolator, UNDEFINED_DURATION); 1314 } 1315 } 1316 1317 /** 1318 * Set factor of how slow the smoothScroller should run. For example when set to 2f, the smooth 1319 * scroller is twice slower. The value is 1f by default. 1320 * 1321 * @param smoothScrollSpeedFactor Factor of how slow the smooth scroll is. 1322 */ setSmoothScrollSpeedFactor(float smoothScrollSpeedFactor)1323 public final void setSmoothScrollSpeedFactor(float smoothScrollSpeedFactor) { 1324 mLayoutManager.mSmoothScrollSpeedFactor = smoothScrollSpeedFactor; 1325 } 1326 1327 /** 1328 * @return Factor of how slow the smoothScroller runs. Default value is 1f. 1329 */ getSmoothScrollSpeedFactor()1330 public final float getSmoothScrollSpeedFactor() { 1331 return mLayoutManager.mSmoothScrollSpeedFactor; 1332 } 1333 1334 /** 1335 * When holding DPAD, DPAD events are generated faster than the grid view can scroll. The 1336 * grid view counts unhandled DPAD events and completes the movement after user release DPAD. 1337 * If the value is set too high, the scrolling will last very long after DPAD is released. If 1338 * the value is set too low, it may miss many DPAD events. The default value is 10. If app 1339 * increases {@link #setSmoothScrollSpeedFactor(float)}, it may need decrease the max pending 1340 * DPAD events to avoid scrolling too long after DPAD release. 1341 * 1342 * @param maxPendingMoves Maximum number of pending DPAD events to be remembered. 1343 */ setSmoothScrollMaxPendingMoves(int maxPendingMoves)1344 public final void setSmoothScrollMaxPendingMoves(int maxPendingMoves) { 1345 mLayoutManager.mMaxPendingMoves = maxPendingMoves; 1346 } 1347 1348 /** 1349 * When holding DPAD, DPAD events are generated faster than the grid view can scroll. The 1350 * grid view counts unhandled DPAD events and complete the movement after user release DPAD. 1351 * If the value is set too high, the scrolling will last very long after DPAD is released. If 1352 * the value is set too low, it may miss many DPAD events. The default value is 10. If app 1353 * increases {@link #setSmoothScrollSpeedFactor(float)}, it may need decrease the max pending 1354 * DPAD events to avoid scrolling too long after DPAD release. 1355 * 1356 * @return Maximum number of pending DPAD events to be remembered when smooth scroll cannot 1357 * catch up speed of DPAD events being sent. 1358 */ getSmoothScrollMaxPendingMoves()1359 public final int getSmoothScrollMaxPendingMoves() { 1360 return mLayoutManager.mMaxPendingMoves; 1361 } 1362 1363 /** 1364 * Sets the number of items to prefetch in 1365 * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)}, 1366 * which defines how many inner items should be prefetched when this GridView is nested inside 1367 * another RecyclerView. 1368 * 1369 * <p>Set this value to the number of items this inner GridView will display when it is 1370 * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items 1371 * so they are ready, avoiding jank as the inner GridView is scrolled into the viewport.</p> 1372 * 1373 * <p>For example, take a VerticalGridView of scrolling HorizontalGridViews. The rows always 1374 * have 6 items visible in them (or 7 if not aligned). Passing <code>6</code> to this method 1375 * for each inner GridView will enable RecyclerView's prefetching feature to do create/bind work 1376 * for 6 views within a row early, before it is scrolled on screen, instead of just the default 1377 * 4.</p> 1378 * 1379 * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView 1380 * nested in another RecyclerView.</p> 1381 * 1382 * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of 1383 * views that will be visible in this view can incur unnecessary bind work, and an increase to 1384 * the number of Views created and in active use.</p> 1385 * 1386 * @param itemCount Number of items to prefetch 1387 * @see #getInitialPrefetchItemCount() 1388 * @see RecyclerView.LayoutManager#isItemPrefetchEnabled() 1389 * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, 1390 * RecyclerView.LayoutManager.LayoutPrefetchRegistry) 1391 */ setInitialPrefetchItemCount(int itemCount)1392 public void setInitialPrefetchItemCount(int itemCount) { 1393 mInitialPrefetchItemCount = itemCount; 1394 } 1395 1396 /** 1397 * Gets the number of items to prefetch in 1398 * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)}, 1399 * which defines how many inner items should be prefetched when this GridView is nested inside 1400 * another RecyclerView. 1401 * 1402 * @return number of items to prefetch. 1403 * @see RecyclerView.LayoutManager#isItemPrefetchEnabled() 1404 * @see #setInitialPrefetchItemCount(int) 1405 * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, 1406 * RecyclerView.LayoutManager.LayoutPrefetchRegistry) 1407 */ getInitialPrefetchItemCount()1408 public int getInitialPrefetchItemCount() { 1409 return mInitialPrefetchItemCount; 1410 } 1411 1412 @Override removeView(@onNull View view)1413 public void removeView(@NonNull View view) { 1414 boolean retainFocusForChild = view.hasFocus() && isFocusable(); 1415 if (retainFocusForChild) { 1416 // When animation or scrolling removes a focused child, focus to GridView itself to 1417 // avoid losing focus. 1418 mPrivateFlag |= PFLAG_RETAIN_FOCUS_FOR_CHILD; 1419 requestFocus(); 1420 } 1421 super.removeView(view); 1422 if (retainFocusForChild) { 1423 mPrivateFlag ^= ~PFLAG_RETAIN_FOCUS_FOR_CHILD; 1424 } 1425 } 1426 1427 @Override removeViewAt(int index)1428 public void removeViewAt(int index) { 1429 boolean retainFocusForChild = getChildAt(index).hasFocus(); 1430 if (retainFocusForChild) { 1431 // When animation or scrolling removes a focused child, focus to GridView itself to 1432 // avoid losing focus. 1433 mPrivateFlag |= PFLAG_RETAIN_FOCUS_FOR_CHILD; 1434 requestFocus(); 1435 } 1436 super.removeViewAt(index); 1437 if (retainFocusForChild) { 1438 mPrivateFlag ^= ~PFLAG_RETAIN_FOCUS_FOR_CHILD; 1439 } 1440 } 1441 } 1442