BaseGridView.java revision ceb7ab2ddd6e157cd4ade0f14a382c39428163c4
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 android.content.Context; 17import android.content.res.TypedArray; 18import android.graphics.Rect; 19import android.support.v17.leanback.R; 20import android.support.v7.widget.RecyclerView; 21import android.util.AttributeSet; 22import android.view.Gravity; 23import android.view.KeyEvent; 24import android.view.MotionEvent; 25import android.view.View; 26 27/** 28 * Base class for vertically and horizontally scrolling lists. The items come 29 * from the {@link RecyclerView.Adapter} associated with this view. 30 * @hide 31 */ 32abstract class BaseGridView extends RecyclerView { 33 34 /** 35 * Always keep focused item at a aligned position. Developer can use 36 * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned. 37 * In this mode, the last focused position will be remembered and restored when focus 38 * is back to the view. 39 */ 40 public final static int FOCUS_SCROLL_ALIGNED = 0; 41 42 /** 43 * Scroll to make the focused item inside client area. 44 */ 45 public final static int FOCUS_SCROLL_ITEM = 1; 46 47 /** 48 * Scroll a page of items when focusing to item outside the client area. 49 * The page size matches the client area size of RecyclerView. 50 */ 51 public final static int FOCUS_SCROLL_PAGE = 2; 52 53 /** 54 * The first item is aligned with the low edge of the viewport. When 55 * navigating away from the first item, the focus maintains a middle 56 * location. 57 * <p> 58 * The middle location is calculated by "windowAlignOffset" and 59 * "windowAlignOffsetPercent"; if neither of these two is defined, the 60 * default value is 1/2 of the size. 61 */ 62 public final static int WINDOW_ALIGN_LOW_EDGE = 1; 63 64 /** 65 * The last item is aligned with the high edge of the viewport when 66 * navigating to the end of list. When navigating away from the end, the 67 * focus maintains a middle location. 68 * <p> 69 * The middle location is calculated by "windowAlignOffset" and 70 * "windowAlignOffsetPercent"; if neither of these two is defined, the 71 * default value is 1/2 of the size. 72 */ 73 public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1; 74 75 /** 76 * The first item and last item are aligned with the two edges of the 77 * viewport. When navigating in the middle of list, the focus maintains a 78 * middle location. 79 * <p> 80 * The middle location is calculated by "windowAlignOffset" and 81 * "windowAlignOffsetPercent"; if neither of these two is defined, the 82 * default value is 1/2 of the size. 83 */ 84 public final static int WINDOW_ALIGN_BOTH_EDGE = 85 WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE; 86 87 /** 88 * The focused item always stays in a 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_NO_EDGE = 0; 95 96 /** 97 * Value indicates that percent is not used. 98 */ 99 public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1; 100 101 /** 102 * Value indicates that percent is not used. 103 */ 104 public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1; 105 106 /** 107 * Dont save states of any child views. 108 */ 109 public static final int SAVE_NO_CHILD = 0; 110 111 /** 112 * Only save on screen child views, the states are lost when they become off screen. 113 */ 114 public static final int SAVE_ON_SCREEN_CHILD = 1; 115 116 /** 117 * Save on screen views plus save off screen child views states up to 118 * {@link #getSaveChildrenLimitNumber()}. 119 */ 120 public static final int SAVE_LIMITED_CHILD = 2; 121 122 /** 123 * Save on screen views plus save off screen child views without any limitation. 124 * This might cause out of memory, only use it when you are dealing with limited data. 125 */ 126 public static final int SAVE_ALL_CHILD = 3; 127 128 /** 129 * Listener for intercepting touch dispatch events. 130 */ 131 public interface OnTouchInterceptListener { 132 /** 133 * Returns true if the touch dispatch event should be consumed. 134 */ 135 public boolean onInterceptTouchEvent(MotionEvent event); 136 } 137 138 /** 139 * Listener for intercepting generic motion dispatch events. 140 */ 141 public interface OnMotionInterceptListener { 142 /** 143 * Returns true if the touch dispatch event should be consumed. 144 */ 145 public boolean onInterceptMotionEvent(MotionEvent event); 146 } 147 148 /** 149 * Listener for intercepting key dispatch events. 150 */ 151 public interface OnKeyInterceptListener { 152 /** 153 * Returns true if the key dispatch event should be consumed. 154 */ 155 public boolean onInterceptKeyEvent(KeyEvent event); 156 } 157 158 protected final GridLayoutManager mLayoutManager; 159 160 /** 161 * Animate layout changes from a child resizing or adding/removing a child. 162 */ 163 private boolean mAnimateChildLayout = true; 164 165 private boolean mHasOverlappingRendering = true; 166 167 private RecyclerView.ItemAnimator mSavedItemAnimator; 168 169 private OnTouchInterceptListener mOnTouchInterceptListener; 170 private OnMotionInterceptListener mOnMotionInterceptListener; 171 private OnKeyInterceptListener mOnKeyInterceptListener; 172 173 public BaseGridView(Context context, AttributeSet attrs, int defStyle) { 174 super(context, attrs, defStyle); 175 mLayoutManager = new GridLayoutManager(this); 176 setLayoutManager(mLayoutManager); 177 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 178 setHasFixedSize(true); 179 setChildrenDrawingOrderEnabled(true); 180 setWillNotDraw(true); 181 setOverScrollMode(View.OVER_SCROLL_NEVER); 182 } 183 184 protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) { 185 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView); 186 boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false); 187 boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false); 188 mLayoutManager.setFocusOutAllowed(throughFront, throughEnd); 189 mLayoutManager.setVerticalMargin( 190 a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0)); 191 mLayoutManager.setHorizontalMargin( 192 a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0)); 193 if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) { 194 setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY)); 195 } 196 a.recycle(); 197 } 198 199 /** 200 * Set the strategy used to scroll in response to item focus changing: 201 * <ul> 202 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 203 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 204 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 205 * </ul> 206 */ 207 public void setFocusScrollStrategy(int scrollStrategy) { 208 if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM 209 && scrollStrategy != FOCUS_SCROLL_PAGE) { 210 throw new IllegalArgumentException("Invalid scrollStrategy"); 211 } 212 mLayoutManager.setFocusScrollStrategy(scrollStrategy); 213 requestLayout(); 214 } 215 216 /** 217 * Returns the strategy used to scroll in response to item focus changing. 218 * <ul> 219 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 220 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 221 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 222 * </ul> 223 */ 224 public int getFocusScrollStrategy() { 225 return mLayoutManager.getFocusScrollStrategy(); 226 } 227 228 /** 229 * Set how the focused item gets aligned in the view. 230 * 231 * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE}, 232 * {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or 233 * {@link #WINDOW_ALIGN_NO_EDGE}. 234 */ 235 public void setWindowAlignment(int windowAlignment) { 236 mLayoutManager.setWindowAlignment(windowAlignment); 237 requestLayout(); 238 } 239 240 /** 241 * Get how the focused item gets aligned in the view. 242 * 243 * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE}, 244 * {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}. 245 */ 246 public int getWindowAlignment() { 247 return mLayoutManager.getWindowAlignment(); 248 } 249 250 /** 251 * Set the absolute offset in pixels for window alignment. 252 * 253 * @param offset The number of pixels to offset. Can be negative for 254 * alignment from the high edge, or positive for alignment from the 255 * low edge. 256 */ 257 public void setWindowAlignmentOffset(int offset) { 258 mLayoutManager.setWindowAlignmentOffset(offset); 259 requestLayout(); 260 } 261 262 /** 263 * Get the absolute offset in pixels for window alignment. 264 * 265 * @return The number of pixels to offset. Will be negative for alignment 266 * from the high edge, or positive for alignment from the low edge. 267 * Default value is 0. 268 */ 269 public int getWindowAlignmentOffset() { 270 return mLayoutManager.getWindowAlignmentOffset(); 271 } 272 273 /** 274 * Set offset percent for window alignment in addition to {@link 275 * #getWindowAlignmentOffset()}. 276 * 277 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 278 * width from low edge. Use 279 * {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 280 */ 281 public void setWindowAlignmentOffsetPercent(float offsetPercent) { 282 mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent); 283 requestLayout(); 284 } 285 286 /** 287 * Get offset percent for window alignment in addition to 288 * {@link #getWindowAlignmentOffset()}. 289 * 290 * @return Percentage to offset. E.g., 40 means 40% of the width from the 291 * low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if 292 * disabled. Default value is 50. 293 */ 294 public float getWindowAlignmentOffsetPercent() { 295 return mLayoutManager.getWindowAlignmentOffsetPercent(); 296 } 297 298 /** 299 * Set the absolute offset in pixels for item alignment. 300 * 301 * @param offset The number of pixels to offset. Can be negative for 302 * alignment from the high edge, or positive for alignment from the 303 * low edge. 304 */ 305 public void setItemAlignmentOffset(int offset) { 306 mLayoutManager.setItemAlignmentOffset(offset); 307 requestLayout(); 308 } 309 310 /** 311 * Get the absolute offset in pixels for item alignment. 312 * 313 * @return The number of pixels to offset. Will be negative for alignment 314 * from the high edge, or positive for alignment from the low edge. 315 * Default value is 0. 316 */ 317 public int getItemAlignmentOffset() { 318 return mLayoutManager.getItemAlignmentOffset(); 319 } 320 321 /** 322 * Set to true if include padding in calculating item align offset. 323 * 324 * @param withPadding When it is true: we include left/top padding for positive 325 * item offset, include right/bottom padding for negative item offset. 326 */ 327 public void setItemAlignmentOffsetWithPadding(boolean withPadding) { 328 mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding); 329 requestLayout(); 330 } 331 332 /** 333 * Returns true if include padding in calculating item align offset. 334 */ 335 public boolean isItemAlignmentOffsetWithPadding() { 336 return mLayoutManager.isItemAlignmentOffsetWithPadding(); 337 } 338 339 /** 340 * Set offset percent for item alignment in addition to {@link 341 * #getItemAlignmentOffset()}. 342 * 343 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 344 * width from the low edge. Use 345 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 346 */ 347 public void setItemAlignmentOffsetPercent(float offsetPercent) { 348 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 349 requestLayout(); 350 } 351 352 /** 353 * Get offset percent for item alignment in addition to {@link 354 * #getItemAlignmentOffset()}. 355 * 356 * @return Percentage to offset. E.g., 40 means 40% of the width from the 357 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 358 * disabled. Default value is 50. 359 */ 360 public float getItemAlignmentOffsetPercent() { 361 return mLayoutManager.getItemAlignmentOffsetPercent(); 362 } 363 364 /** 365 * Set the id of the view to align with. Use zero (default) for the item 366 * view itself. 367 */ 368 public void setItemAlignmentViewId(int viewId) { 369 mLayoutManager.setItemAlignmentViewId(viewId); 370 } 371 372 /** 373 * Get the id of the view to align with, or zero for the item view itself. 374 */ 375 public int getItemAlignmentViewId() { 376 return mLayoutManager.getItemAlignmentViewId(); 377 } 378 379 /** 380 * Set the margin in pixels between two child items. 381 */ 382 public void setItemMargin(int margin) { 383 mLayoutManager.setItemMargin(margin); 384 requestLayout(); 385 } 386 387 /** 388 * Set the margin in pixels between two child items vertically. 389 */ 390 public void setVerticalMargin(int margin) { 391 mLayoutManager.setVerticalMargin(margin); 392 requestLayout(); 393 } 394 395 /** 396 * Get the margin in pixels between two child items vertically. 397 */ 398 public int getVerticalMargin() { 399 return mLayoutManager.getVerticalMargin(); 400 } 401 402 /** 403 * Set the margin in pixels between two child items horizontally. 404 */ 405 public void setHorizontalMargin(int margin) { 406 mLayoutManager.setHorizontalMargin(margin); 407 requestLayout(); 408 } 409 410 /** 411 * Get the margin in pixels between two child items horizontally. 412 */ 413 public int getHorizontalMargin() { 414 return mLayoutManager.getHorizontalMargin(); 415 } 416 417 /** 418 * Register a callback to be invoked when an item in BaseGridView has 419 * been selected. Note that the listener may be invoked when there is a 420 * layout pending on the view, affording the listener an opportunity to 421 * adjust the upcoming layout based on the selection state. 422 * 423 * @param listener The listener to be invoked. 424 */ 425 public void setOnChildSelectedListener(OnChildSelectedListener listener) { 426 mLayoutManager.setOnChildSelectedListener(listener); 427 } 428 429 /** 430 * Change the selected item immediately without animation. 431 */ 432 public void setSelectedPosition(int position) { 433 mLayoutManager.setSelection(this, position); 434 } 435 436 /** 437 * Change the selected item and run an animation to scroll to the target 438 * position. 439 */ 440 public void setSelectedPositionSmooth(int position) { 441 mLayoutManager.setSelectionSmooth(this, position); 442 } 443 444 /** 445 * Get the selected item position. 446 */ 447 public int getSelectedPosition() { 448 return mLayoutManager.getSelection(); 449 } 450 451 /** 452 * Set if an animation should run when a child changes size or when adding 453 * or removing a child. 454 * <p><i>Unstable API, might change later.</i> 455 */ 456 public void setAnimateChildLayout(boolean animateChildLayout) { 457 if (mAnimateChildLayout != animateChildLayout) { 458 mAnimateChildLayout = animateChildLayout; 459 if (!mAnimateChildLayout) { 460 mSavedItemAnimator = getItemAnimator(); 461 super.setItemAnimator(null); 462 } else { 463 super.setItemAnimator(mSavedItemAnimator); 464 } 465 } 466 } 467 468 /** 469 * Return true if an animation will run when a child changes size or when 470 * adding or removing a child. 471 * <p><i>Unstable API, might change later.</i> 472 */ 473 public boolean isChildLayoutAnimated() { 474 return mAnimateChildLayout; 475 } 476 477 /** 478 * Describes how the child views are positioned. Defaults to 479 * GRAVITY_TOP|GRAVITY_LEFT. 480 * 481 * @param gravity See {@link android.view.Gravity} 482 */ 483 public void setGravity(int gravity) { 484 mLayoutManager.setGravity(gravity); 485 requestLayout(); 486 } 487 488 @Override 489 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 490 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 491 previouslyFocusedRect); 492 } 493 494 /** 495 * Get the x/y offsets to final position from current position if the view 496 * is selected. 497 * 498 * @param view The view to get offsets. 499 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of 500 * Y. 501 */ 502 public void getViewSelectedOffsets(View view, int[] offsets) { 503 mLayoutManager.getViewSelectedOffsets(view, offsets); 504 } 505 506 @Override 507 public int getChildDrawingOrder(int childCount, int i) { 508 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 509 } 510 511 final boolean isChildrenDrawingOrderEnabledInternal() { 512 return isChildrenDrawingOrderEnabled(); 513 } 514 515 /** 516 * Disable or enable focus search. 517 */ 518 public final void setFocusSearchDisabled(boolean disabled) { 519 mLayoutManager.setFocusSearchDisabled(disabled); 520 } 521 522 /** 523 * Return true if focus search is disabled. 524 */ 525 public final boolean isFocusSearchDisabled() { 526 return mLayoutManager.isFocusSearchDisabled(); 527 } 528 529 /** 530 * Enable or disable layout. All children will be removed when layout is 531 * disabled. 532 */ 533 public void setLayoutEnabled(boolean layoutEnabled) { 534 mLayoutManager.setLayoutEnabled(layoutEnabled); 535 } 536 537 /** 538 * Change and override children's visibility. 539 */ 540 public void setChildrenVisibility(int visibility) { 541 mLayoutManager.setChildrenVisibility(visibility); 542 } 543 544 /** 545 * Enable or disable pruning child. Disable is useful during transition. 546 */ 547 public void setPruneChild(boolean pruneChild) { 548 mLayoutManager.setPruneChild(pruneChild); 549 } 550 551 /** 552 * Enable or disable scrolling. Disable is useful during transition. 553 */ 554 public void setScrollEnabled(boolean scrollEnabled) { 555 mLayoutManager.setScrollEnabled(scrollEnabled); 556 } 557 558 /** 559 * Returns true if scrolling is enabled. 560 */ 561 public boolean isScrollEnabled() { 562 return mLayoutManager.isScrollEnabled(); 563 } 564 565 /** 566 * Returns true if the view at the given position has a same row sibling 567 * in front of it. 568 * 569 * @param position Position in adapter. 570 */ 571 public boolean hasPreviousViewInSameRow(int position) { 572 return mLayoutManager.hasPreviousViewInSameRow(position); 573 } 574 575 /** 576 * Enable or disable the default "focus draw at last" order rule. 577 */ 578 public void setFocusDrawingOrderEnabled(boolean enabled) { 579 super.setChildrenDrawingOrderEnabled(enabled); 580 } 581 582 /** 583 * Returns true if default "focus draw at last" order rule is enabled. 584 */ 585 public boolean isFocusDrawingOrderEnabled() { 586 return super.isChildrenDrawingOrderEnabled(); 587 } 588 589 /** 590 * Sets the touch intercept listener. 591 */ 592 public void setOnTouchInterceptListener(OnTouchInterceptListener listener) { 593 mOnTouchInterceptListener = listener; 594 } 595 596 /** 597 * Sets the generic motion intercept listener. 598 */ 599 public void setOnMotionInterceptListener(OnMotionInterceptListener listener) { 600 mOnMotionInterceptListener = listener; 601 } 602 603 /** 604 * Sets the key intercept listener. 605 */ 606 public void setOnKeyInterceptListener(OnKeyInterceptListener listener) { 607 mOnKeyInterceptListener = listener; 608 } 609 610 @Override 611 public boolean dispatchKeyEvent(KeyEvent event) { 612 if (mOnKeyInterceptListener != null) { 613 if (mOnKeyInterceptListener.onInterceptKeyEvent(event)) { 614 return true; 615 } 616 } 617 return super.dispatchKeyEvent(event); 618 } 619 620 @Override 621 public boolean dispatchTouchEvent(MotionEvent event) { 622 if (mOnTouchInterceptListener != null) { 623 if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) { 624 return true; 625 } 626 } 627 return super.dispatchTouchEvent(event); 628 } 629 630 @Override 631 public boolean dispatchGenericFocusedEvent(MotionEvent event) { 632 if (mOnMotionInterceptListener != null) { 633 if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) { 634 return true; 635 } 636 } 637 return super.dispatchGenericFocusedEvent(event); 638 } 639 640 /** 641 * @return policy for saving children. One of {@link #SAVE_NO_CHILD} 642 * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 643 */ 644 public final int getSaveChildrenPolicy() { 645 return mLayoutManager.mChildrenStates.getSavePolicy(); 646 } 647 648 /** 649 * @return The limit number when {@link #getSaveChildrenPolicy()} is 650 * {@link #SAVE_LIMITED_CHILD} 651 */ 652 public final int getSaveChildrenLimitNumber() { 653 return mLayoutManager.mChildrenStates.getLimitNumber(); 654 } 655 656 /** 657 * Set policy for saving children. 658 * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD} 659 * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 660 */ 661 public final void setSaveChildrenPolicy(int savePolicy) { 662 mLayoutManager.mChildrenStates.setSavePolicy(savePolicy); 663 } 664 665 /** 666 * Set limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}. 667 */ 668 public final void setSaveChildrenLimitNumber(int limitNumber) { 669 mLayoutManager.mChildrenStates.setLimitNumber(limitNumber); 670 } 671 672 /** 673 * Set the factor by which children should be laid out beyond the view bounds 674 * in the direction of orientation. 1.0 disables over reach. 675 * 676 * @param fraction fraction of over reach 677 */ 678 public final void setPrimaryOverReach(float fraction) { 679 mLayoutManager.setPrimaryOverReach(fraction); 680 } 681 682 @Override 683 public boolean hasOverlappingRendering() { 684 return mHasOverlappingRendering; 685 } 686 687 public void setHasOverlappingRendering(boolean hasOverlapping) { 688 mHasOverlappingRendering = hasOverlapping; 689 } 690} 691