BaseGridView.java revision 0fff85d7f9dee67ec5116f3cba4e8b3961f805a7
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 RecyclerView.ItemAnimator mSavedItemAnimator; 166 167 private OnTouchInterceptListener mOnTouchInterceptListener; 168 private OnMotionInterceptListener mOnMotionInterceptListener; 169 private OnKeyInterceptListener mOnKeyInterceptListener; 170 171 public BaseGridView(Context context, AttributeSet attrs, int defStyle) { 172 super(context, attrs, defStyle); 173 mLayoutManager = new GridLayoutManager(this); 174 setLayoutManager(mLayoutManager); 175 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 176 setHasFixedSize(true); 177 setChildrenDrawingOrderEnabled(true); 178 setWillNotDraw(true); 179 setOverScrollMode(View.OVER_SCROLL_NEVER); 180 } 181 182 protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) { 183 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView); 184 boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false); 185 boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false); 186 mLayoutManager.setFocusOutAllowed(throughFront, throughEnd); 187 mLayoutManager.setVerticalMargin( 188 a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0)); 189 mLayoutManager.setHorizontalMargin( 190 a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0)); 191 if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) { 192 setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY)); 193 } 194 a.recycle(); 195 } 196 197 /** 198 * Set the strategy used to scroll in response to item focus changing: 199 * <ul> 200 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 201 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 202 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 203 * </ul> 204 */ 205 public void setFocusScrollStrategy(int scrollStrategy) { 206 if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM 207 && scrollStrategy != FOCUS_SCROLL_PAGE) { 208 throw new IllegalArgumentException("Invalid scrollStrategy"); 209 } 210 mLayoutManager.setFocusScrollStrategy(scrollStrategy); 211 requestLayout(); 212 } 213 214 /** 215 * Returns the strategy used to scroll in response to item focus changing. 216 * <ul> 217 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 218 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 219 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 220 * </ul> 221 */ 222 public int getFocusScrollStrategy() { 223 return mLayoutManager.getFocusScrollStrategy(); 224 } 225 226 /** 227 * Set how the focused item gets aligned in the view. 228 * 229 * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE}, 230 * {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or 231 * {@link #WINDOW_ALIGN_NO_EDGE}. 232 */ 233 public void setWindowAlignment(int windowAlignment) { 234 mLayoutManager.setWindowAlignment(windowAlignment); 235 requestLayout(); 236 } 237 238 /** 239 * Get how the focused item gets aligned in the view. 240 * 241 * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE}, 242 * {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}. 243 */ 244 public int getWindowAlignment() { 245 return mLayoutManager.getWindowAlignment(); 246 } 247 248 /** 249 * Set the absolute offset in pixels for window alignment. 250 * 251 * @param offset The number of pixels to offset. Can be negative for 252 * alignment from the high edge, or positive for alignment from the 253 * low edge. 254 */ 255 public void setWindowAlignmentOffset(int offset) { 256 mLayoutManager.setWindowAlignmentOffset(offset); 257 requestLayout(); 258 } 259 260 /** 261 * Get the absolute offset in pixels for window alignment. 262 * 263 * @return The number of pixels to offset. Will be negative for alignment 264 * from the high edge, or positive for alignment from the low edge. 265 * Default value is 0. 266 */ 267 public int getWindowAlignmentOffset() { 268 return mLayoutManager.getWindowAlignmentOffset(); 269 } 270 271 /** 272 * Set offset percent for window alignment in addition to {@link 273 * #getWindowAlignmentOffset()}. 274 * 275 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 276 * width from low edge. Use 277 * {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 278 */ 279 public void setWindowAlignmentOffsetPercent(float offsetPercent) { 280 mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent); 281 requestLayout(); 282 } 283 284 /** 285 * Get offset percent for window alignment in addition to 286 * {@link #getWindowAlignmentOffset()}. 287 * 288 * @return Percentage to offset. E.g., 40 means 40% of the width from the 289 * low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if 290 * disabled. Default value is 50. 291 */ 292 public float getWindowAlignmentOffsetPercent() { 293 return mLayoutManager.getWindowAlignmentOffsetPercent(); 294 } 295 296 /** 297 * Set the absolute offset in pixels for item alignment. 298 * 299 * @param offset The number of pixels to offset. Can be negative for 300 * alignment from the high edge, or positive for alignment from the 301 * low edge. 302 */ 303 public void setItemAlignmentOffset(int offset) { 304 mLayoutManager.setItemAlignmentOffset(offset); 305 requestLayout(); 306 } 307 308 /** 309 * Get the absolute offset in pixels for item alignment. 310 * 311 * @return The number of pixels to offset. Will be negative for alignment 312 * from the high edge, or positive for alignment from the low edge. 313 * Default value is 0. 314 */ 315 public int getItemAlignmentOffset() { 316 return mLayoutManager.getItemAlignmentOffset(); 317 } 318 319 /** 320 * Set to true if include padding in calculating item align offset. 321 * 322 * @param withPadding When it is true: we include left/top padding for positive 323 * item offset, include right/bottom padding for negative item offset. 324 */ 325 public void setItemAlignmentOffsetWithPadding(boolean withPadding) { 326 mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding); 327 requestLayout(); 328 } 329 330 /** 331 * Returns true if include padding in calculating item align offset. 332 */ 333 public boolean isItemAlignmentOffsetWithPadding() { 334 return mLayoutManager.isItemAlignmentOffsetWithPadding(); 335 } 336 337 /** 338 * Set offset percent for item alignment in addition to {@link 339 * #getItemAlignmentOffset()}. 340 * 341 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 342 * width from the low edge. Use 343 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 344 */ 345 public void setItemAlignmentOffsetPercent(float offsetPercent) { 346 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 347 requestLayout(); 348 } 349 350 /** 351 * Get offset percent for item alignment in addition to {@link 352 * #getItemAlignmentOffset()}. 353 * 354 * @return Percentage to offset. E.g., 40 means 40% of the width from the 355 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 356 * disabled. Default value is 50. 357 */ 358 public float getItemAlignmentOffsetPercent() { 359 return mLayoutManager.getItemAlignmentOffsetPercent(); 360 } 361 362 /** 363 * Set the id of the view to align with. Use zero (default) for the item 364 * view itself. 365 */ 366 public void setItemAlignmentViewId(int viewId) { 367 mLayoutManager.setItemAlignmentViewId(viewId); 368 } 369 370 /** 371 * Get the id of the view to align with, or zero for the item view itself. 372 */ 373 public int getItemAlignmentViewId() { 374 return mLayoutManager.getItemAlignmentViewId(); 375 } 376 377 /** 378 * Set the margin in pixels between two child items. 379 */ 380 public void setItemMargin(int margin) { 381 mLayoutManager.setItemMargin(margin); 382 requestLayout(); 383 } 384 385 /** 386 * Set the margin in pixels between two child items vertically. 387 */ 388 public void setVerticalMargin(int margin) { 389 mLayoutManager.setVerticalMargin(margin); 390 requestLayout(); 391 } 392 393 /** 394 * Get the margin in pixels between two child items vertically. 395 */ 396 public int getVerticalMargin() { 397 return mLayoutManager.getVerticalMargin(); 398 } 399 400 /** 401 * Set the margin in pixels between two child items horizontally. 402 */ 403 public void setHorizontalMargin(int margin) { 404 mLayoutManager.setHorizontalMargin(margin); 405 requestLayout(); 406 } 407 408 /** 409 * Get the margin in pixels between two child items horizontally. 410 */ 411 public int getHorizontalMargin() { 412 return mLayoutManager.getHorizontalMargin(); 413 } 414 415 /** 416 * Register a callback to be invoked when an item in BaseGridView has 417 * been selected. Note that the listener may be invoked when there is a 418 * layout pending on the view, affording the listener an opportunity to 419 * adjust the upcoming layout based on the selection state. 420 * 421 * @param listener The listener to be invoked. 422 */ 423 public void setOnChildSelectedListener(OnChildSelectedListener listener) { 424 mLayoutManager.setOnChildSelectedListener(listener); 425 } 426 427 /** 428 * Change the selected item immediately without animation. 429 */ 430 public void setSelectedPosition(int position) { 431 mLayoutManager.setSelection(this, position); 432 } 433 434 /** 435 * Change the selected item and run an animation to scroll to the target 436 * position. 437 */ 438 public void setSelectedPositionSmooth(int position) { 439 mLayoutManager.setSelectionSmooth(this, position); 440 } 441 442 /** 443 * Get the selected item position. 444 */ 445 public int getSelectedPosition() { 446 return mLayoutManager.getSelection(); 447 } 448 449 /** 450 * Set if an animation should run when a child changes size or when adding 451 * or removing a child. 452 * <p><i>Unstable API, might change later.</i> 453 */ 454 public void setAnimateChildLayout(boolean animateChildLayout) { 455 if (mAnimateChildLayout != animateChildLayout) { 456 mAnimateChildLayout = animateChildLayout; 457 if (!mAnimateChildLayout) { 458 mSavedItemAnimator = getItemAnimator(); 459 super.setItemAnimator(null); 460 } else { 461 super.setItemAnimator(mSavedItemAnimator); 462 } 463 } 464 } 465 466 /** 467 * Return true if an animation will run when a child changes size or when 468 * adding or removing a child. 469 * <p><i>Unstable API, might change later.</i> 470 */ 471 public boolean isChildLayoutAnimated() { 472 return mAnimateChildLayout; 473 } 474 475 /** 476 * Describes how the child views are positioned. Defaults to 477 * GRAVITY_TOP|GRAVITY_LEFT. 478 * 479 * @param gravity See {@link android.view.Gravity} 480 */ 481 public void setGravity(int gravity) { 482 mLayoutManager.setGravity(gravity); 483 requestLayout(); 484 } 485 486 @Override 487 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 488 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 489 previouslyFocusedRect); 490 } 491 492 /** 493 * Get the x/y offsets to final position from current position if the view 494 * is selected. 495 * 496 * @param view The view to get offsets. 497 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of 498 * Y. 499 */ 500 public void getViewSelectedOffsets(View view, int[] offsets) { 501 mLayoutManager.getViewSelectedOffsets(view, offsets); 502 } 503 504 @Override 505 public int getChildDrawingOrder(int childCount, int i) { 506 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 507 } 508 509 final boolean isChildrenDrawingOrderEnabledInternal() { 510 return isChildrenDrawingOrderEnabled(); 511 } 512 513 /** 514 * Disable or enable focus search. 515 */ 516 public final void setFocusSearchDisabled(boolean disabled) { 517 mLayoutManager.setFocusSearchDisabled(disabled); 518 } 519 520 /** 521 * Return true if focus search is disabled. 522 */ 523 public final boolean isFocusSearchDisabled() { 524 return mLayoutManager.isFocusSearchDisabled(); 525 } 526 527 /** 528 * Enable or disable layout. All children will be removed when layout is 529 * disabled. 530 */ 531 public void setLayoutEnabled(boolean layoutEnabled) { 532 mLayoutManager.setLayoutEnabled(layoutEnabled); 533 } 534 535 /** 536 * Change and override children's visibility. 537 */ 538 public void setChildrenVisibility(int visibility) { 539 mLayoutManager.setChildrenVisibility(visibility); 540 } 541 542 /** 543 * Enable or disable pruning child. Disable is useful during transition. 544 */ 545 public void setPruneChild(boolean pruneChild) { 546 mLayoutManager.setPruneChild(pruneChild); 547 } 548 549 /** 550 * Enable or disable scrolling. Disable is useful during transition. 551 */ 552 public void setScrollEnabled(boolean scrollEnabled) { 553 mLayoutManager.setScrollEnabled(scrollEnabled); 554 } 555 556 /** 557 * Returns true if scrolling is enabled. 558 */ 559 public boolean isScrollEnabled() { 560 return mLayoutManager.isScrollEnabled(); 561 } 562 563 /** 564 * Returns true if the view at the given position has a same row sibling 565 * in front of it. 566 * 567 * @param position Position in adapter. 568 */ 569 public boolean hasPreviousViewInSameRow(int position) { 570 return mLayoutManager.hasPreviousViewInSameRow(position); 571 } 572 573 /** 574 * Enable or disable the default "focus draw at last" order rule. 575 */ 576 public void setFocusDrawingOrderEnabled(boolean enabled) { 577 super.setChildrenDrawingOrderEnabled(enabled); 578 } 579 580 /** 581 * Returns true if default "focus draw at last" order rule is enabled. 582 */ 583 public boolean isFocusDrawingOrderEnabled() { 584 return super.isChildrenDrawingOrderEnabled(); 585 } 586 587 /** 588 * Sets the touch intercept listener. 589 */ 590 public void setOnTouchInterceptListener(OnTouchInterceptListener listener) { 591 mOnTouchInterceptListener = listener; 592 } 593 594 /** 595 * Sets the generic motion intercept listener. 596 */ 597 public void setOnMotionInterceptListener(OnMotionInterceptListener listener) { 598 mOnMotionInterceptListener = listener; 599 } 600 601 /** 602 * Sets the key intercept listener. 603 */ 604 public void setOnKeyInterceptListener(OnKeyInterceptListener listener) { 605 mOnKeyInterceptListener = listener; 606 } 607 608 @Override 609 public boolean dispatchKeyEvent(KeyEvent event) { 610 if (mOnKeyInterceptListener != null) { 611 if (mOnKeyInterceptListener.onInterceptKeyEvent(event)) { 612 return true; 613 } 614 } 615 return super.dispatchKeyEvent(event); 616 } 617 618 @Override 619 public boolean dispatchTouchEvent(MotionEvent event) { 620 if (mOnTouchInterceptListener != null) { 621 if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) { 622 return true; 623 } 624 } 625 return super.dispatchTouchEvent(event); 626 } 627 628 @Override 629 public boolean dispatchGenericFocusedEvent(MotionEvent event) { 630 if (mOnMotionInterceptListener != null) { 631 if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) { 632 return true; 633 } 634 } 635 return super.dispatchGenericFocusedEvent(event); 636 } 637 638 /** 639 * @return policy for saving children. One of {@link #SAVE_NO_CHILD} 640 * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 641 */ 642 public final int getSaveChildrenPolicy() { 643 return mLayoutManager.mChildrenStates.getSavePolicy(); 644 } 645 646 /** 647 * @return The limit number when {@link #getSaveChildrenPolicy()} is 648 * {@link #SAVE_LIMITED_CHILD} 649 */ 650 public final int getSaveChildrenLimitNumber() { 651 return mLayoutManager.mChildrenStates.getLimitNumber(); 652 } 653 654 /** 655 * Set policy for saving children. 656 * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD} 657 * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}. 658 */ 659 public final void setSaveChildrenPolicy(int savePolicy) { 660 mLayoutManager.mChildrenStates.setSavePolicy(savePolicy); 661 } 662 663 /** 664 * Set limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}. 665 */ 666 public final void setSaveChildrenLimitNumber(int limitNumber) { 667 mLayoutManager.mChildrenStates.setLimitNumber(limitNumber); 668 } 669 670 /** 671 * Set the factor by which children should be laid out beyond the view bounds 672 * in the direction of orientation. 1.0 disables over reach. 673 * 674 * @param fraction fraction of over reach 675 */ 676 public final void setPrimaryOverReach(float fraction) { 677 mLayoutManager.setPrimaryOverReach(fraction); 678 } 679} 680