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