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