BaseGridView.java revision 5c05fc026b77c6387917560f8dbbbd4bff13bbf9
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 */ 14package android.support.v17.leanback.widget; 15 16import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 17 18import android.content.Context; 19import android.content.res.TypedArray; 20import android.graphics.Rect; 21import android.support.annotation.RestrictTo; 22import android.support.v17.leanback.R; 23import android.support.v7.widget.RecyclerView; 24import android.support.v7.widget.SimpleItemAnimator; 25import android.util.AttributeSet; 26import android.view.Gravity; 27import android.view.KeyEvent; 28import android.view.MotionEvent; 29import android.view.View; 30 31/** 32 * An abstract base class for vertically and horizontally scrolling lists. The items come 33 * from the {@link RecyclerView.Adapter} associated with this view. 34 * Do not directly use this class, use {@link VerticalGridView} and {@link HorizontalGridView}. 35 * @hide 36 */ 37@RestrictTo(LIBRARY_GROUP) 38abstract class BaseGridView extends RecyclerView { 39 40 /** 41 * Always keep focused item at a aligned position. Developer can use 42 * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned. 43 * In this mode, the last focused position will be remembered and restored when focus 44 * is back to the view. 45 */ 46 public final static int FOCUS_SCROLL_ALIGNED = 0; 47 48 /** 49 * Scroll to make the focused item inside client area. 50 */ 51 public final static int FOCUS_SCROLL_ITEM = 1; 52 53 /** 54 * Scroll a page of items when focusing to item outside the client area. 55 * The page size matches the client area size of RecyclerView. 56 */ 57 public final static int FOCUS_SCROLL_PAGE = 2; 58 59 /** 60 * The first item is aligned with the low edge of the viewport. When 61 * navigating away from the first item, the focus maintains a middle 62 * location. 63 * <p> 64 * For HorizontalGridView, low edge refers to left edge when RTL is false or 65 * right edge when RTL is true. 66 * For VerticalGridView, low edge refers to top edge. 67 * <p> 68 * The middle location is calculated by "windowAlignOffset" and 69 * "windowAlignOffsetPercent"; if neither of these two is defined, the 70 * default value is 1/2 of the size. 71 */ 72 public final static int WINDOW_ALIGN_LOW_EDGE = 1; 73 74 /** 75 * The last item is aligned with the high edge of the viewport when 76 * navigating to the end of list. When navigating away from the end, the 77 * focus maintains a middle location. 78 * <p> 79 * For HorizontalGridView, high edge refers to right edge when RTL is false or 80 * left edge when RTL is true. 81 * For VerticalGridView, high edge refers to bottom edge. 82 * <p> 83 * The middle location is calculated by "windowAlignOffset" and 84 * "windowAlignOffsetPercent"; if neither of these two is defined, the 85 * default value is 1/2 of the size. 86 */ 87 public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1; 88 89 /** 90 * The first item and last item are aligned with the two edges of the 91 * viewport. When navigating in the middle of list, the focus maintains a 92 * middle location. 93 * <p> 94 * The middle location is calculated by "windowAlignOffset" and 95 * "windowAlignOffsetPercent"; if neither of these two is defined, the 96 * default value is 1/2 of the size. 97 */ 98 public final static int WINDOW_ALIGN_BOTH_EDGE = 99 WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE; 100 101 /** 102 * The focused item always stays in a middle location. 103 * <p> 104 * The middle location is calculated by "windowAlignOffset" and 105 * "windowAlignOffsetPercent"; if neither of these two is defined, the 106 * default value is 1/2 of the size. 107 */ 108 public final static int WINDOW_ALIGN_NO_EDGE = 0; 109 110 /** 111 * Value indicates that percent is not used. 112 */ 113 public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1; 114 115 /** 116 * Value indicates that percent is not used. 117 */ 118 public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = 119 ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED; 120 121 /** 122 * Dont save states of any child views. 123 */ 124 public static final int SAVE_NO_CHILD = 0; 125 126 /** 127 * Only save on screen child views, the states are lost when they become off screen. 128 */ 129 public static final int SAVE_ON_SCREEN_CHILD = 1; 130 131 /** 132 * Save on screen views plus save off screen child views states up to 133 * {@link #getSaveChildrenLimitNumber()}. 134 */ 135 public static final int SAVE_LIMITED_CHILD = 2; 136 137 /** 138 * Save on screen views plus save off screen child views without any limitation. 139 * This might cause out of memory, only use it when you are dealing with limited data. 140 */ 141 public static final int SAVE_ALL_CHILD = 3; 142 143 /** 144 * Listener for intercepting touch dispatch events. 145 */ 146 public interface OnTouchInterceptListener { 147 /** 148 * Returns true if the touch dispatch event should be consumed. 149 */ 150 public boolean onInterceptTouchEvent(MotionEvent event); 151 } 152 153 /** 154 * Listener for intercepting generic motion dispatch events. 155 */ 156 public interface OnMotionInterceptListener { 157 /** 158 * Returns true if the touch dispatch event should be consumed. 159 */ 160 public boolean onInterceptMotionEvent(MotionEvent event); 161 } 162 163 /** 164 * Listener for intercepting key dispatch events. 165 */ 166 public interface OnKeyInterceptListener { 167 /** 168 * Returns true if the key dispatch event should be consumed. 169 */ 170 public boolean onInterceptKeyEvent(KeyEvent event); 171 } 172 173 public interface OnUnhandledKeyListener { 174 /** 175 * Returns true if the key event should be consumed. 176 */ 177 public boolean onUnhandledKey(KeyEvent event); 178 } 179 180 final GridLayoutManager mLayoutManager; 181 182 /** 183 * Animate layout changes from a child resizing or adding/removing a child. 184 */ 185 private boolean mAnimateChildLayout = true; 186 187 private boolean mHasOverlappingRendering = true; 188 189 private RecyclerView.ItemAnimator mSavedItemAnimator; 190 191 private OnTouchInterceptListener mOnTouchInterceptListener; 192 private OnMotionInterceptListener mOnMotionInterceptListener; 193 private OnKeyInterceptListener mOnKeyInterceptListener; 194 RecyclerView.RecyclerListener mChainedRecyclerListener; 195 private OnUnhandledKeyListener mOnUnhandledKeyListener; 196 197 /** 198 * Number of items to prefetch when first coming on screen with new data. 199 */ 200 int mInitialItemPrefetchCount = 4; 201 202 public BaseGridView(Context context, AttributeSet attrs, int defStyle) { 203 super(context, attrs, defStyle); 204 mLayoutManager = new GridLayoutManager(this); 205 setLayoutManager(mLayoutManager); 206 // leanback LayoutManager already restores focus inside onLayoutChildren(). 207 setPreserveFocusAfterLayout(false); 208 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 209 setHasFixedSize(true); 210 setChildrenDrawingOrderEnabled(true); 211 setWillNotDraw(true); 212 setOverScrollMode(View.OVER_SCROLL_NEVER); 213 // Disable change animation by default on leanback. 214 // Change animation will create a new view and cause undesired 215 // focus animation between the old view and new view. 216 ((SimpleItemAnimator)getItemAnimator()).setSupportsChangeAnimations(false); 217 super.setRecyclerListener(new RecyclerView.RecyclerListener() { 218 @Override 219 public void onViewRecycled(RecyclerView.ViewHolder holder) { 220 mLayoutManager.onChildRecycled(holder); 221 if (mChainedRecyclerListener != null) { 222 mChainedRecyclerListener.onViewRecycled(holder); 223 } 224 } 225 }); 226 } 227 228 protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) { 229 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView); 230 boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false); 231 boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false); 232 mLayoutManager.setFocusOutAllowed(throughFront, throughEnd); 233 boolean throughSideStart = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideStart, true); 234 boolean throughSideEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideEnd, true); 235 mLayoutManager.setFocusOutSideAllowed(throughSideStart, throughSideEnd); 236 mLayoutManager.setVerticalSpacing( 237 a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_verticalSpacing, 238 a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0))); 239 mLayoutManager.setHorizontalSpacing( 240 a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_horizontalSpacing, 241 a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0))); 242 if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) { 243 setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY)); 244 } 245 a.recycle(); 246 } 247 248 /** 249 * Sets the strategy used to scroll in response to item focus changing: 250 * <ul> 251 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 252 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 253 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 254 * </ul> 255 */ 256 public void setFocusScrollStrategy(int scrollStrategy) { 257 if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM 258 && scrollStrategy != FOCUS_SCROLL_PAGE) { 259 throw new IllegalArgumentException("Invalid scrollStrategy"); 260 } 261 mLayoutManager.setFocusScrollStrategy(scrollStrategy); 262 requestLayout(); 263 } 264 265 /** 266 * Returns the strategy used to scroll in response to item focus changing. 267 * <ul> 268 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 269 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 270 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 271 * </ul> 272 */ 273 public int getFocusScrollStrategy() { 274 return mLayoutManager.getFocusScrollStrategy(); 275 } 276 277 /** 278 * Sets the method for focused item alignment in the view. 279 * 280 * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE}, 281 * {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or 282 * {@link #WINDOW_ALIGN_NO_EDGE}. 283 */ 284 public void setWindowAlignment(int windowAlignment) { 285 mLayoutManager.setWindowAlignment(windowAlignment); 286 requestLayout(); 287 } 288 289 /** 290 * Returns the method for focused item alignment in the view. 291 * 292 * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE}, 293 * {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}. 294 */ 295 public int getWindowAlignment() { 296 return mLayoutManager.getWindowAlignment(); 297 } 298 299 /** 300 * Sets the offset in pixels for window alignment. 301 * 302 * @param offset The number of pixels to offset. If the offset is positive, 303 * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE}); 304 * if the offset is negative, the absolute value is distance from high 305 * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}). 306 * Default value is 0. 307 */ 308 public void setWindowAlignmentOffset(int offset) { 309 mLayoutManager.setWindowAlignmentOffset(offset); 310 requestLayout(); 311 } 312 313 /** 314 * Returns the offset in pixels for window alignment. 315 * 316 * @return The number of pixels to offset. If the offset is positive, 317 * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE}); 318 * if the offset is negative, the absolute value is distance from high 319 * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}). 320 * Default value is 0. 321 */ 322 public int getWindowAlignmentOffset() { 323 return mLayoutManager.getWindowAlignmentOffset(); 324 } 325 326 /** 327 * Sets the offset percent for window alignment in addition to {@link 328 * #getWindowAlignmentOffset()}. 329 * 330 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 331 * width from low edge. Use 332 * {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 333 * Default value is 50. 334 */ 335 public void setWindowAlignmentOffsetPercent(float offsetPercent) { 336 mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent); 337 requestLayout(); 338 } 339 340 /** 341 * Returns the offset percent for window alignment in addition to 342 * {@link #getWindowAlignmentOffset()}. 343 * 344 * @return Percentage to offset. E.g., 40 means 40% of the width from the 345 * low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if 346 * disabled. Default value is 50. 347 */ 348 public float getWindowAlignmentOffsetPercent() { 349 return mLayoutManager.getWindowAlignmentOffsetPercent(); 350 } 351 352 /** 353 * Sets the absolute offset in pixels for item alignment. 354 * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet} 355 * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}. 356 * 357 * @param offset The number of pixels to offset. Can be negative for 358 * alignment from the high edge, or positive for alignment from the 359 * low edge. 360 */ 361 public void setItemAlignmentOffset(int offset) { 362 mLayoutManager.setItemAlignmentOffset(offset); 363 requestLayout(); 364 } 365 366 /** 367 * Returns the absolute offset in pixels for item alignment. 368 * 369 * @return The number of pixels to offset. Will be negative for alignment 370 * from the high edge, or positive for alignment from the low edge. 371 * Default value is 0. 372 */ 373 public int getItemAlignmentOffset() { 374 return mLayoutManager.getItemAlignmentOffset(); 375 } 376 377 /** 378 * Set to true if include padding in calculating item align offset. 379 * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet} 380 * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}. 381 * 382 * @param withPadding When it is true: we include left/top padding for positive 383 * item offset, include right/bottom padding for negative item offset. 384 */ 385 public void setItemAlignmentOffsetWithPadding(boolean withPadding) { 386 mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding); 387 requestLayout(); 388 } 389 390 /** 391 * Returns true if include padding in calculating item align offset. 392 */ 393 public boolean isItemAlignmentOffsetWithPadding() { 394 return mLayoutManager.isItemAlignmentOffsetWithPadding(); 395 } 396 397 /** 398 * Sets the offset percent for item alignment in addition to {@link 399 * #getItemAlignmentOffset()}. 400 * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet} 401 * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}. 402 * 403 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 404 * width from the low edge. Use 405 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 406 */ 407 public void setItemAlignmentOffsetPercent(float offsetPercent) { 408 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 409 requestLayout(); 410 } 411 412 /** 413 * Returns the offset percent for item alignment in addition to {@link 414 * #getItemAlignmentOffset()}. 415 * 416 * @return Percentage to offset. E.g., 40 means 40% of the width from the 417 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 418 * disabled. Default value is 50. 419 */ 420 public float getItemAlignmentOffsetPercent() { 421 return mLayoutManager.getItemAlignmentOffsetPercent(); 422 } 423 424 /** 425 * Sets the id of the view to align with. Use {@link android.view.View#NO_ID} (default) 426 * for the item view itself. 427 * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet} 428 * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}. 429 */ 430 public void setItemAlignmentViewId(int viewId) { 431 mLayoutManager.setItemAlignmentViewId(viewId); 432 } 433 434 /** 435 * Returns the id of the view to align with, or zero for the item view itself. 436 */ 437 public int getItemAlignmentViewId() { 438 return mLayoutManager.getItemAlignmentViewId(); 439 } 440 441 /** 442 * Sets the spacing in pixels between two child items. 443 * @deprecated use {@link #setItemSpacing(int)} 444 */ 445 @Deprecated 446 public void setItemMargin(int margin) { 447 setItemSpacing(margin); 448 } 449 450 /** 451 * Sets the spacing in pixels between two child items. 452 */ 453 public void setItemSpacing(int spacing) { 454 mLayoutManager.setItemSpacing(spacing); 455 requestLayout(); 456 } 457 458 /** 459 * Sets the spacing in pixels between two child items vertically. 460 * @deprecated Use {@link #setVerticalSpacing(int)} 461 */ 462 @Deprecated 463 public void setVerticalMargin(int margin) { 464 setVerticalSpacing(margin); 465 } 466 467 /** 468 * Returns the spacing in pixels between two child items vertically. 469 * @deprecated Use {@link #getVerticalSpacing()} 470 */ 471 @Deprecated 472 public int getVerticalMargin() { 473 return mLayoutManager.getVerticalSpacing(); 474 } 475 476 /** 477 * Sets the spacing in pixels between two child items horizontally. 478 * @deprecated Use {@link #setHorizontalSpacing(int)} 479 */ 480 @Deprecated 481 public void setHorizontalMargin(int margin) { 482 setHorizontalSpacing(margin); 483 } 484 485 /** 486 * Returns the spacing in pixels between two child items horizontally. 487 * @deprecated Use {@link #getHorizontalSpacing()} 488 */ 489 @Deprecated 490 public int getHorizontalMargin() { 491 return mLayoutManager.getHorizontalSpacing(); 492 } 493 494 /** 495 * Sets the spacing in pixels between two child items vertically. 496 */ 497 public void setVerticalSpacing(int spacing) { 498 mLayoutManager.setVerticalSpacing(spacing); 499 requestLayout(); 500 } 501 502 /** 503 * Returns the spacing in pixels between two child items vertically. 504 */ 505 public int getVerticalSpacing() { 506 return mLayoutManager.getVerticalSpacing(); 507 } 508 509 /** 510 * Sets the spacing in pixels between two child items horizontally. 511 */ 512 public void setHorizontalSpacing(int spacing) { 513 mLayoutManager.setHorizontalSpacing(spacing); 514 requestLayout(); 515 } 516 517 /** 518 * Returns the spacing in pixels between two child items horizontally. 519 */ 520 public int getHorizontalSpacing() { 521 return mLayoutManager.getHorizontalSpacing(); 522 } 523 524 /** 525 * Registers a callback to be invoked when an item in BaseGridView has 526 * been laid out. 527 * 528 * @param listener The listener to be invoked. 529 */ 530 public void setOnChildLaidOutListener(OnChildLaidOutListener listener) { 531 mLayoutManager.setOnChildLaidOutListener(listener); 532 } 533 534 /** 535 * Registers a callback to be invoked when an item in BaseGridView has 536 * been selected. Note that the listener may be invoked when there is a 537 * layout pending on the view, affording the listener an opportunity to 538 * adjust the upcoming layout based on the selection state. 539 * 540 * @param listener The listener to be invoked. 541 */ 542 public void setOnChildSelectedListener(OnChildSelectedListener listener) { 543 mLayoutManager.setOnChildSelectedListener(listener); 544 } 545 546 /** 547 * Registers a callback to be invoked when an item in BaseGridView has 548 * been selected. Note that the listener may be invoked when there is a 549 * layout pending on the view, affording the listener an opportunity to 550 * adjust the upcoming layout based on the selection state. 551 * This method will clear all existing listeners added by 552 * {@link #addOnChildViewHolderSelectedListener}. 553 * 554 * @param listener The listener to be invoked. 555 */ 556 public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) { 557 mLayoutManager.setOnChildViewHolderSelectedListener(listener); 558 } 559 560 /** 561 * Registers a callback to be invoked when an item in BaseGridView has 562 * been selected. Note that the listener may be invoked when there is a 563 * layout pending on the view, affording the listener an opportunity to 564 * adjust the upcoming layout based on the selection state. 565 * 566 * @param listener The listener to be invoked. 567 */ 568 public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) { 569 mLayoutManager.addOnChildViewHolderSelectedListener(listener); 570 } 571 572 /** 573 * Remove the callback invoked when an item in BaseGridView has been selected. 574 * 575 * @param listener The listener to be removed. 576 */ 577 public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) 578 { 579 mLayoutManager.removeOnChildViewHolderSelectedListener(listener); 580 } 581 582 /** 583 * Changes the selected item immediately without animation. 584 */ 585 public void setSelectedPosition(int position) { 586 mLayoutManager.setSelection(position, 0); 587 } 588 589 /** 590 * Changes the selected item and/or subposition immediately without animation. 591 */ 592 public void setSelectedPositionWithSub(int position, int subposition) { 593 mLayoutManager.setSelectionWithSub(position, subposition, 0); 594 } 595 596 /** 597 * Changes the selected item immediately without animation, scrollExtra is 598 * applied in primary scroll direction. The scrollExtra will be kept until 599 * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call. 600 */ 601 public void setSelectedPosition(int position, int scrollExtra) { 602 mLayoutManager.setSelection(position, scrollExtra); 603 } 604 605 /** 606 * Changes the selected item and/or subposition immediately without animation, scrollExtra is 607 * applied in primary scroll direction. The scrollExtra will be kept until 608 * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call. 609 */ 610 public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) { 611 mLayoutManager.setSelectionWithSub(position, subposition, scrollExtra); 612 } 613 614 /** 615 * Changes the selected item and run an animation to scroll to the target 616 * position. 617 */ 618 public void setSelectedPositionSmooth(int position) { 619 mLayoutManager.setSelectionSmooth(position); 620 } 621 622 /** 623 * Changes the selected item and/or subposition, runs an animation to scroll to the target 624 * position. 625 */ 626 public void setSelectedPositionSmoothWithSub(int position, int subposition) { 627 mLayoutManager.setSelectionSmoothWithSub(position, subposition); 628 } 629 630 /** 631 * Perform a task on ViewHolder at given position after smooth scrolling to it. 632 * @param position Position of item in adapter. 633 * @param task Task to executed on the ViewHolder at a given position. 634 */ 635 public void setSelectedPositionSmooth(final int position, final ViewHolderTask task) { 636 if (task != null) { 637 RecyclerView.ViewHolder vh = findViewHolderForPosition(position); 638 if (vh == null || hasPendingAdapterUpdates()) { 639 addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { 640 @Override 641 public void onChildViewHolderSelected(RecyclerView parent, 642 RecyclerView.ViewHolder child, int selectedPosition, int subposition) { 643 if (selectedPosition == position) { 644 removeOnChildViewHolderSelectedListener(this); 645 task.run(child); 646 } 647 } 648 }); 649 } else { 650 task.run(vh); 651 } 652 } 653 setSelectedPositionSmooth(position); 654 } 655 656 /** 657 * Perform a task on ViewHolder at given position after scroll to it. 658 * @param position Position of item in adapter. 659 * @param task Task to executed on the ViewHolder at a given position. 660 */ 661 public void setSelectedPosition(final int position, final ViewHolderTask task) { 662 if (task != null) { 663 RecyclerView.ViewHolder vh = findViewHolderForPosition(position); 664 if (vh == null || hasPendingAdapterUpdates()) { 665 addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { 666 @Override 667 public void onChildViewHolderSelectedAndPositioned(RecyclerView parent, 668 RecyclerView.ViewHolder child, int selectedPosition, int subposition) { 669 if (selectedPosition == position) { 670 removeOnChildViewHolderSelectedListener(this); 671 task.run(child); 672 } 673 } 674 }); 675 } else { 676 task.run(vh); 677 } 678 } 679 setSelectedPosition(position); 680 } 681 682 /** 683 * Returns the selected item position. 684 */ 685 public int getSelectedPosition() { 686 return mLayoutManager.getSelection(); 687 } 688 689 /** 690 * Returns the sub selected item position started from zero. An item can have 691 * multiple {@link ItemAlignmentFacet}s provided by {@link RecyclerView.ViewHolder} 692 * or {@link FacetProviderAdapter}. Zero is returned when no {@link ItemAlignmentFacet} 693 * is defined. 694 */ 695 public int getSelectedSubPosition() { 696 return mLayoutManager.getSubSelection(); 697 } 698 699 /** 700 * Sets whether an animation should run when a child changes size or when adding 701 * or removing a child. 702 * <p><i>Unstable API, might change later.</i> 703 */ 704 public void setAnimateChildLayout(boolean animateChildLayout) { 705 if (mAnimateChildLayout != animateChildLayout) { 706 mAnimateChildLayout = animateChildLayout; 707 if (!mAnimateChildLayout) { 708 mSavedItemAnimator = getItemAnimator(); 709 super.setItemAnimator(null); 710 } else { 711 super.setItemAnimator(mSavedItemAnimator); 712 } 713 } 714 } 715 716 /** 717 * Returns true if an animation will run when a child changes size or when 718 * adding or removing a child. 719 * <p><i>Unstable API, might change later.</i> 720 */ 721 public boolean isChildLayoutAnimated() { 722 return mAnimateChildLayout; 723 } 724 725 /** 726 * Sets the gravity used for child view positioning. Defaults to 727 * GRAVITY_TOP|GRAVITY_START. 728 * 729 * @param gravity See {@link android.view.Gravity} 730 */ 731 public void setGravity(int gravity) { 732 mLayoutManager.setGravity(gravity); 733 requestLayout(); 734 } 735 736 @Override 737 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 738 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 739 previouslyFocusedRect); 740 } 741 742 /** 743 * Returns the x/y offsets to final position from current position if the view 744 * is selected. 745 * 746 * @param view The view to get offsets. 747 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y. 748 */ 749 public void getViewSelectedOffsets(View view, int[] offsets) { 750 mLayoutManager.getViewSelectedOffsets(view, offsets); 751 } 752 753 @Override 754 public int getChildDrawingOrder(int childCount, int i) { 755 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 756 } 757 758 final boolean isChildrenDrawingOrderEnabledInternal() { 759 return isChildrenDrawingOrderEnabled(); 760 } 761 762 @Override 763 public View focusSearch(int direction) { 764 if (isFocused()) { 765 // focusSearch(int) is called when GridView itself is focused. 766 // Calling focusSearch(view, int) to get next sibling of current selected child. 767 View view = mLayoutManager.findViewByPosition(mLayoutManager.getSelection()); 768 if (view != null) { 769 return focusSearch(view, direction); 770 } 771 } 772 // otherwise, go to mParent to perform focusSearch 773 return super.focusSearch(direction); 774 } 775 776 @Override 777 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 778 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 779 mLayoutManager.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 780 } 781 782 /** 783 * Disables or enables focus search. 784 */ 785 public final void setFocusSearchDisabled(boolean disabled) { 786 // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment 787 // re-gain focus after a BACK key pressed, so block children focus during transition. 788 setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS); 789 mLayoutManager.setFocusSearchDisabled(disabled); 790 } 791 792 /** 793 * Returns true if focus search is disabled. 794 */ 795 public final boolean isFocusSearchDisabled() { 796 return mLayoutManager.isFocusSearchDisabled(); 797 } 798 799 /** 800 * Enables or disables layout. All children will be removed when layout is 801 * disabled. 802 */ 803 public void setLayoutEnabled(boolean layoutEnabled) { 804 mLayoutManager.setLayoutEnabled(layoutEnabled); 805 } 806 807 /** 808 * Changes and overrides children's visibility. 809 */ 810 public void setChildrenVisibility(int visibility) { 811 mLayoutManager.setChildrenVisibility(visibility); 812 } 813 814 /** 815 * Enables or disables pruning of children. Disable is useful during transition. 816 */ 817 public void setPruneChild(boolean pruneChild) { 818 mLayoutManager.setPruneChild(pruneChild); 819 } 820 821 /** 822 * Enables or disables scrolling. Disable is useful during transition. 823 */ 824 public void setScrollEnabled(boolean scrollEnabled) { 825 mLayoutManager.setScrollEnabled(scrollEnabled); 826 } 827 828 /** 829 * Returns true if scrolling is enabled. 830 */ 831 public boolean isScrollEnabled() { 832 return mLayoutManager.isScrollEnabled(); 833 } 834 835 /** 836 * Returns true if the view at the given position has a same row sibling 837 * in front of it. This will return true if first item view is not created. 838 * So application should check in both {@link OnChildSelectedListener} and {@link 839 * OnChildLaidOutListener}. 840 * 841 * @param position Position in adapter. 842 */ 843 public boolean hasPreviousViewInSameRow(int position) { 844 return mLayoutManager.hasPreviousViewInSameRow(position); 845 } 846 847 /** 848 * Enables or disables the default "focus draw at last" order rule. 849 */ 850 public void setFocusDrawingOrderEnabled(boolean enabled) { 851 super.setChildrenDrawingOrderEnabled(enabled); 852 } 853 854 /** 855 * Returns true if default "focus draw at last" order rule is enabled. 856 */ 857 public boolean isFocusDrawingOrderEnabled() { 858 return super.isChildrenDrawingOrderEnabled(); 859 } 860 861 /** 862 * Sets the touch intercept listener. 863 */ 864 public void setOnTouchInterceptListener(OnTouchInterceptListener listener) { 865 mOnTouchInterceptListener = listener; 866 } 867 868 /** 869 * Sets the generic motion intercept listener. 870 */ 871 public void setOnMotionInterceptListener(OnMotionInterceptListener listener) { 872 mOnMotionInterceptListener = listener; 873 } 874 875 /** 876 * Sets the key intercept listener. 877 */ 878 public void setOnKeyInterceptListener(OnKeyInterceptListener listener) { 879 mOnKeyInterceptListener = listener; 880 } 881 882 /** 883 * Sets the unhandled key listener. 884 */ 885 public void setOnUnhandledKeyListener(OnUnhandledKeyListener listener) { 886 mOnUnhandledKeyListener = listener; 887 } 888 889 /** 890 * Returns the unhandled key listener. 891 */ 892 public OnUnhandledKeyListener getOnUnhandledKeyListener() { 893 return mOnUnhandledKeyListener; 894 } 895 896 @Override 897 public boolean dispatchKeyEvent(KeyEvent event) { 898 if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) { 899 return true; 900 } 901 if (super.dispatchKeyEvent(event)) { 902 return true; 903 } 904 return mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event); 905 } 906 907 @Override 908 public boolean dispatchTouchEvent(MotionEvent event) { 909 if (mOnTouchInterceptListener != null) { 910 if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) { 911 return true; 912 } 913 } 914 return super.dispatchTouchEvent(event); 915 } 916 917 @Override 918 public boolean dispatchGenericFocusedEvent(MotionEvent event) { 919 if (mOnMotionInterceptListener != null) { 920 if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) { 921 return true; 922 } 923 } 924 return super.dispatchGenericFocusedEvent(event); 925 } 926 927 /** 928 * Returns the policy for saving children. 929 * 930 * @return policy, one of {@link #SAVE_NO_CHILD} 931 * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 932 */ 933 public final int getSaveChildrenPolicy() { 934 return mLayoutManager.mChildrenStates.getSavePolicy(); 935 } 936 937 /** 938 * Returns the limit used when when {@link #getSaveChildrenPolicy()} is 939 * {@link #SAVE_LIMITED_CHILD} 940 */ 941 public final int getSaveChildrenLimitNumber() { 942 return mLayoutManager.mChildrenStates.getLimitNumber(); 943 } 944 945 /** 946 * Sets the policy for saving children. 947 * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD} 948 * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 949 */ 950 public final void setSaveChildrenPolicy(int savePolicy) { 951 mLayoutManager.mChildrenStates.setSavePolicy(savePolicy); 952 } 953 954 /** 955 * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}. 956 */ 957 public final void setSaveChildrenLimitNumber(int limitNumber) { 958 mLayoutManager.mChildrenStates.setLimitNumber(limitNumber); 959 } 960 961 @Override 962 public boolean hasOverlappingRendering() { 963 return mHasOverlappingRendering; 964 } 965 966 public void setHasOverlappingRendering(boolean hasOverlapping) { 967 mHasOverlappingRendering = hasOverlapping; 968 } 969 970 /** 971 * Notify layout manager that layout directionality has been updated 972 */ 973 @Override 974 public void onRtlPropertiesChanged(int layoutDirection) { 975 mLayoutManager.onRtlPropertiesChanged(layoutDirection); 976 } 977 978 @Override 979 public void setRecyclerListener(RecyclerView.RecyclerListener listener) { 980 mChainedRecyclerListener = listener; 981 } 982 983 /** 984 * Sets pixels of extra space for layout child in invisible area. 985 * 986 * @param extraLayoutSpace Pixels of extra space for layout invisible child. 987 * Must be bigger or equals to 0. 988 * @hide 989 */ 990 @RestrictTo(LIBRARY_GROUP) 991 public void setExtraLayoutSpace(int extraLayoutSpace) { 992 mLayoutManager.setExtraLayoutSpace(extraLayoutSpace); 993 } 994 995 /** 996 * Returns pixels of extra space for layout child in invisible area. 997 * 998 * @hide 999 */ 1000 @RestrictTo(LIBRARY_GROUP) 1001 public int getExtraLayoutSpace() { 1002 return mLayoutManager.getExtraLayoutSpace(); 1003 } 1004 1005 /** 1006 * Temporarily slide out child views to bottom (for VerticalGridView) or end 1007 * (for HorizontalGridView). Layout and scrolling will be suppressed until 1008 * {@link #animateIn()} is called. 1009 */ 1010 public void animateOut() { 1011 mLayoutManager.slideOut(); 1012 } 1013 1014 /** 1015 * Undo animateOut() and slide in child views. 1016 */ 1017 public void animateIn() { 1018 mLayoutManager.slideIn(); 1019 } 1020 1021 @Override 1022 public void scrollToPosition(int position) { 1023 // dont abort the animateOut() animation, just record the position 1024 if (mLayoutManager.mIsSlidingChildViews) { 1025 mLayoutManager.setSelectionWithSub(position, 0, 0); 1026 return; 1027 } 1028 super.scrollToPosition(position); 1029 } 1030 1031 @Override 1032 public void smoothScrollToPosition(int position) { 1033 // dont abort the animateOut() animation, just record the position 1034 if (mLayoutManager.mIsSlidingChildViews) { 1035 mLayoutManager.setSelectionWithSub(position, 0, 0); 1036 return; 1037 } 1038 super.smoothScrollToPosition(position); 1039 } 1040 1041 /** 1042 * Sets the number of items to prefetch in 1043 * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)}, 1044 * which defines how many inner items should be prefetched when this GridView is nested inside 1045 * another RecyclerView. 1046 * 1047 * <p>Set this value to the number of items this inner GridView will display when it is 1048 * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items 1049 * so they are ready, avoiding jank as the inner GridView is scrolled into the viewport.</p> 1050 * 1051 * <p>For example, take a VerticalGridView of scrolling HorizontalGridViews. The rows always 1052 * have 6 items visible in them (or 7 if not aligned). Passing <code>6</code> to this method 1053 * for each inner GridView will enable RecyclerView's prefetching feature to do create/bind work 1054 * for 6 views within a row early, before it is scrolled on screen, instead of just the default 1055 * 4.</p> 1056 * 1057 * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView 1058 * nested in another RecyclerView.</p> 1059 * 1060 * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of 1061 * views that will be visible in this view can incur unnecessary bind work, and an increase to 1062 * the number of Views created and in active use.</p> 1063 * 1064 * @param itemCount Number of items to prefetch 1065 * 1066 * @see #getInitialItemPrefetchCount() 1067 * @see RecyclerView.LayoutManager#isItemPrefetchEnabled() 1068 * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry) 1069 */ 1070 public void setInitialPrefetchItemCount(int itemCount) { 1071 mInitialItemPrefetchCount = itemCount; 1072 } 1073 1074 /** 1075 * Gets the number of items to prefetch in 1076 * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)}, 1077 * which defines how many inner items should be prefetched when this GridView is nested inside 1078 * another RecyclerView. 1079 * 1080 * @see RecyclerView.LayoutManager#isItemPrefetchEnabled() 1081 * @see #setInitialPrefetchItemCount(int) 1082 * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry) 1083 * 1084 * @return number of items to prefetch. 1085 */ 1086 public int getInitialItemPrefetchCount() { 1087 return mInitialItemPrefetchCount; 1088 } 1089} 1090