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