BaseGridView.java revision e34cae48707e70442aca13e1b4ab55757292828d
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.View; 23import android.view.ViewGroup; 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 a.recycle(); 126 } 127 128 /** 129 * Set the strategy used to scroll in response to item focus changing: 130 * <ul> 131 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 132 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 133 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 134 * </ul> 135 */ 136 public void setFocusScrollStrategy(int scrollStrategy) { 137 if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM 138 && scrollStrategy != FOCUS_SCROLL_PAGE) { 139 throw new IllegalArgumentException("Invalid scrollStrategy"); 140 } 141 mLayoutManager.setFocusScrollStrategy(scrollStrategy); 142 requestLayout(); 143 } 144 145 /** 146 * Returns the strategy used to scroll in response to item focus changing. 147 * <ul> 148 * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li> 149 * <li>{@link #FOCUS_SCROLL_ITEM}</li> 150 * <li>{@link #FOCUS_SCROLL_PAGE}</li> 151 * </ul> 152 */ 153 public int getFocusScrollStrategy() { 154 return mLayoutManager.getFocusScrollStrategy(); 155 } 156 157 /** 158 * Set how the focused item gets aligned in the view. 159 * 160 * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE}, 161 * {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or 162 * {@link #WINDOW_ALIGN_NO_EDGE}. 163 */ 164 public void setWindowAlignment(int windowAlignment) { 165 mLayoutManager.setWindowAlignment(windowAlignment); 166 requestLayout(); 167 } 168 169 /** 170 * Get how the focused item gets aligned in the view. 171 * 172 * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE}, 173 * {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}. 174 */ 175 public int getWindowAlignment() { 176 return mLayoutManager.getWindowAlignment(); 177 } 178 179 /** 180 * Set the absolute offset in pixels for window alignment. 181 * 182 * @param offset The number of pixels to offset. Can be negative for 183 * alignment from the high edge, or positive for alignment from the 184 * low edge. 185 */ 186 public void setWindowAlignmentOffset(int offset) { 187 mLayoutManager.setWindowAlignmentOffset(offset); 188 requestLayout(); 189 } 190 191 /** 192 * Get the absolute offset in pixels for window alignment. 193 * 194 * @return The number of pixels to offset. Will be negative for alignment 195 * from the high edge, or positive for alignment from the low edge. 196 * Default value is 0. 197 */ 198 public int getWindowAlignmentOffset() { 199 return mLayoutManager.getWindowAlignmentOffset(); 200 } 201 202 /** 203 * Set offset percent for window alignment in addition to {@link 204 * #getWindowAlignmentOffset()}. 205 * 206 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 207 * width from low edge. Use 208 * {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 209 */ 210 public void setWindowAlignmentOffsetPercent(float offsetPercent) { 211 mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent); 212 requestLayout(); 213 } 214 215 /** 216 * Get offset percent for window alignment in addition to 217 * {@link #getWindowAlignmentOffset()}. 218 * 219 * @return Percentage to offset. E.g., 40 means 40% of the width from the 220 * low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if 221 * disabled. Default value is 50. 222 */ 223 public float getWindowAlignmentOffsetPercent() { 224 return mLayoutManager.getWindowAlignmentOffsetPercent(); 225 } 226 227 /** 228 * Set the absolute offset in pixels for item alignment. 229 * 230 * @param offset The number of pixels to offset. Can be negative for 231 * alignment from the high edge, or positive for alignment from the 232 * low edge. 233 */ 234 public void setItemAlignmentOffset(int offset) { 235 mLayoutManager.setItemAlignmentOffset(offset); 236 requestLayout(); 237 } 238 239 /** 240 * Get the absolute offset in pixels for item alignment. 241 * 242 * @return The number of pixels to offset. Will be negative for alignment 243 * from the high edge, or positive for alignment from the low edge. 244 * Default value is 0. 245 */ 246 public int getItemAlignmentOffset() { 247 return mLayoutManager.getItemAlignmentOffset(); 248 } 249 250 /** 251 * Set offset percent for item alignment in addition to {@link 252 * #getItemAlignmentOffset()}. 253 * 254 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 255 * width from the low edge. Use 256 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 257 */ 258 public void setItemAlignmentOffsetPercent(float offsetPercent) { 259 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 260 requestLayout(); 261 } 262 263 /** 264 * Get offset percent for item alignment in addition to {@link 265 * #getItemAlignmentOffset()}. 266 * 267 * @return Percentage to offset. E.g., 40 means 40% of the width from the 268 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 269 * disabled. Default value is 50. 270 */ 271 public float getItemAlignmentOffsetPercent() { 272 return mLayoutManager.getItemAlignmentOffsetPercent(); 273 } 274 275 /** 276 * Set the id of the view to align with. Use zero (default) for the item 277 * view itself. 278 */ 279 public void setItemAlignmentViewId(int viewId) { 280 mLayoutManager.setItemAlignmentViewId(viewId); 281 } 282 283 /** 284 * Get the id of the view to align with, or zero for the item view itself. 285 */ 286 public int getItemAlignmentViewId() { 287 return mLayoutManager.getItemAlignmentViewId(); 288 } 289 290 /** 291 * Set the margin in pixels between two child items. 292 */ 293 public void setItemMargin(int margin) { 294 mLayoutManager.setItemMargin(margin); 295 requestLayout(); 296 } 297 298 /** 299 * Set the margin in pixels between two child items vertically. 300 */ 301 public void setVerticalMargin(int margin) { 302 mLayoutManager.setVerticalMargin(margin); 303 requestLayout(); 304 } 305 306 /** 307 * Get the margin in pixels between two child items vertically. 308 */ 309 public int getVerticalMargin() { 310 return mLayoutManager.getVerticalMargin(); 311 } 312 313 /** 314 * Set the margin in pixels between two child items horizontally. 315 */ 316 public void setHorizontalMargin(int margin) { 317 mLayoutManager.setHorizontalMargin(margin); 318 requestLayout(); 319 } 320 321 /** 322 * Get the margin in pixels between two child items horizontally. 323 */ 324 public int getHorizontalMargin() { 325 return mLayoutManager.getHorizontalMargin(); 326 } 327 328 /** 329 * Register a callback to be invoked when an item in BaseGridView has 330 * been selected. 331 */ 332 public void setOnChildSelectedListener(OnChildSelectedListener listener) { 333 mLayoutManager.setOnChildSelectedListener(listener); 334 } 335 336 /** 337 * Change the selected item immediately without animation. 338 */ 339 public void setSelectedPosition(int position) { 340 mLayoutManager.setSelection(this, position); 341 } 342 343 /** 344 * Change the selected item and run an animation to scroll to the target 345 * position. 346 */ 347 public void setSelectedPositionSmooth(int position) { 348 mLayoutManager.setSelectionSmooth(this, position); 349 } 350 351 /** 352 * Get the selected item position. 353 */ 354 public int getSelectedPosition() { 355 return mLayoutManager.getSelection(); 356 } 357 358 /** 359 * Set if an animation should run when a child changes size or when adding 360 * or removing a child. 361 * <p><i>Unstable API, might change later.</i> 362 */ 363 public void setAnimateChildLayout(boolean animateChildLayout) { 364 mLayoutManager.setAnimateChildLayout(animateChildLayout); 365 } 366 367 /** 368 * Return true if an animation will run when a child changes size or when 369 * adding or removing a child. 370 * <p><i>Unstable API, might change later.</i> 371 */ 372 public boolean isChildLayoutAnimated() { 373 return mLayoutManager.isChildLayoutAnimated(); 374 } 375 376 /** 377 * Set an interpolator for the animation when a child changes size or when 378 * adding or removing a child. 379 * <p><i>Unstable API, might change later.</i> 380 */ 381 public void setChildLayoutAnimationInterpolator(Interpolator interpolator) { 382 mLayoutManager.setChildLayoutAnimationInterpolator(interpolator); 383 } 384 385 /** 386 * Get the interpolator for the animation when a child changes size or when 387 * adding or removing a child. 388 * <p><i>Unstable API, might change later.</i> 389 */ 390 public Interpolator getChildLayoutAnimationInterpolator() { 391 return mLayoutManager.getChildLayoutAnimationInterpolator(); 392 } 393 394 /** 395 * Set the duration of the animation when a child changes size or when 396 * adding or removing a child. 397 * <p><i>Unstable API, might change later.</i> 398 */ 399 public void setChildLayoutAnimationDuration(long duration) { 400 mLayoutManager.setChildLayoutAnimationDuration(duration); 401 } 402 403 /** 404 * Get the duration of the animation when a child changes size or when 405 * adding or removing a child. 406 * <p><i>Unstable API, might change later.</i> 407 */ 408 public long getChildLayoutAnimationDuration() { 409 return mLayoutManager.getChildLayoutAnimationDuration(); 410 } 411 412 /** 413 * Describes how the child views are positioned. Defaults to 414 * GRAVITY_TOP|GRAVITY_LEFT. 415 * 416 * @param gravity See {@link android.view.Gravity} 417 */ 418 public void setGravity(int gravity) { 419 mLayoutManager.setGravity(gravity); 420 requestLayout(); 421 } 422 423 @Override 424 public void setDescendantFocusability (int focusability) { 425 // enforce FOCUS_AFTER_DESCENDANTS 426 super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 427 } 428 429 @Override 430 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 431 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 432 previouslyFocusedRect); 433 } 434 435 /** 436 * Get the x/y offsets to final position from current position if the view 437 * is selected. 438 * 439 * @param view The view to get offsets. 440 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of 441 * Y. 442 */ 443 public void getViewSelectedOffsets(View view, int[] offsets) { 444 mLayoutManager.getViewSelectedOffsets(view, offsets); 445 } 446 447 @Override 448 public int getChildDrawingOrder(int childCount, int i) { 449 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 450 } 451 452 final boolean isChildrenDrawingOrderEnabledInternal() { 453 return isChildrenDrawingOrderEnabled(); 454 } 455 456 /** 457 * Disable or enable focus search. 458 */ 459 public final void setFocusSearchDisabled(boolean disabled) { 460 mLayoutManager.setFocusSearchDisabled(disabled); 461 } 462 463 /** 464 * Return true if focus search is disabled. 465 */ 466 public final boolean isFocusSearchDisabled() { 467 return mLayoutManager.isFocusSearchDisabled(); 468 } 469 470 /** 471 * Get if view has same row sibling next to it. 472 * 473 * @param position Position in adapter. 474 */ 475 public boolean hasNextViewInSameRow(int position) { 476 return mLayoutManager.hasNextViewInSameRow(position); 477 } 478 479 /** 480 * Get if view has same row sibling in front of it. 481 * 482 * @param position Position in adapter. 483 */ 484 public boolean hasPreviousViewInSameRow(int position) { 485 return mLayoutManager.hasPreviousViewInSameRow(position); 486 } 487} 488