BaseGridView.java revision 2f97594742886d045ca1ce409ebc6e6e780452f6
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 offset percent for item alignment in addition to {@link 255 * #getItemAlignmentOffset()}. 256 * 257 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 258 * width from the low edge. Use 259 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 260 */ 261 public void setItemAlignmentOffsetPercent(float offsetPercent) { 262 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 263 requestLayout(); 264 } 265 266 /** 267 * Get offset percent for item alignment in addition to {@link 268 * #getItemAlignmentOffset()}. 269 * 270 * @return Percentage to offset. E.g., 40 means 40% of the width from the 271 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 272 * disabled. Default value is 50. 273 */ 274 public float getItemAlignmentOffsetPercent() { 275 return mLayoutManager.getItemAlignmentOffsetPercent(); 276 } 277 278 /** 279 * Set the id of the view to align with. Use zero (default) for the item 280 * view itself. 281 */ 282 public void setItemAlignmentViewId(int viewId) { 283 mLayoutManager.setItemAlignmentViewId(viewId); 284 } 285 286 /** 287 * Get the id of the view to align with, or zero for the item view itself. 288 */ 289 public int getItemAlignmentViewId() { 290 return mLayoutManager.getItemAlignmentViewId(); 291 } 292 293 /** 294 * Set the margin in pixels between two child items. 295 */ 296 public void setItemMargin(int margin) { 297 mLayoutManager.setItemMargin(margin); 298 requestLayout(); 299 } 300 301 /** 302 * Set the margin in pixels between two child items vertically. 303 */ 304 public void setVerticalMargin(int margin) { 305 mLayoutManager.setVerticalMargin(margin); 306 requestLayout(); 307 } 308 309 /** 310 * Get the margin in pixels between two child items vertically. 311 */ 312 public int getVerticalMargin() { 313 return mLayoutManager.getVerticalMargin(); 314 } 315 316 /** 317 * Set the margin in pixels between two child items horizontally. 318 */ 319 public void setHorizontalMargin(int margin) { 320 mLayoutManager.setHorizontalMargin(margin); 321 requestLayout(); 322 } 323 324 /** 325 * Get the margin in pixels between two child items horizontally. 326 */ 327 public int getHorizontalMargin() { 328 return mLayoutManager.getHorizontalMargin(); 329 } 330 331 /** 332 * Register a callback to be invoked when an item in BaseGridView has 333 * been selected. 334 */ 335 public void setOnChildSelectedListener(OnChildSelectedListener listener) { 336 mLayoutManager.setOnChildSelectedListener(listener); 337 } 338 339 /** 340 * Change the selected item immediately without animation. 341 */ 342 public void setSelectedPosition(int position) { 343 mLayoutManager.setSelection(this, position); 344 } 345 346 /** 347 * Change the selected item and run an animation to scroll to the target 348 * position. 349 */ 350 public void setSelectedPositionSmooth(int position) { 351 mLayoutManager.setSelectionSmooth(this, position); 352 } 353 354 /** 355 * Get the selected item position. 356 */ 357 public int getSelectedPosition() { 358 return mLayoutManager.getSelection(); 359 } 360 361 /** 362 * Set if an animation should run when a child changes size or when adding 363 * or removing a child. 364 * <p><i>Unstable API, might change later.</i> 365 */ 366 public void setAnimateChildLayout(boolean animateChildLayout) { 367 mLayoutManager.setAnimateChildLayout(animateChildLayout); 368 } 369 370 /** 371 * Return true if an animation will run when a child changes size or when 372 * adding or removing a child. 373 * <p><i>Unstable API, might change later.</i> 374 */ 375 public boolean isChildLayoutAnimated() { 376 return mLayoutManager.isChildLayoutAnimated(); 377 } 378 379 /** 380 * Set an interpolator for the animation when a child changes size or when 381 * adding or removing a child. 382 * <p><i>Unstable API, might change later.</i> 383 */ 384 public void setChildLayoutAnimationInterpolator(Interpolator interpolator) { 385 mLayoutManager.setChildLayoutAnimationInterpolator(interpolator); 386 } 387 388 /** 389 * Get the interpolator for the animation 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 Interpolator getChildLayoutAnimationInterpolator() { 394 return mLayoutManager.getChildLayoutAnimationInterpolator(); 395 } 396 397 /** 398 * Set the duration of 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 setChildLayoutAnimationDuration(long duration) { 403 mLayoutManager.setChildLayoutAnimationDuration(duration); 404 } 405 406 /** 407 * Get the duration of 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 long getChildLayoutAnimationDuration() { 412 return mLayoutManager.getChildLayoutAnimationDuration(); 413 } 414 415 /** 416 * Describes how the child views are positioned. Defaults to 417 * GRAVITY_TOP|GRAVITY_LEFT. 418 * 419 * @param gravity See {@link android.view.Gravity} 420 */ 421 public void setGravity(int gravity) { 422 mLayoutManager.setGravity(gravity); 423 requestLayout(); 424 } 425 426 @Override 427 public void setDescendantFocusability (int focusability) { 428 // enforce FOCUS_AFTER_DESCENDANTS 429 super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 430 } 431 432 @Override 433 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 434 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 435 previouslyFocusedRect); 436 } 437 438 /** 439 * Get the x/y offsets to final position from current position if the view 440 * is selected. 441 * 442 * @param view The view to get offsets. 443 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of 444 * Y. 445 */ 446 public void getViewSelectedOffsets(View view, int[] offsets) { 447 mLayoutManager.getViewSelectedOffsets(view, offsets); 448 } 449 450 @Override 451 public int getChildDrawingOrder(int childCount, int i) { 452 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 453 } 454 455 final boolean isChildrenDrawingOrderEnabledInternal() { 456 return isChildrenDrawingOrderEnabled(); 457 } 458 459 /** 460 * Disable or enable focus search. 461 */ 462 public final void setFocusSearchDisabled(boolean disabled) { 463 mLayoutManager.setFocusSearchDisabled(disabled); 464 } 465 466 /** 467 * Return true if focus search is disabled. 468 */ 469 public final boolean isFocusSearchDisabled() { 470 return mLayoutManager.isFocusSearchDisabled(); 471 } 472 473 /** 474 * Enable or disable layout. All children will be removed when layout is 475 * disabled. 476 */ 477 public void setLayoutEnabled(boolean layoutEnabled) { 478 mLayoutManager.setLayoutEnabled(layoutEnabled); 479 } 480 481 /** 482 * Get if view has same row sibling next to it. 483 * 484 * @param position Position in adapter. 485 */ 486 public boolean hasNextViewInSameRow(int position) { 487 return mLayoutManager.hasNextViewInSameRow(position); 488 } 489 490 /** 491 * Get if view has same row sibling in front of it. 492 * 493 * @param position Position in adapter. 494 */ 495 public boolean hasPreviousViewInSameRow(int position) { 496 return mLayoutManager.hasPreviousViewInSameRow(position); 497 } 498} 499