BaseGridView.java revision 9421aa6ca7de4174ddbe2e10fbb05cb31685ffcc
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 private int[] mMeasuredSize = new int[2]; 108 109 public BaseGridView(Context context, AttributeSet attrs, int defStyle) { 110 super(context, attrs, defStyle); 111 mLayoutManager = new GridLayoutManager(this); 112 setLayoutManager(mLayoutManager); 113 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 114 setHasFixedSize(true); 115 setChildrenDrawingOrderEnabled(true); 116 } 117 118 protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) { 119 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView); 120 boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false); 121 boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false); 122 mLayoutManager.setFocusOutAllowed(throughFront, throughEnd); 123 mLayoutManager.setVerticalMargin( 124 a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0)); 125 mLayoutManager.setHorizontalMargin( 126 a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0)); 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 offset percent for item alignment in addition to {@link 254 * #getItemAlignmentOffset()}. 255 * 256 * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the 257 * width from the low edge. Use 258 * {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable. 259 */ 260 public void setItemAlignmentOffsetPercent(float offsetPercent) { 261 mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent); 262 requestLayout(); 263 } 264 265 /** 266 * Get offset percent for item alignment in addition to {@link 267 * #getItemAlignmentOffset()}. 268 * 269 * @return Percentage to offset. E.g., 40 means 40% of the width from the 270 * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if 271 * disabled. Default value is 50. 272 */ 273 public float getItemAlignmentOffsetPercent() { 274 return mLayoutManager.getItemAlignmentOffsetPercent(); 275 } 276 277 /** 278 * Set the id of the view to align with. Use zero (default) for the item 279 * view itself. 280 */ 281 public void setItemAlignmentViewId(int viewId) { 282 mLayoutManager.setItemAlignmentViewId(viewId); 283 } 284 285 /** 286 * Get the id of the view to align with, or zero for the item view itself. 287 */ 288 public int getItemAlignmentViewId() { 289 return mLayoutManager.getItemAlignmentViewId(); 290 } 291 292 /** 293 * Set the margin in pixels between two child items. 294 */ 295 public void setItemMargin(int margin) { 296 mLayoutManager.setItemMargin(margin); 297 requestLayout(); 298 } 299 300 /** 301 * Set the margin in pixels between two child items vertically. 302 */ 303 public void setVerticalMargin(int margin) { 304 mLayoutManager.setVerticalMargin(margin); 305 requestLayout(); 306 } 307 308 /** 309 * Get the margin in pixels between two child items vertically. 310 */ 311 public int getVerticalMargin() { 312 return mLayoutManager.getVerticalMargin(); 313 } 314 315 /** 316 * Set the margin in pixels between two child items horizontally. 317 */ 318 public void setHorizontalMargin(int margin) { 319 mLayoutManager.setHorizontalMargin(margin); 320 requestLayout(); 321 } 322 323 /** 324 * Get the margin in pixels between two child items horizontally. 325 */ 326 public int getHorizontalMargin() { 327 return mLayoutManager.getHorizontalMargin(); 328 } 329 330 /** 331 * Register a callback to be invoked when an item in BaseGridView has 332 * been selected. 333 */ 334 public void setOnChildSelectedListener(OnChildSelectedListener listener) { 335 mLayoutManager.setOnChildSelectedListener(listener); 336 } 337 338 /** 339 * Change the selected item immediately without animation. 340 */ 341 public void setSelectedPosition(int position) { 342 mLayoutManager.setSelection(this, position); 343 } 344 345 /** 346 * Change the selected item and run an animation to scroll to the target 347 * position. 348 */ 349 public void setSelectedPositionSmooth(int position) { 350 mLayoutManager.setSelectionSmooth(this, position); 351 } 352 353 /** 354 * Get the selected item position. 355 */ 356 public int getSelectedPosition() { 357 return mLayoutManager.getSelection(); 358 } 359 360 /** 361 * Set if an animation should run when a child changes size or when adding 362 * or removing a child. 363 * <p><i>Unstable API, might change later.</i> 364 */ 365 public void setAnimateChildLayout(boolean animateChildLayout) { 366 mLayoutManager.setAnimateChildLayout(animateChildLayout); 367 } 368 369 /** 370 * Return true if an animation will run when a child changes size or when 371 * adding or removing a child. 372 * <p><i>Unstable API, might change later.</i> 373 */ 374 public boolean isChildLayoutAnimated() { 375 return mLayoutManager.isChildLayoutAnimated(); 376 } 377 378 /** 379 * Set an interpolator for the animation when a child changes size or when 380 * adding or removing a child. 381 * <p><i>Unstable API, might change later.</i> 382 */ 383 public void setChildLayoutAnimationInterpolator(Interpolator interpolator) { 384 mLayoutManager.setChildLayoutAnimationInterpolator(interpolator); 385 } 386 387 /** 388 * Get the interpolator for the animation 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 Interpolator getChildLayoutAnimationInterpolator() { 393 return mLayoutManager.getChildLayoutAnimationInterpolator(); 394 } 395 396 /** 397 * Set the duration of the animation when a child changes size or when 398 * adding or removing a child. 399 * <p><i>Unstable API, might change later.</i> 400 */ 401 public void setChildLayoutAnimationDuration(long duration) { 402 mLayoutManager.setChildLayoutAnimationDuration(duration); 403 } 404 405 /** 406 * Get the duration of the animation when a child changes size or when 407 * adding or removing a child. 408 * <p><i>Unstable API, might change later.</i> 409 */ 410 public long getChildLayoutAnimationDuration() { 411 return mLayoutManager.getChildLayoutAnimationDuration(); 412 } 413 414 /** 415 * Describes how the child views are positioned. Defaults to 416 * GRAVITY_TOP|GRAVITY_LEFT. 417 * 418 * @param gravity See {@link android.view.Gravity} 419 */ 420 public void setGravity(int gravity) { 421 mLayoutManager.setGravity(gravity); 422 requestLayout(); 423 } 424 425 @Override 426 protected final void onMeasure(int widthSpec, int heightSpec) { 427 mLayoutManager.gridOnMeasure(widthSpec, heightSpec, mMeasuredSize); 428 setMeasuredDimension(mMeasuredSize[0], mMeasuredSize[1]); 429 } 430 431 @Override 432 public void setDescendantFocusability (int focusability) { 433 // enforce FOCUS_AFTER_DESCENDANTS 434 super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 435 } 436 437 @Override 438 public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 439 return mLayoutManager.gridOnRequestFocusInDescendants(this, direction, 440 previouslyFocusedRect); 441 } 442 443 /** 444 * Get the x/y offsets to final position from current position if the view 445 * is selected. 446 * 447 * @param view The view to get offsets. 448 * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of 449 * Y. 450 */ 451 public void getViewSelectedOffsets(View view, int[] offsets) { 452 mLayoutManager.getViewSelectedOffsets(view, offsets); 453 } 454 455 @Override 456 public int getChildDrawingOrder(int childCount, int i) { 457 return mLayoutManager.getChildDrawingOrder(this, childCount, i); 458 } 459 460 final boolean isChildrenDrawingOrderEnabledInternal() { 461 return isChildrenDrawingOrderEnabled(); 462 } 463} 464