BaseGridView.java revision e43e9266c4b7e4902fefb5d2a0cacca90a3d2681
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.View; 24import android.view.animation.Interpolator; 25 26/** 27 * Base class for vertically and horizontally scrolling lists. The items come 28 * from the {@link RecyclerView.Adapter} associated with this view. 29 * @hide 30 */ 31abstract class BaseGridView extends RecyclerView { 32 33 /** 34 * Always keep focused item at a aligned position. Developer can use 35 * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned. 36 * In this mode, the last focused position will be remembered and restored when focus 37 * is back to the view. 38 */ 39 public final static int FOCUS_SCROLL_ALIGNED = 0; 40 41 /** 42 * Scroll to make the focused item inside client area. 43 */ 44 public final static int FOCUS_SCROLL_ITEM = 1; 45 46 /** 47 * Scroll a page of items when focusing to item outside the client area. 48 * The page size matches the client area size of RecyclerView. 49 */ 50 public final static int FOCUS_SCROLL_PAGE = 2; 51 52 /** 53 * The first item is aligned with the low edge of the viewport. When 54 * navigating away from the first item, the focus maintains a middle 55 * location. 56 * <p> 57 * The middle location is calculated by "windowAlignOffset" and 58 * "windowAlignOffsetPercent"; if neither of these two is defined, the 59 * default value is 1/2 of the size. 60 */ 61 public final static int WINDOW_ALIGN_LOW_EDGE = 1; 62 63 /** 64 * The last item is aligned with the high edge of the viewport when 65 * navigating to the end of list. When navigating away from the end, the 66 * focus maintains a middle location. 67 * <p> 68 * The middle location is calculated by "windowAlignOffset" and 69 * "windowAlignOffsetPercent"; if neither of these two is defined, the 70 * default value is 1/2 of the size. 71 */ 72 public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1; 73 74 /** 75 * The first item and last item are aligned with the two edges of the 76 * viewport. When navigating in the middle of list, the focus maintains a 77 * middle location. 78 * <p> 79 * The middle location is calculated by "windowAlignOffset" and 80 * "windowAlignOffsetPercent"; if neither of these two is defined, the 81 * default value is 1/2 of the size. 82 */ 83 public final static int WINDOW_ALIGN_BOTH_EDGE = 84 WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE; 85 86 /** 87 * The focused item always stays in a 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_NO_EDGE = 0; 94 95 /** 96 * Value indicates that percent is not used. 97 */ 98 public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1; 99 100 /** 101 * Value indicates that percent is not used. 102 */ 103 public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1; 104 105 protected final GridLayoutManager mLayoutManager; 106 107 public BaseGridView(Context context, AttributeSet attrs, int defStyle) { 108 super(context, attrs, defStyle); 109 mLayoutManager = new GridLayoutManager(this); 110 setLayoutManager(mLayoutManager); 111 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 112 setHasFixedSize(true); 113 setChildrenDrawingOrderEnabled(true); 114 } 115 116 protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) { 117 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView); 118 boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false); 119 boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false); 120 mLayoutManager.setFocusOutAllowed(throughFront, throughEnd); 121 mLayoutManager.setVerticalMargin( 122 a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0)); 123 mLayoutManager.setHorizontalMargin( 124 a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0)); 125 if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) { 126 setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY)); 127 } 128 a.recycle(); 129 } 130 131 /** 132 * Set the strategy used to scroll in response to item focus changing: 133 * <ul> 134 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 135 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 136 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 137 * </ul> 138 */ 139 public void setFocusScrollStrategy(int scrollStrategy) { 140 if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM 141 && scrollStrategy != FOCUS_SCROLL_PAGE) { 142 throw new IllegalArgumentException("Invalid scrollStrategy"); 143 } 144 mLayoutManager.setFocusScrollStrategy(scrollStrategy); 145 requestLayout(); 146 } 147 148 /** 149 * Returns the strategy used to scroll in response to item focus changing. 150 * <ul> 151 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 152 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 153 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 154 * </ul> 155 */ 156 public int getFocusScrollStrategy() { 157 return mLayoutManager.getFocusScrollStrategy(); 158 } 159 160 /** 161 * Set how the focused item gets aligned in the view. 162 * 163 * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE}, 164 * {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or 165 * {@link #WINDOW_ALIGN_NO_EDGE}. 166 */ 167 public void setWindowAlignment(int windowAlignment) { 168 mLayoutManager.setWindowAlignment(windowAlignment); 169 requestLayout(); 170 } 171 172 /** 173 * Get how the focused item gets aligned in the view. 174 * 175 * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE}, 176 * {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}. 177 */ 178 public int getWindowAlignment() { 179 return mLayoutManager.getWindowAlignment(); 180 } 181 182 /** 183 * Set the absolute offset in pixels for window alignment. 184 * 185 * @param offset The number of pixels to offset. Can be negative for 186 * alignment from the high edge, or positive for alignment from the 187 * low edge. 188 */ 189 public void setWindowAlignmentOffset(int offset) { 190 mLayoutManager.setWindowAlignmentOffset(offset); 191 requestLayout(); 192 } 193 194 /** 195 * Get the absolute offset in pixels for window alignment. 196 * 197 * @return The number of pixels to offset. Will be negative for alignment 198 * from the high edge, or positive for alignment from the low edge. 199 * Default value is 0. 200 */ 201 public int getWindowAlignmentOffset() { 202 return mLayoutManager.getWindowAlignmentOffset(); 203 } 204 205 /** 206 * Set offset percent for window alignment in addition to {@link 207 * #getWindowAlignmentOffset()}. 208 * 209 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 210 * width from low edge. Use 211 * {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 212 */ 213 public void setWindowAlignmentOffsetPercent(float offsetPercent) { 214 mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent); 215 requestLayout(); 216 } 217 218 /** 219 * Get offset percent for window alignment in addition to 220 * {@link #getWindowAlignmentOffset()}. 221 * 222 * @return Percentage to offset. E.g., 40 means 40% of the width from the 223 * low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if 224 * disabled. Default value is 50. 225 */ 226 public float getWindowAlignmentOffsetPercent() { 227 return mLayoutManager.getWindowAlignmentOffsetPercent(); 228 } 229 230 /** 231 * Set the absolute offset in pixels for item alignment. 232 * 233 * @param offset The number of pixels to offset. Can be negative for 234 * alignment from the high edge, or positive for alignment from the 235 * low edge. 236 */ 237 public void setItemAlignmentOffset(int offset) { 238 mLayoutManager.setItemAlignmentOffset(offset); 239 requestLayout(); 240 } 241 242 /** 243 * Get the absolute offset in pixels for item alignment. 244 * 245 * @return The number of pixels to offset. Will be negative for alignment 246 * from the high edge, or positive for alignment from the low edge. 247 * Default value is 0. 248 */ 249 public int getItemAlignmentOffset() { 250 return mLayoutManager.getItemAlignmentOffset(); 251 } 252 253 /** 254 * Set to true if include padding in calculating item align offset. 255 * 256 * @param withPadding When it is true: we include left/top padding for positive 257 * item offset, include right/bottom padding for negative item offset. 258 */ 259 public void setItemAlignmentOffsetWithPadding(boolean withPadding) { 260 mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding); 261 requestLayout(); 262 } 263 264 /** 265 * Returns true if include padding in calculating item align offset. 266 */ 267 public boolean isItemAlignmentOffsetWithPadding() { 268 return mLayoutManager.isItemAlignmentOffsetWithPadding(); 269 } 270 271 /** 272 * Set offset percent for item alignment in addition to {@link 273 * #getItemAlignmentOffset()}. 274 * 275 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 276 * width from the low edge. Use 277 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 278 */ 279 public void setItemAlignmentOffsetPercent(float offsetPercent) { 280 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 281 requestLayout(); 282 } 283 284 /** 285 * Get offset percent for item alignment in addition to {@link 286 * #getItemAlignmentOffset()}. 287 * 288 * @return Percentage to offset. E.g., 40 means 40% of the width from the 289 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 290 * disabled. Default value is 50. 291 */ 292 public float getItemAlignmentOffsetPercent() { 293 return mLayoutManager.getItemAlignmentOffsetPercent(); 294 } 295 296 /** 297 * Set the id of the view to align with. Use zero (default) for the item 298 * view itself. 299 */ 300 public void setItemAlignmentViewId(int viewId) { 301 mLayoutManager.setItemAlignmentViewId(viewId); 302 } 303 304 /** 305 * Get the id of the view to align with, or zero for the item view itself. 306 */ 307 public int getItemAlignmentViewId() { 308 return mLayoutManager.getItemAlignmentViewId(); 309 } 310 311 /** 312 * Set the margin in pixels between two child items. 313 */ 314 public void setItemMargin(int margin) { 315 mLayoutManager.setItemMargin(margin); 316 requestLayout(); 317 } 318 319 /** 320 * Set the margin in pixels between two child items vertically. 321 */ 322 public void setVerticalMargin(int margin) { 323 mLayoutManager.setVerticalMargin(margin); 324 requestLayout(); 325 } 326 327 /** 328 * Get the margin in pixels between two child items vertically. 329 */ 330 public int getVerticalMargin() { 331 return mLayoutManager.getVerticalMargin(); 332 } 333 334 /** 335 * Set the margin in pixels between two child items horizontally. 336 */ 337 public void setHorizontalMargin(int margin) { 338 mLayoutManager.setHorizontalMargin(margin); 339 requestLayout(); 340 } 341 342 /** 343 * Get the margin in pixels between two child items horizontally. 344 */ 345 public int getHorizontalMargin() { 346 return mLayoutManager.getHorizontalMargin(); 347 } 348 349 /** 350 * Register a callback to be invoked when an item in BaseGridView has 351 * been selected. 352 */ 353 public void setOnChildSelectedListener(OnChildSelectedListener listener) { 354 mLayoutManager.setOnChildSelectedListener(listener); 355 } 356 357 /** 358 * Change the selected item immediately without animation. 359 */ 360 public void setSelectedPosition(int position) { 361 mLayoutManager.setSelection(this, position); 362 } 363 364 /** 365 * Change the selected item and run an animation to scroll to the target 366 * position. 367 */ 368 public void setSelectedPositionSmooth(int position) { 369 mLayoutManager.setSelectionSmooth(this, position); 370 } 371 372 /** 373 * Get the selected item position. 374 */ 375 public int getSelectedPosition() { 376 return mLayoutManager.getSelection(); 377 } 378 379 /** 380 * Set if an animation should run when a child changes size or when adding 381 * or removing a child. 382 * <p><i>Unstable API, might change later.</i> 383 */ 384 public void setAnimateChildLayout(boolean animateChildLayout) { 385 mLayoutManager.setAnimateChildLayout(animateChildLayout); 386 } 387 388 /** 389 * Return true if an animation will run when a child changes size or when 390 * adding or removing a child. 391 * <p><i>Unstable API, might change later.</i> 392 */ 393 public boolean isChildLayoutAnimated() { 394 return mLayoutManager.isChildLayoutAnimated(); 395 } 396 397 /** 398 * Set an interpolator for the animation when a child changes size or when 399 * adding or removing a child. 400 * <p><i>Unstable API, might change later.</i> 401 */ 402 public void setChildLayoutAnimationInterpolator(Interpolator interpolator) { 403 mLayoutManager.setChildLayoutAnimationInterpolator(interpolator); 404 } 405 406 /** 407 * Get the interpolator for the animation when a child changes size or when 408 * adding or removing a child. 409 * <p><i>Unstable API, might change later.</i> 410 */ 411 public Interpolator getChildLayoutAnimationInterpolator() { 412 return mLayoutManager.getChildLayoutAnimationInterpolator(); 413 } 414 415 /** 416 * Set the duration of the animation when a child changes size or when 417 * adding or removing a child. 418 * <p><i>Unstable API, might change later.</i> 419 */ 420 public void setChildLayoutAnimationDuration(long duration) { 421 mLayoutManager.setChildLayoutAnimationDuration(duration); 422 } 423 424 /** 425 * Get the duration of the animation when a child changes size or when 426 * adding or removing a child. 427 * <p><i>Unstable API, might change later.</i> 428 */ 429 public long getChildLayoutAnimationDuration() { 430 return mLayoutManager.getChildLayoutAnimationDuration(); 431 } 432 433 /** 434 * Describes how the child views are positioned. Defaults to 435 * GRAVITY_TOP|GRAVITY_LEFT. 436 * 437 * @param gravity See {@link android.view.Gravity} 438 */ 439 public void setGravity(int gravity) { 440 mLayoutManager.setGravity(gravity); 441 requestLayout(); 442 } 443 444 @Override 445 public void setDescendantFocusability (int focusability) { 446 // enforce FOCUS_AFTER_DESCENDANTS 447 super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 448 } 449 450 @Override 451 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 452 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 453 previouslyFocusedRect); 454 } 455 456 /** 457 * Get the x/y offsets to final position from current position if the view 458 * is selected. 459 * 460 * @param view The view to get offsets. 461 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of 462 * Y. 463 */ 464 public void getViewSelectedOffsets(View view, int[] offsets) { 465 mLayoutManager.getViewSelectedOffsets(view, offsets); 466 } 467 468 @Override 469 public int getChildDrawingOrder(int childCount, int i) { 470 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 471 } 472 473 final boolean isChildrenDrawingOrderEnabledInternal() { 474 return isChildrenDrawingOrderEnabled(); 475 } 476 477 /** 478 * Disable or enable focus search. 479 */ 480 public final void setFocusSearchDisabled(boolean disabled) { 481 mLayoutManager.setFocusSearchDisabled(disabled); 482 } 483 484 /** 485 * Return true if focus search is disabled. 486 */ 487 public final boolean isFocusSearchDisabled() { 488 return mLayoutManager.isFocusSearchDisabled(); 489 } 490 491 /** 492 * Enable or disable layout. All children will be removed when layout is 493 * disabled. 494 */ 495 public void setLayoutEnabled(boolean layoutEnabled) { 496 mLayoutManager.setLayoutEnabled(layoutEnabled); 497 } 498 499 /** 500 * Enable or disable pruning child. Disable is useful during transition. 501 */ 502 public void setPruneChild(boolean pruneChild) { 503 mLayoutManager.setPruneChild(pruneChild); 504 } 505 506 /** 507 * Get if view has same row sibling next to it. 508 * 509 * @param position Position in adapter. 510 */ 511 public boolean hasNextViewInSameRow(int position) { 512 return mLayoutManager.hasNextViewInSameRow(position); 513 } 514 515 /** 516 * Get if view has same row sibling in front of it. 517 * 518 * @param position Position in adapter. 519 */ 520 public boolean hasPreviousViewInSameRow(int position) { 521 return mLayoutManager.hasPreviousViewInSameRow(position); 522 } 523} 524