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