BaseGridView.java revision 709bb7083a089e788d84ffa81f2c4f60a1bc8cf2
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 * Listener for intercepting touch dispatch events. 108 */ 109 public interface OnTouchInterceptListener { 110 /** 111 * Returns true if the touch dispatch event should be consumed. 112 */ 113 public boolean onInterceptTouchEvent(MotionEvent event); 114 } 115 116 /** 117 * Listener for intercepting generic motion dispatch events. 118 */ 119 public interface OnMotionInterceptListener { 120 /** 121 * Returns true if the touch dispatch event should be consumed. 122 */ 123 public boolean onInterceptMotionEvent(MotionEvent event); 124 } 125 126 /** 127 * Listener for intercepting key dispatch events. 128 */ 129 public interface OnKeyInterceptListener { 130 /** 131 * Returns true if the key dispatch event should be consumed. 132 */ 133 public boolean onInterceptKeyEvent(KeyEvent event); 134 } 135 136 protected final GridLayoutManager mLayoutManager; 137 138 /** 139 * Animate layout changes from a child resizing or adding/removing a child. 140 */ 141 private boolean mAnimateChildLayout = true; 142 143 private RecyclerView.ItemAnimator mSavedItemAnimator; 144 145 private OnTouchInterceptListener mOnTouchInterceptListener; 146 private OnMotionInterceptListener mOnMotionInterceptListener; 147 private OnKeyInterceptListener mOnKeyInterceptListener; 148 149 public BaseGridView(Context context, AttributeSet attrs, int defStyle) { 150 super(context, attrs, defStyle); 151 mLayoutManager = new GridLayoutManager(this); 152 setLayoutManager(mLayoutManager); 153 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 154 setHasFixedSize(true); 155 setChildrenDrawingOrderEnabled(true); 156 } 157 158 protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) { 159 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView); 160 boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false); 161 boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false); 162 mLayoutManager.setFocusOutAllowed(throughFront, throughEnd); 163 mLayoutManager.setVerticalMargin( 164 a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0)); 165 mLayoutManager.setHorizontalMargin( 166 a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0)); 167 if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) { 168 setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY)); 169 } 170 a.recycle(); 171 } 172 173 /** 174 * Set the strategy used to scroll in response to item focus changing: 175 * <ul> 176 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 177 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 178 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 179 * </ul> 180 */ 181 public void setFocusScrollStrategy(int scrollStrategy) { 182 if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM 183 && scrollStrategy != FOCUS_SCROLL_PAGE) { 184 throw new IllegalArgumentException("Invalid scrollStrategy"); 185 } 186 mLayoutManager.setFocusScrollStrategy(scrollStrategy); 187 requestLayout(); 188 } 189 190 /** 191 * Returns the strategy used to scroll in response to item focus changing. 192 * <ul> 193 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 194 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 195 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 196 * </ul> 197 */ 198 public int getFocusScrollStrategy() { 199 return mLayoutManager.getFocusScrollStrategy(); 200 } 201 202 /** 203 * Set how the focused item gets aligned in the view. 204 * 205 * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE}, 206 * {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or 207 * {@link #WINDOW_ALIGN_NO_EDGE}. 208 */ 209 public void setWindowAlignment(int windowAlignment) { 210 mLayoutManager.setWindowAlignment(windowAlignment); 211 requestLayout(); 212 } 213 214 /** 215 * Get how the focused item gets aligned in the view. 216 * 217 * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE}, 218 * {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}. 219 */ 220 public int getWindowAlignment() { 221 return mLayoutManager.getWindowAlignment(); 222 } 223 224 /** 225 * Set the absolute offset in pixels for window alignment. 226 * 227 * @param offset The number of pixels to offset. Can be negative for 228 * alignment from the high edge, or positive for alignment from the 229 * low edge. 230 */ 231 public void setWindowAlignmentOffset(int offset) { 232 mLayoutManager.setWindowAlignmentOffset(offset); 233 requestLayout(); 234 } 235 236 /** 237 * Get the absolute offset in pixels for window alignment. 238 * 239 * @return The number of pixels to offset. Will be negative for alignment 240 * from the high edge, or positive for alignment from the low edge. 241 * Default value is 0. 242 */ 243 public int getWindowAlignmentOffset() { 244 return mLayoutManager.getWindowAlignmentOffset(); 245 } 246 247 /** 248 * Set offset percent for window alignment in addition to {@link 249 * #getWindowAlignmentOffset()}. 250 * 251 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 252 * width from low edge. Use 253 * {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 254 */ 255 public void setWindowAlignmentOffsetPercent(float offsetPercent) { 256 mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent); 257 requestLayout(); 258 } 259 260 /** 261 * Get offset percent for window alignment in addition to 262 * {@link #getWindowAlignmentOffset()}. 263 * 264 * @return Percentage to offset. E.g., 40 means 40% of the width from the 265 * low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if 266 * disabled. Default value is 50. 267 */ 268 public float getWindowAlignmentOffsetPercent() { 269 return mLayoutManager.getWindowAlignmentOffsetPercent(); 270 } 271 272 /** 273 * Set the absolute offset in pixels for item alignment. 274 * 275 * @param offset The number of pixels to offset. Can be negative for 276 * alignment from the high edge, or positive for alignment from the 277 * low edge. 278 */ 279 public void setItemAlignmentOffset(int offset) { 280 mLayoutManager.setItemAlignmentOffset(offset); 281 requestLayout(); 282 } 283 284 /** 285 * Get the absolute offset in pixels for item alignment. 286 * 287 * @return The number of pixels to offset. Will be negative for alignment 288 * from the high edge, or positive for alignment from the low edge. 289 * Default value is 0. 290 */ 291 public int getItemAlignmentOffset() { 292 return mLayoutManager.getItemAlignmentOffset(); 293 } 294 295 /** 296 * Set to true if include padding in calculating item align offset. 297 * 298 * @param withPadding When it is true: we include left/top padding for positive 299 * item offset, include right/bottom padding for negative item offset. 300 */ 301 public void setItemAlignmentOffsetWithPadding(boolean withPadding) { 302 mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding); 303 requestLayout(); 304 } 305 306 /** 307 * Returns true if include padding in calculating item align offset. 308 */ 309 public boolean isItemAlignmentOffsetWithPadding() { 310 return mLayoutManager.isItemAlignmentOffsetWithPadding(); 311 } 312 313 /** 314 * Set offset percent for item alignment in addition to {@link 315 * #getItemAlignmentOffset()}. 316 * 317 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 318 * width from the low edge. Use 319 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 320 */ 321 public void setItemAlignmentOffsetPercent(float offsetPercent) { 322 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 323 requestLayout(); 324 } 325 326 /** 327 * Get offset percent for item alignment in addition to {@link 328 * #getItemAlignmentOffset()}. 329 * 330 * @return Percentage to offset. E.g., 40 means 40% of the width from the 331 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 332 * disabled. Default value is 50. 333 */ 334 public float getItemAlignmentOffsetPercent() { 335 return mLayoutManager.getItemAlignmentOffsetPercent(); 336 } 337 338 /** 339 * Set the id of the view to align with. Use zero (default) for the item 340 * view itself. 341 */ 342 public void setItemAlignmentViewId(int viewId) { 343 mLayoutManager.setItemAlignmentViewId(viewId); 344 } 345 346 /** 347 * Get the id of the view to align with, or zero for the item view itself. 348 */ 349 public int getItemAlignmentViewId() { 350 return mLayoutManager.getItemAlignmentViewId(); 351 } 352 353 /** 354 * Set the margin in pixels between two child items. 355 */ 356 public void setItemMargin(int margin) { 357 mLayoutManager.setItemMargin(margin); 358 requestLayout(); 359 } 360 361 /** 362 * Set the margin in pixels between two child items vertically. 363 */ 364 public void setVerticalMargin(int margin) { 365 mLayoutManager.setVerticalMargin(margin); 366 requestLayout(); 367 } 368 369 /** 370 * Get the margin in pixels between two child items vertically. 371 */ 372 public int getVerticalMargin() { 373 return mLayoutManager.getVerticalMargin(); 374 } 375 376 /** 377 * Set the margin in pixels between two child items horizontally. 378 */ 379 public void setHorizontalMargin(int margin) { 380 mLayoutManager.setHorizontalMargin(margin); 381 requestLayout(); 382 } 383 384 /** 385 * Get the margin in pixels between two child items horizontally. 386 */ 387 public int getHorizontalMargin() { 388 return mLayoutManager.getHorizontalMargin(); 389 } 390 391 /** 392 * Register a callback to be invoked when an item in BaseGridView has 393 * been selected. 394 */ 395 public void setOnChildSelectedListener(OnChildSelectedListener listener) { 396 mLayoutManager.setOnChildSelectedListener(listener); 397 } 398 399 /** 400 * Change the selected item immediately without animation. 401 */ 402 public void setSelectedPosition(int position) { 403 mLayoutManager.setSelection(this, position); 404 } 405 406 /** 407 * Change the selected item and run an animation to scroll to the target 408 * position. 409 */ 410 public void setSelectedPositionSmooth(int position) { 411 mLayoutManager.setSelectionSmooth(this, position); 412 } 413 414 /** 415 * Get the selected item position. 416 */ 417 public int getSelectedPosition() { 418 return mLayoutManager.getSelection(); 419 } 420 421 /** 422 * Set if an animation should run when a child changes size or when adding 423 * or removing a child. 424 * <p><i>Unstable API, might change later.</i> 425 */ 426 public void setAnimateChildLayout(boolean animateChildLayout) { 427 if (mAnimateChildLayout != animateChildLayout) { 428 mAnimateChildLayout = animateChildLayout; 429 if (!mAnimateChildLayout) { 430 mSavedItemAnimator = getItemAnimator(); 431 super.setItemAnimator(null); 432 } else { 433 super.setItemAnimator(mSavedItemAnimator); 434 } 435 } 436 } 437 438 /** 439 * Return true if an animation will run when a child changes size or when 440 * adding or removing a child. 441 * <p><i>Unstable API, might change later.</i> 442 */ 443 public boolean isChildLayoutAnimated() { 444 return mAnimateChildLayout; 445 } 446 447 /** 448 * Describes how the child views are positioned. Defaults to 449 * GRAVITY_TOP|GRAVITY_LEFT. 450 * 451 * @param gravity See {@link android.view.Gravity} 452 */ 453 public void setGravity(int gravity) { 454 mLayoutManager.setGravity(gravity); 455 requestLayout(); 456 } 457 458 @Override 459 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 460 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 461 previouslyFocusedRect); 462 } 463 464 /** 465 * Get the x/y offsets to final position from current position if the view 466 * is selected. 467 * 468 * @param view The view to get offsets. 469 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of 470 * Y. 471 */ 472 public void getViewSelectedOffsets(View view, int[] offsets) { 473 mLayoutManager.getViewSelectedOffsets(view, offsets); 474 } 475 476 @Override 477 public int getChildDrawingOrder(int childCount, int i) { 478 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 479 } 480 481 final boolean isChildrenDrawingOrderEnabledInternal() { 482 return isChildrenDrawingOrderEnabled(); 483 } 484 485 /** 486 * Disable or enable focus search. 487 */ 488 public final void setFocusSearchDisabled(boolean disabled) { 489 mLayoutManager.setFocusSearchDisabled(disabled); 490 } 491 492 /** 493 * Return true if focus search is disabled. 494 */ 495 public final boolean isFocusSearchDisabled() { 496 return mLayoutManager.isFocusSearchDisabled(); 497 } 498 499 /** 500 * Enable or disable layout. All children will be removed when layout is 501 * disabled. 502 */ 503 public void setLayoutEnabled(boolean layoutEnabled) { 504 mLayoutManager.setLayoutEnabled(layoutEnabled); 505 } 506 507 /** 508 * Enable or disable pruning child. Disable is useful during transition. 509 */ 510 public void setPruneChild(boolean pruneChild) { 511 mLayoutManager.setPruneChild(pruneChild); 512 } 513 514 /** 515 * Enable or disable scrolling. Disable is useful during transition. 516 */ 517 public void setScrollEnabled(boolean scrollEnabled) { 518 mLayoutManager.setScrollEnabled(scrollEnabled); 519 } 520 521 /** 522 * Returns true if scrolling is enabled. 523 */ 524 public boolean isScrollEnabled() { 525 return mLayoutManager.isScrollEnabled(); 526 } 527 528 /** 529 * Returns true if the view at the given position has a same row sibling 530 * in front of it. 531 * 532 * @param position Position in adapter. 533 */ 534 public boolean hasPreviousViewInSameRow(int position) { 535 return mLayoutManager.hasPreviousViewInSameRow(position); 536 } 537 538 /** 539 * Enable or disable the default "focus draw at last" order rule. 540 */ 541 public void setFocusDrawingOrderEnabled(boolean enabled) { 542 super.setChildrenDrawingOrderEnabled(enabled); 543 } 544 545 /** 546 * Returns true if default "focus draw at last" order rule is enabled. 547 */ 548 public boolean isFocusDrawingOrderEnabled() { 549 return super.isChildrenDrawingOrderEnabled(); 550 } 551 552 /** 553 * Sets the touch intercept listener. 554 */ 555 public void setOnTouchInterceptListener(OnTouchInterceptListener listener) { 556 mOnTouchInterceptListener = listener; 557 } 558 559 /** 560 * Sets the generic motion intercept listener. 561 */ 562 public void setOnMotionInterceptListener(OnMotionInterceptListener listener) { 563 mOnMotionInterceptListener = listener; 564 } 565 566 /** 567 * Sets the key intercept listener. 568 */ 569 public void setOnKeyInterceptListener(OnKeyInterceptListener listener) { 570 mOnKeyInterceptListener = listener; 571 } 572 573 @Override 574 public boolean dispatchKeyEvent(KeyEvent event) { 575 if (mOnKeyInterceptListener != null) { 576 if (mOnKeyInterceptListener.onInterceptKeyEvent(event)) { 577 return true; 578 } 579 } 580 return super.dispatchKeyEvent(event); 581 } 582 583 @Override 584 public boolean dispatchTouchEvent(MotionEvent event) { 585 if (mOnTouchInterceptListener != null) { 586 if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) { 587 return true; 588 } 589 } 590 return super.dispatchTouchEvent(event); 591 } 592 593 @Override 594 public boolean dispatchGenericFocusedEvent(MotionEvent event) { 595 if (mOnMotionInterceptListener != null) { 596 if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) { 597 return true; 598 } 599 } 600 return super.dispatchGenericFocusedEvent(event); 601 } 602} 603