RecyclerView.java revision a102d8db8bfebe4da3d9a8e204b28527f069e81e
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18package android.support.v7.widget; 19 20import android.content.Context; 21import android.database.Observable; 22import android.graphics.Canvas; 23import android.graphics.Rect; 24import android.os.Build; 25import android.support.v4.util.Pools; 26import android.support.v4.view.MotionEventCompat; 27import android.support.v4.view.VelocityTrackerCompat; 28import android.support.v4.view.ViewCompat; 29import android.support.v4.widget.EdgeEffectCompat; 30import android.support.v4.widget.ScrollerCompat; 31import android.util.AttributeSet; 32import android.util.Log; 33import android.util.SparseArray; 34import android.util.SparseIntArray; 35import android.view.FocusFinder; 36import android.view.MotionEvent; 37import android.view.VelocityTracker; 38import android.view.View; 39import android.view.ViewConfiguration; 40import android.view.ViewGroup; 41import android.view.ViewParent; 42import android.view.animation.Interpolator; 43 44import java.util.ArrayList; 45import java.util.List; 46 47/** 48 * A flexible view for providing a limited window into a large data set. 49 * 50 * <h3>Glossary of terms:</h3> 51 * 52 * <ul> 53 * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views 54 * that represent items in a data set.</li> 55 * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li> 56 * <li><em>Index:</em> The index of an attached child view as used in a call to 57 * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li> 58 * <li><em>Binding:</em> The process of preparing a child view to display data corresponding 59 * to a <em>position</em> within the adapter.</li> 60 * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter 61 * position may be placed in a cache for later reuse to display the same type of data again 62 * later. This can drastically improve performance by skipping initial layout inflation 63 * or construction.</li> 64 * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached 65 * state during layout. Scrap views may be reused without becoming fully detached 66 * from the parent RecyclerView, either unmodified if no rebinding is required or modified 67 * by the adapter if the view was considered <em>dirty</em>.</li> 68 * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before 69 * being displayed.</li> 70 * </ul> 71 */ 72public class RecyclerView extends ViewGroup { 73 private static final String TAG = "RecyclerView"; 74 private static final boolean DEBUG = false; 75 private static final boolean DISPATCH_TEMP_DETACH = false; 76 77 public static final int HORIZONTAL = 0; 78 public static final int VERTICAL = 1; 79 80 public static final int NO_POSITION = -1; 81 public static final long NO_ID = -1; 82 public static final int INVALID_TYPE = -1; 83 84 private static final int MAX_SCROLL_DURATION = 2000; 85 86 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); 87 88 private final Recycler mRecycler = new Recycler(); 89 90 /** 91 * Note: this Runnable is only ever posted if: 92 * 1) We've been through first layout 93 * 2) We know we have a fixed size (mHasFixedSize) 94 * 3) We're attached 95 */ 96 private final Runnable mUpdateChildViewsRunnable = new Runnable() { 97 public void run() { 98 eatRequestLayout(); 99 updateChildViews(); 100 resumeRequestLayout(true); 101 } 102 }; 103 104 private final Rect mTempRect = new Rect(); 105 106 private final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>(); 107 private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE); 108 109 private Adapter mAdapter; 110 private LayoutManager mLayout; 111 private RecyclerListener mRecyclerListener; 112 private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>(); 113 private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = 114 new ArrayList<OnItemTouchListener>(); 115 private OnItemTouchListener mActiveOnItemTouchListener; 116 private boolean mIsAttached; 117 private boolean mHasFixedSize; 118 private boolean mFirstLayoutComplete; 119 private boolean mEatRequestLayout; 120 private boolean mLayoutRequestEaten; 121 private boolean mAdapterUpdateDuringMeasure; 122 private boolean mStructureChanged; 123 private final boolean mPostUpdatesOnAnimation; 124 125 private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; 126 127 private static final int INVALID_POINTER = -1; 128 129 /** 130 * The RecyclerView is not currently scrolling. 131 * @see #getScrollState() 132 */ 133 public static final int SCROLL_STATE_IDLE = 0; 134 135 /** 136 * The RecyclerView is currently being dragged by outside input such as user touch input. 137 * @see #getScrollState() 138 */ 139 public static final int SCROLL_STATE_DRAGGING = 1; 140 141 /** 142 * The RecyclerView is currently animating to a final position while not under 143 * outside control. 144 * @see #getScrollState() 145 */ 146 public static final int SCROLL_STATE_SETTLING = 2; 147 148 // Touch/scrolling handling 149 150 private int mScrollState = SCROLL_STATE_IDLE; 151 private int mScrollPointerId = INVALID_POINTER; 152 private VelocityTracker mVelocityTracker; 153 private int mInitialTouchX; 154 private int mInitialTouchY; 155 private int mLastTouchX; 156 private int mLastTouchY; 157 private final int mTouchSlop; 158 private final int mMinFlingVelocity; 159 private final int mMaxFlingVelocity; 160 161 private final ViewFlinger mViewFlinger = new ViewFlinger(); 162 163 private OnScrollListener mScrollListener; 164 165 private static final Interpolator sQuinticInterpolator = new Interpolator() { 166 public float getInterpolation(float t) { 167 t -= 1.0f; 168 return t * t * t * t * t + 1.0f; 169 } 170 }; 171 172 public RecyclerView(Context context) { 173 this(context, null); 174 } 175 176 public RecyclerView(Context context, AttributeSet attrs) { 177 this(context, attrs, 0); 178 } 179 180 public RecyclerView(Context context, AttributeSet attrs, int defStyle) { 181 super(context, attrs, defStyle); 182 183 final int version = Build.VERSION.SDK_INT; 184 mPostUpdatesOnAnimation = version >= 16; 185 186 final ViewConfiguration vc = ViewConfiguration.get(context); 187 mTouchSlop = vc.getScaledTouchSlop(); 188 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 189 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 190 191 setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); 192 } 193 194 /** 195 * RecyclerView can perform several optimizations if it can know in advance that changes in 196 * adapter content cannot change the size of the RecyclerView itself. 197 * If your use of RecyclerView falls into this category, set this to true. 198 * 199 * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. 200 */ 201 public void setHasFixedSize(boolean hasFixedSize) { 202 mHasFixedSize = hasFixedSize; 203 } 204 205 /** 206 * @return true if the app has specified that changes in adapter content cannot change 207 * the size of the RecyclerView itself. 208 */ 209 public boolean hasFixedSize() { 210 return mHasFixedSize; 211 } 212 213 /** 214 * Set a new adapter to provide child views on demand. 215 * 216 * @param adapter The new adapter to set, or null to set no adapter. 217 */ 218 public void setAdapter(Adapter adapter) { 219 if (mAdapter != null) { 220 mAdapter.unregisterAdapterDataObserver(mObserver); 221 } 222 final Adapter oldAdapter = adapter; 223 mAdapter = adapter; 224 if (adapter != null) { 225 adapter.registerAdapterDataObserver(mObserver); 226 } 227 if (mLayout != null) { 228 mLayout.onAdapterChanged(); 229 } 230 mRecycler.onAdapterChanged(oldAdapter, mAdapter); 231 requestLayout(); 232 } 233 234 /** 235 * Retrieves the previously set adapter or null if no adapter is set. 236 * 237 * @return The previously set adapter 238 * @see #setAdapter(Adapter) 239 */ 240 public Adapter getAdapter() { 241 return mAdapter; 242 } 243 244 /** 245 * Register a listener that will be notified whenever a child view is recycled. 246 * 247 * <p>This listener will be called when a LayoutManager or the RecyclerView decides 248 * that a child view is no longer needed. If an application associates expensive 249 * or heavyweight data with item views, this may be a good place to release 250 * or free those resources.</p> 251 * 252 * @param listener Listener to register, or null to clear 253 */ 254 public void setRecyclerListener(RecyclerListener listener) { 255 mRecyclerListener = listener; 256 } 257 258 /** 259 * Set the {@link LayoutManager} that this RecyclerView will use. 260 * 261 * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView} 262 * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom 263 * layout arrangements for child views. These arrangements are controlled by the 264 * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p> 265 * 266 * <p>Several default strategies are provided for common uses such as lists and grids.</p> 267 * 268 * @param layout LayoutManager to use 269 */ 270 public void setLayoutManager(LayoutManager layout) { 271 if (layout == mLayout) { 272 return; 273 } 274 275 mRecycler.clear(); 276 removeAllViews(); 277 if (mLayout != null) { 278 if (mIsAttached) { 279 mLayout.onDetachedFromWindow(this); 280 } 281 mLayout.mRecyclerView = null; 282 } 283 mLayout = layout; 284 if (layout != null) { 285 if (layout.mRecyclerView != null) { 286 throw new IllegalArgumentException("LayoutManager " + layout + 287 " is already attached to a RecyclerView: " + layout.mRecyclerView); 288 } 289 layout.mRecyclerView = this; 290 if (mIsAttached) { 291 mLayout.onAttachedToWindow(this); 292 } 293 } 294 requestLayout(); 295 } 296 297 /** 298 * Return the {@link LayoutManager} currently responsible for 299 * layout policy for this RecyclerView. 300 * 301 * @return The currently bound LayoutManager 302 */ 303 public LayoutManager getLayoutManager() { 304 return mLayout; 305 } 306 307 /** 308 * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; 309 * if no pool is set for this view a new one will be created. See 310 * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. 311 * 312 * @return The pool used to store recycled item views for reuse. 313 * @see #setRecycledViewPool(RecycledViewPool) 314 */ 315 public RecycledViewPool getRecycledViewPool() { 316 return mRecycler.getRecycledViewPool(); 317 } 318 319 /** 320 * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. 321 * This can be useful if you have multiple RecyclerViews with adapters that use the same 322 * view types, for example if you have several data sets with the same kinds of item views 323 * displayed by a {@link android.support.v4.view.ViewPager ViewPager}. 324 * 325 * @param pool Pool to set. If this parameter is null a new pool will be created and used. 326 */ 327 public void setRecycledViewPool(RecycledViewPool pool) { 328 mRecycler.setRecycledViewPool(pool); 329 } 330 331 /** 332 * Set the number of offscreen views to retain before adding them to the potentially shared 333 * {@link #getRecycledViewPool() recycled view pool}. 334 * 335 * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing 336 * a LayoutManager to reuse those views unmodified without needing to return to the adapter 337 * to rebind them.</p> 338 * 339 * @param size Number of views to cache offscreen before returning them to the general 340 * recycled view pool 341 */ 342 public void setItemViewCacheSize(int size) { 343 mRecycler.setViewCacheSize(size); 344 } 345 346 /** 347 * Return the current scrolling state of the RecyclerView. 348 * 349 * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or 350 * {@link #SCROLL_STATE_SETTLING} 351 */ 352 public int getScrollState() { 353 return mScrollState; 354 } 355 356 private void setScrollState(int state) { 357 if (state == mScrollState) { 358 return; 359 } 360 mScrollState = state; 361 if (state != SCROLL_STATE_SETTLING) { 362 mViewFlinger.stop(); 363 } 364 if (mScrollListener != null) { 365 mScrollListener.onScrollStateChanged(state); 366 } 367 } 368 369 /** 370 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 371 * affect both measurement and drawing of individual item views. 372 * 373 * <p>Item decorations are ordered. Decorations placed earlier in the list will 374 * be run/queried/drawn first for their effects on item views. Padding added to views 375 * will be nested; a padding added by an earlier decoration will mean further 376 * item decorations in the list will be asked to draw/pad within the previous decoration's 377 * given area.</p> 378 * 379 * @param decor Decoration to add 380 * @param index Position in the decoration chain to insert this decoration at. If this value 381 * is negative the decoration will be added at the end. 382 */ 383 public void addItemDecoration(ItemDecoration decor, int index) { 384 if (mItemDecorations.isEmpty()) { 385 setWillNotDraw(false); 386 } 387 if (index < 0) { 388 mItemDecorations.add(decor); 389 } else { 390 mItemDecorations.add(index, decor); 391 } 392 markItemDecorInsetsDirty(); 393 requestLayout(); 394 } 395 396 /** 397 * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can 398 * affect both measurement and drawing of individual item views. 399 * 400 * <p>Item decorations are ordered. Decorations placed earlier in the list will 401 * be run/queried/drawn first for their effects on item views. Padding added to views 402 * will be nested; a padding added by an earlier decoration will mean further 403 * item decorations in the list will be asked to draw/pad within the previous decoration's 404 * given area.</p> 405 * 406 * @param decor Decoration to add 407 */ 408 public void addItemDecoration(ItemDecoration decor) { 409 addItemDecoration(decor, -1); 410 } 411 412 /** 413 * Remove an {@link ItemDecoration} from this RecyclerView. 414 * 415 * <p>The given decoration will no longer impact the measurement and drawing of 416 * item views.</p> 417 * 418 * @param decor Decoration to remove 419 * @see #addItemDecoration(ItemDecoration) 420 */ 421 public void removeItemDecoration(ItemDecoration decor) { 422 mItemDecorations.remove(decor); 423 if (mItemDecorations.isEmpty()) { 424 setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); 425 } 426 markItemDecorInsetsDirty(); 427 requestLayout(); 428 } 429 430 /** 431 * Set a listener that will be notified of any changes in scroll state or position. 432 * 433 * @param listener Listener to set or null to clear 434 */ 435 public void setOnScrollListener(OnScrollListener listener) { 436 mScrollListener = listener; 437 } 438 439 @Override 440 public void scrollTo(int x, int y) { 441 throw new UnsupportedOperationException( 442 "RecyclerView does not support scrolling to an absolute position."); 443 } 444 445 @Override 446 public void scrollBy(int x, int y) { 447 if (mLayout == null) { 448 throw new IllegalStateException("Cannot scroll without a LayoutManager set. " + 449 "Call setLayoutManager with a non-null argument."); 450 } 451 final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); 452 final boolean canScrollVertical = mLayout.canScrollVertically(); 453 if (canScrollHorizontal || canScrollVertical) { 454 scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0); 455 } 456 } 457 458 /** 459 * Does not perform bounds checking. Used by internal methods that have already validated input. 460 */ 461 void scrollByInternal(int x, int y) { 462 int overscrollX = 0, overscrollY = 0; 463 eatRequestLayout(); 464 if (x != 0) { 465 final int hresult = mLayout.scrollHorizontallyBy(x, getAdapter(), mRecycler); 466 overscrollX = x - hresult; 467 } 468 if (y != 0) { 469 final int vresult = mLayout.scrollVerticallyBy(y, getAdapter(), mRecycler); 470 overscrollY = y - vresult; 471 } 472 resumeRequestLayout(false); 473 if (!mItemDecorations.isEmpty()) { 474 invalidate(); 475 } 476 pullGlows(overscrollX, overscrollY); 477 if (mScrollListener != null && (x != 0 || y != 0)) { 478 mScrollListener.onScrolled(x, y); 479 } 480 } 481 482 void eatRequestLayout() { 483 if (!mEatRequestLayout) { 484 mEatRequestLayout = true; 485 mLayoutRequestEaten = false; 486 } 487 } 488 489 void resumeRequestLayout(boolean performLayoutChildren) { 490 if (mEatRequestLayout) { 491 if (performLayoutChildren && mLayoutRequestEaten && 492 mLayout != null && mAdapter != null) { 493 layoutChildren(); 494 } 495 mEatRequestLayout = false; 496 mLayoutRequestEaten = false; 497 } 498 } 499 500 /** 501 * Animate a scroll by the given amount of pixels along either axis. 502 * 503 * @param dx Pixels to scroll horizontally 504 * @param dy Pixels to scroll vertically 505 */ 506 public void smoothScrollBy(int dx, int dy) { 507 if (dx != 0 || dy != 0) { 508 mViewFlinger.smoothScrollBy(dx, dy); 509 } 510 } 511 512 /** 513 * Begin a standard fling with an initial velocity along each axis in pixels per second. 514 * If the velocity given is below the system-defined minimum this method will return false 515 * and no fling will occur. 516 * 517 * @param velocityX Initial horizontal velocity in pixels per second 518 * @param velocityY Initial vertical velocity in pixels per second 519 * @return true if the fling was started, false if the velocity was too low to fling 520 */ 521 public boolean fling(int velocityX, int velocityY) { 522 if (Math.abs(velocityX) < mMinFlingVelocity) { 523 velocityX = 0; 524 } 525 if (Math.abs(velocityY) < mMinFlingVelocity) { 526 velocityY = 0; 527 } 528 velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); 529 velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); 530 if (velocityX != 0 || velocityY != 0) { 531 mViewFlinger.fling(velocityX, velocityY); 532 return true; 533 } 534 return false; 535 } 536 537 /** 538 * Stop any current scroll in progress, such as one started by 539 * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. 540 */ 541 public void stopScroll() { 542 mViewFlinger.stop(); 543 } 544 545 /** 546 * Apply a pull to relevant overscroll glow effects 547 */ 548 private void pullGlows(int overscrollX, int overscrollY) { 549 if (overscrollX < 0) { 550 if (mLeftGlow == null) { 551 mLeftGlow = new EdgeEffectCompat(getContext()); 552 mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 553 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 554 } 555 mLeftGlow.onPull(-overscrollX / (float) getWidth()); 556 } else if (overscrollX > 0) { 557 if (mRightGlow == null) { 558 mRightGlow = new EdgeEffectCompat(getContext()); 559 mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 560 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 561 } 562 mRightGlow.onPull(overscrollX / (float) getWidth()); 563 } 564 565 if (overscrollY < 0) { 566 if (mTopGlow == null) { 567 mTopGlow = new EdgeEffectCompat(getContext()); 568 mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 569 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 570 } 571 mTopGlow.onPull(-overscrollY / (float) getHeight()); 572 } else if (overscrollY > 0) { 573 if (mBottomGlow == null) { 574 mBottomGlow = new EdgeEffectCompat(getContext()); 575 mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 576 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 577 } 578 mBottomGlow.onPull(overscrollY / (float) getHeight()); 579 } 580 581 if (overscrollX != 0 || overscrollY != 0) { 582 ViewCompat.postInvalidateOnAnimation(this); 583 } 584 } 585 586 private void releaseGlows() { 587 boolean needsInvalidate = false; 588 if (mLeftGlow != null) needsInvalidate = mLeftGlow.onRelease(); 589 if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease(); 590 if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease(); 591 if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease(); 592 if (needsInvalidate) { 593 ViewCompat.postInvalidateOnAnimation(this); 594 } 595 } 596 597 void absorbGlows(int velocityX, int velocityY) { 598 if (velocityX < 0) { 599 if (mLeftGlow == null) { 600 mLeftGlow = new EdgeEffectCompat(getContext()); 601 mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 602 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 603 } 604 mLeftGlow.onAbsorb(-velocityX); 605 } else if (velocityX > 0) { 606 if (mRightGlow == null) { 607 mRightGlow = new EdgeEffectCompat(getContext()); 608 mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), 609 getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); 610 } 611 mRightGlow.onAbsorb(velocityX); 612 } 613 614 if (velocityY < 0) { 615 if (mTopGlow == null) { 616 mTopGlow = new EdgeEffectCompat(getContext()); 617 mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 618 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 619 } 620 mTopGlow.onAbsorb(-velocityY); 621 } else if (velocityY > 0) { 622 if (mBottomGlow == null) { 623 mBottomGlow = new EdgeEffectCompat(getContext()); 624 mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), 625 getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); 626 } 627 mBottomGlow.onAbsorb(velocityY); 628 } 629 630 if (velocityX != 0 || velocityY != 0) { 631 ViewCompat.postInvalidateOnAnimation(this); 632 } 633 } 634 635 // Focus handling 636 637 @Override 638 public View focusSearch(View focused, int direction) { 639 View result = mLayout.onInterceptFocusSearch(focused, direction); 640 if (result != null) { 641 return result; 642 } 643 final FocusFinder ff = FocusFinder.getInstance(); 644 result = ff.findNextFocus(this, focused, direction); 645 if (result == null) { 646 eatRequestLayout(); 647 result = mLayout.onFocusSearchFailed(focused, direction, getAdapter(), mRecycler); 648 resumeRequestLayout(false); 649 } 650 return result != null ? result : super.focusSearch(focused, direction); 651 } 652 653 @Override 654 public void requestChildFocus(View child, View focused) { 655 if (!mLayout.onRequestChildFocus(this, child, focused)) { 656 mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); 657 offsetDescendantRectToMyCoords(focused, mTempRect); 658 offsetRectIntoDescendantCoords(child, mTempRect); 659 requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete); 660 } 661 super.requestChildFocus(child, focused); 662 } 663 664 @Override 665 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { 666 return mLayout.requestChildRectangleOnScreen(child, rect, immediate); 667 } 668 669 @Override 670 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 671 if (!mLayout.onAddFocusables(views, direction, focusableMode)) { 672 super.addFocusables(views, direction, focusableMode); 673 } 674 } 675 676 @Override 677 protected void onAttachedToWindow() { 678 super.onAttachedToWindow(); 679 mIsAttached = true; 680 mFirstLayoutComplete = false; 681 if (mLayout != null) { 682 mLayout.onAttachedToWindow(this); 683 } 684 } 685 686 @Override 687 protected void onDetachedFromWindow() { 688 super.onDetachedFromWindow(); 689 mFirstLayoutComplete = false; 690 691 mViewFlinger.stop(); 692 // TODO Mark what our target position was if relevant, then we can jump there 693 // on reattach. 694 695 mIsAttached = false; 696 if (mLayout != null) { 697 mLayout.onDetachedFromWindow(this); 698 } 699 } 700 701 /** 702 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched 703 * to child views or this view's standard scrolling behavior. 704 * 705 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener 706 * returns true from 707 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its 708 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called 709 * for each incoming MotionEvent until the end of the gesture.</p> 710 * 711 * @param listener Listener to add 712 */ 713 public void addOnItemTouchListener(OnItemTouchListener listener) { 714 mOnItemTouchListeners.add(listener); 715 } 716 717 /** 718 * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events. 719 * 720 * @param listener Listener to remove 721 */ 722 public void removeOnItemTouchListener(OnItemTouchListener listener) { 723 mOnItemTouchListeners.remove(listener); 724 if (mActiveOnItemTouchListener == listener) { 725 mActiveOnItemTouchListener = null; 726 } 727 } 728 729 private boolean dispatchOnItemTouchIntercept(MotionEvent e) { 730 final int action = e.getAction(); 731 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) { 732 mActiveOnItemTouchListener = null; 733 } 734 735 final int listenerCount = mOnItemTouchListeners.size(); 736 for (int i = 0; i < listenerCount; i++) { 737 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 738 if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { 739 mActiveOnItemTouchListener = listener; 740 return true; 741 } 742 } 743 return false; 744 } 745 746 private boolean dispatchOnItemTouch(MotionEvent e) { 747 final int action = e.getAction(); 748 if (mActiveOnItemTouchListener != null) { 749 if (action == MotionEvent.ACTION_DOWN) { 750 // Stale state from a previous gesture, we're starting a new one. Clear it. 751 mActiveOnItemTouchListener = null; 752 } else { 753 mActiveOnItemTouchListener.onTouchEvent(this, e); 754 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 755 // Clean up for the next gesture. 756 mActiveOnItemTouchListener = null; 757 } 758 return true; 759 } 760 } 761 762 // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept 763 // as called from onInterceptTouchEvent; skip it. 764 if (action != MotionEvent.ACTION_DOWN) { 765 final int listenerCount = mOnItemTouchListeners.size(); 766 for (int i = 0; i < listenerCount; i++) { 767 final OnItemTouchListener listener = mOnItemTouchListeners.get(i); 768 if (listener.onInterceptTouchEvent(this, e)) { 769 mActiveOnItemTouchListener = listener; 770 return true; 771 } 772 } 773 } 774 return false; 775 } 776 777 @Override 778 public boolean onInterceptTouchEvent(MotionEvent e) { 779 if (dispatchOnItemTouchIntercept(e)) { 780 cancelTouch(); 781 return true; 782 } 783 784 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 785 final boolean canScrollVertically = mLayout.canScrollVertically(); 786 787 if (mVelocityTracker == null) { 788 mVelocityTracker = VelocityTracker.obtain(); 789 } 790 mVelocityTracker.addMovement(e); 791 792 final int action = MotionEventCompat.getActionMasked(e); 793 final int actionIndex = MotionEventCompat.getActionIndex(e); 794 795 switch (action) { 796 case MotionEvent.ACTION_DOWN: 797 mScrollPointerId = MotionEventCompat.getPointerId(e, 0); 798 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 799 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 800 801 if (mScrollState == SCROLL_STATE_SETTLING) { 802 getParent().requestDisallowInterceptTouchEvent(true); 803 setScrollState(SCROLL_STATE_DRAGGING); 804 } 805 break; 806 807 case MotionEventCompat.ACTION_POINTER_DOWN: 808 mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); 809 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); 810 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); 811 break; 812 813 case MotionEvent.ACTION_MOVE: { 814 final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); 815 if (index < 0) { 816 Log.e(TAG, "Error processing scroll; pointer index for id " + 817 mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 818 return false; 819 } 820 821 final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); 822 final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); 823 if (mScrollState != SCROLL_STATE_DRAGGING) { 824 final int dx = x - mInitialTouchX; 825 final int dy = y - mInitialTouchY; 826 boolean startScroll = false; 827 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 828 mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); 829 startScroll = true; 830 } 831 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 832 mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); 833 startScroll = true; 834 } 835 if (startScroll) { 836 getParent().requestDisallowInterceptTouchEvent(true); 837 setScrollState(SCROLL_STATE_DRAGGING); 838 } 839 } 840 } break; 841 842 case MotionEventCompat.ACTION_POINTER_UP: { 843 onPointerUp(e); 844 } break; 845 846 case MotionEvent.ACTION_UP: { 847 mVelocityTracker.clear(); 848 } break; 849 850 case MotionEvent.ACTION_CANCEL: { 851 cancelTouch(); 852 } 853 } 854 return mScrollState == SCROLL_STATE_DRAGGING; 855 } 856 857 @Override 858 public boolean onTouchEvent(MotionEvent e) { 859 if (dispatchOnItemTouch(e)) { 860 cancelTouch(); 861 return true; 862 } 863 864 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); 865 final boolean canScrollVertically = mLayout.canScrollVertically(); 866 867 if (mVelocityTracker == null) { 868 mVelocityTracker = VelocityTracker.obtain(); 869 } 870 mVelocityTracker.addMovement(e); 871 872 final int action = MotionEventCompat.getActionMasked(e); 873 final int actionIndex = MotionEventCompat.getActionIndex(e); 874 875 switch (action) { 876 case MotionEvent.ACTION_DOWN: { 877 mScrollPointerId = MotionEventCompat.getPointerId(e, 0); 878 mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); 879 mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); 880 } break; 881 882 case MotionEventCompat.ACTION_POINTER_DOWN: { 883 mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); 884 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); 885 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); 886 } break; 887 888 case MotionEvent.ACTION_MOVE: { 889 final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); 890 if (index < 0) { 891 Log.e(TAG, "Error processing scroll; pointer index for id " + 892 mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 893 return false; 894 } 895 896 final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); 897 final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); 898 if (mScrollState != SCROLL_STATE_DRAGGING) { 899 final int dx = x - mInitialTouchX; 900 final int dy = y - mInitialTouchY; 901 boolean startScroll = false; 902 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 903 mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); 904 startScroll = true; 905 } 906 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 907 mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); 908 startScroll = true; 909 } 910 if (startScroll) { 911 getParent().requestDisallowInterceptTouchEvent(true); 912 setScrollState(SCROLL_STATE_DRAGGING); 913 } 914 } 915 if (mScrollState == SCROLL_STATE_DRAGGING) { 916 final int dx = x - mLastTouchX; 917 final int dy = y - mLastTouchY; 918 scrollByInternal(canScrollHorizontally ? -dx : 0, 919 canScrollVertically ? -dy : 0); 920 } 921 mLastTouchX = x; 922 mLastTouchY = y; 923 } break; 924 925 case MotionEventCompat.ACTION_POINTER_UP: { 926 onPointerUp(e); 927 } break; 928 929 case MotionEvent.ACTION_UP: { 930 mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); 931 final float xvel = canScrollHorizontally ? 932 -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0; 933 final float yvel = canScrollVertically ? 934 -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0; 935 if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { 936 setScrollState(SCROLL_STATE_IDLE); 937 } 938 939 mVelocityTracker.clear(); 940 releaseGlows(); 941 } break; 942 943 case MotionEvent.ACTION_CANCEL: { 944 cancelTouch(); 945 } break; 946 } 947 948 return true; 949 } 950 951 private void cancelTouch() { 952 mVelocityTracker.clear(); 953 releaseGlows(); 954 setScrollState(SCROLL_STATE_IDLE); 955 } 956 957 private void onPointerUp(MotionEvent e) { 958 final int actionIndex = MotionEventCompat.getActionIndex(e); 959 if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) { 960 // Pick a new pointer to pick up the slack. 961 final int newIndex = actionIndex == 0 ? 1 : 0; 962 mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex); 963 mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f); 964 mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f); 965 } 966 } 967 968 @Override 969 protected void onMeasure(int widthSpec, int heightSpec) { 970 if (mAdapterUpdateDuringMeasure) { 971 eatRequestLayout(); 972 updateChildViews(); 973 resumeRequestLayout(false); 974 } 975 976 final int widthMode = MeasureSpec.getMode(widthSpec); 977 final int heightMode = MeasureSpec.getMode(heightSpec); 978 final int widthSize = MeasureSpec.getSize(widthSpec); 979 final int heightSize = MeasureSpec.getSize(heightSpec); 980 981 if (!mHasFixedSize || widthMode == MeasureSpec.UNSPECIFIED || 982 heightMode == MeasureSpec.UNSPECIFIED) { 983 throw new IllegalStateException("Non-fixed sizes not yet supported"); 984 } 985 986 if (mLeftGlow != null) mLeftGlow.setSize(heightSize, widthSize); 987 if (mTopGlow != null) mTopGlow.setSize(widthSize, heightSize); 988 if (mRightGlow != null) mRightGlow.setSize(heightSize, widthSize); 989 if (mBottomGlow != null) mBottomGlow.setSize(widthSize, heightSize); 990 991 setMeasuredDimension(widthSize, heightSize); 992 } 993 994 @Override 995 protected void onLayout(boolean changed, int l, int t, int r, int b) { 996 if (mAdapter == null) { 997 Log.e(TAG, "No adapter attached; skipping layout"); 998 return; 999 } 1000 eatRequestLayout(); 1001 layoutChildren(); 1002 resumeRequestLayout(false); 1003 mFirstLayoutComplete = true; 1004 } 1005 1006 @Override 1007 public void requestLayout() { 1008 if (!mEatRequestLayout) { 1009 super.requestLayout(); 1010 } else { 1011 mLayoutRequestEaten = true; 1012 } 1013 } 1014 1015 void layoutChildren() { 1016 mLayout.layoutChildren(mAdapter, mRecycler, mStructureChanged); 1017 mStructureChanged = false; 1018 } 1019 1020 void markItemDecorInsetsDirty() { 1021 final int childCount = getChildCount(); 1022 for (int i = 0; i < childCount; i++) { 1023 final View child = getChildAt(i); 1024 ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; 1025 } 1026 } 1027 1028 @Override 1029 public void draw(Canvas c) { 1030 super.draw(c); 1031 1032 final int count = mItemDecorations.size(); 1033 for (int i = 0; i < count; i++) { 1034 mItemDecorations.get(i).onDrawOver(c, this); 1035 } 1036 1037 boolean needsInvalidate = false; 1038 if (mLeftGlow != null && !mLeftGlow.isFinished()) { 1039 final int restore = c.save(); 1040 c.rotate(270); 1041 c.translate(-getHeight() + getPaddingTop(), 0); 1042 needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); 1043 c.restoreToCount(restore); 1044 } 1045 if (mTopGlow != null && !mTopGlow.isFinished()) { 1046 c.translate(getPaddingLeft(), getPaddingTop()); 1047 needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); 1048 } 1049 if (mRightGlow != null && !mRightGlow.isFinished()) { 1050 final int restore = c.save(); 1051 final int width = getWidth(); 1052 1053 c.rotate(90); 1054 c.translate(-getPaddingTop(), -width); 1055 needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); 1056 c.restoreToCount(restore); 1057 } 1058 if (mBottomGlow != null && !mBottomGlow.isFinished()) { 1059 final int restore = c.save(); 1060 c.rotate(180); 1061 c.translate(-getWidth() + getPaddingLeft(), -getHeight() + getPaddingTop()); 1062 needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); 1063 c.restoreToCount(restore); 1064 } 1065 1066 if (needsInvalidate) { 1067 ViewCompat.postInvalidateOnAnimation(this); 1068 } 1069 } 1070 1071 @Override 1072 public void onDraw(Canvas c) { 1073 super.onDraw(c); 1074 1075 final int count = mItemDecorations.size(); 1076 for (int i = 0; i < count; i++) { 1077 mItemDecorations.get(i).onDraw(c, this); 1078 } 1079 } 1080 1081 @Override 1082 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1083 return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); 1084 } 1085 1086 @Override 1087 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1088 if (mLayout == null) { 1089 throw new IllegalStateException("RecyclerView has no LayoutManager"); 1090 } 1091 return mLayout.generateDefaultLayoutParams(); 1092 } 1093 1094 @Override 1095 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1096 if (mLayout == null) { 1097 throw new IllegalStateException("RecyclerView has no LayoutManager"); 1098 } 1099 return mLayout.generateLayoutParams(getContext(), attrs); 1100 } 1101 1102 @Override 1103 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1104 if (mLayout == null) { 1105 throw new IllegalStateException("RecyclerView has no LayoutManager"); 1106 } 1107 return mLayout.generateLayoutParams(p); 1108 } 1109 1110 void updateChildViews() { 1111 final int opCount = mPendingUpdates.size(); 1112 for (int i = 0; i < opCount; i++) { 1113 final UpdateOp op = mPendingUpdates.get(i); 1114 switch (op.cmd) { 1115 case UpdateOp.ADD: 1116 if (DEBUG) { 1117 Log.d(TAG, "UpdateOp.ADD start=" + op.positionStart + " count=" + 1118 op.itemCount); 1119 } 1120 offsetPositionRecordsForInsert(op.positionStart, op.itemCount); 1121 1122 mLayout.onItemsAdded(this, op.positionStart, op.itemCount); 1123 1124 // TODO Animate it in 1125 break; 1126 case UpdateOp.REMOVE: 1127 if (DEBUG) { 1128 Log.d(TAG, "UpdateOp.REMOVE start=" + op.positionStart + " count=" + 1129 op.itemCount); 1130 } 1131 offsetPositionRecordsForRemove(op.positionStart, op.itemCount); 1132 1133 mLayout.onItemsRemoved(this, op.positionStart, op.itemCount); 1134 1135 // TODO Animate it away 1136 break; 1137 case UpdateOp.UPDATE: 1138 viewRangeUpdate(op.positionStart, op.itemCount); 1139 break; 1140 } 1141 recycleUpdateOp(op); 1142 } 1143 mPendingUpdates.clear(); 1144 } 1145 1146 void offsetPositionRecordsForInsert(int positionStart, int itemCount) { 1147 boolean needsLayout = false; 1148 final int childCount = getChildCount(); 1149 for (int i = 0; i < childCount; i++) { 1150 final ViewHolder holder = getChildViewHolderInt(getChildAt(i)); 1151 if (holder != null && holder.mPosition >= positionStart) { 1152 if (DEBUG) { 1153 Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " + 1154 holder + " now at position " + (holder.mPosition + itemCount)); 1155 } 1156 holder.mPosition += itemCount; 1157 needsLayout = true; 1158 mStructureChanged = true; 1159 } 1160 } 1161 mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); 1162 if (needsLayout) { 1163 requestLayout(); 1164 } 1165 } 1166 1167 void offsetPositionRecordsForRemove(int positionStart, int itemCount) { 1168 boolean needsLayout = false; 1169 final int positionEnd = positionStart + itemCount; 1170 final int childCount = getChildCount(); 1171 for (int i = 0; i < childCount; i++) { 1172 final ViewHolder holder = getChildViewHolderInt(getChildAt(i)); 1173 if (holder != null) { 1174 if (holder.mPosition >= positionEnd) { 1175 if (DEBUG) { 1176 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + 1177 " holder " + holder + " now at position " + 1178 (holder.mPosition - itemCount)); 1179 } 1180 holder.mPosition -= itemCount; 1181 needsLayout = true; 1182 mStructureChanged = true; 1183 } else if (holder.mPosition >= positionStart) { 1184 if (DEBUG) { 1185 Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i + 1186 " holder " + holder + " now REMOVED"); 1187 } 1188 holder.addFlags(ViewHolder.FLAG_REMOVED); 1189 needsLayout = true; 1190 mStructureChanged = true; 1191 } 1192 } 1193 } 1194 mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount); 1195 if (needsLayout) { 1196 requestLayout(); 1197 } 1198 } 1199 1200 /** 1201 * Rebind existing views for the given range, or create as needed. 1202 * 1203 * @param positionStart Adapter position to start at 1204 * @param itemCount Number of views that must explicitly be rebound 1205 */ 1206 void viewRangeUpdate(int positionStart, int itemCount) { 1207 final int childCount = getChildCount(); 1208 final int positionEnd = positionStart + itemCount; 1209 1210 for (int i = 0; i < childCount; i++) { 1211 final ViewHolder holder = getChildViewHolderInt(getChildAt(i)); 1212 if (holder == null) { 1213 continue; 1214 } 1215 1216 final int position = holder.getPosition(); 1217 if (position >= positionStart && position < positionEnd) { 1218 holder.addFlags(ViewHolder.FLAG_UPDATE); 1219 // Binding an attached view will request a layout if needed. 1220 mAdapter.bindViewHolder(holder, holder.getPosition()); 1221 } 1222 } 1223 mRecycler.viewRangeUpdate(positionStart, itemCount); 1224 } 1225 1226 /** 1227 * Mark all known views as invalid. Used in response to a, "the whole world might have changed" 1228 * data change event. 1229 */ 1230 void markKnownViewsInvalid() { 1231 final int childCount = getChildCount(); 1232 1233 for (int i = 0; i < childCount; i++) { 1234 final ViewHolder holder = getChildViewHolderInt(getChildAt(i)); 1235 if (holder != null) { 1236 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 1237 } 1238 } 1239 mRecycler.markKnownViewsInvalid(); 1240 } 1241 1242 /** 1243 * Schedule an update of data from the adapter to occur on the next frame. 1244 * On newer platform versions this happens via the postOnAnimation mechanism and RecyclerView 1245 * attempts to avoid relayouts if possible. 1246 * On older platform versions the RecyclerView requests a layout the same way ListView does. 1247 */ 1248 void postAdapterUpdate(UpdateOp op) { 1249 mPendingUpdates.add(op); 1250 if (mPendingUpdates.size() == 1) { 1251 if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) { 1252 ViewCompat.postOnAnimation(this, mUpdateChildViewsRunnable); 1253 } else { 1254 mAdapterUpdateDuringMeasure = true; 1255 requestLayout(); 1256 } 1257 } 1258 } 1259 1260 /** 1261 * Retrieve the {@link ViewHolder} for the given child view. 1262 * 1263 * @param child Child of this RecyclerView to query for its ViewHolder 1264 * @return The child view's ViewHolder 1265 */ 1266 public ViewHolder getChildViewHolder(View child) { 1267 if (child.getParent() != this) { 1268 throw new IllegalArgumentException("View " + child + " is not a direct child of " + 1269 this); 1270 } 1271 return getChildViewHolderInt(child); 1272 } 1273 1274 static ViewHolder getChildViewHolderInt(View child) { 1275 if (child == null) { 1276 return null; 1277 } 1278 return ((LayoutParams) child.getLayoutParams()).mViewHolder; 1279 } 1280 1281 /** 1282 * Return the adapter position that the given child view corresponds to. 1283 * 1284 * @param child Child View to query 1285 * @return Adapter position corresponding to the given view or {@link #NO_POSITION} 1286 */ 1287 public int getChildPosition(View child) { 1288 final ViewHolder holder = getChildViewHolderInt(child); 1289 return holder != null ? holder.getPosition() : NO_POSITION; 1290 } 1291 1292 /** 1293 * Return the stable item id that the given child view corresponds to. 1294 * 1295 * @param child Child View to query 1296 * @return Item id corresponding to the given view or {@link #NO_ID} 1297 */ 1298 public long getChildItemId(View child) { 1299 if (mAdapter == null || !mAdapter.hasStableIds()) { 1300 return NO_ID; 1301 } 1302 final ViewHolder holder = getChildViewHolderInt(child); 1303 return holder != null ? holder.getItemId() : NO_ID; 1304 } 1305 1306 /** 1307 * Return the ViewHolder for the item in the given position of the data set. 1308 * 1309 * @param position The position of the item in the data set of the adapter 1310 * @return The ViewHolder at <code>position</code> 1311 */ 1312 public ViewHolder findViewHolderForPosition(int position) { 1313 final int childCount = getChildCount(); 1314 for (int i = 0; i < childCount; i++) { 1315 final ViewHolder holder = getChildViewHolderInt(getChildAt(i)); 1316 if (holder != null && holder.getPosition() == position) { 1317 return holder; 1318 } 1319 } 1320 return mRecycler.findViewHolderForPosition(position); 1321 } 1322 1323 /** 1324 * Return the ViewHolder for the item with the given id. The RecyclerView must 1325 * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to 1326 * return a non-null value. 1327 * 1328 * @param id The id for the requested item 1329 * @return The ViewHolder with the given <code>id</code>, of null if there 1330 * is no such item. 1331 */ 1332 public ViewHolder findViewHolderForItemId(long id) { 1333 final int childCount = getChildCount(); 1334 for (int i = 0; i < childCount; i++) { 1335 final ViewHolder holder = getChildViewHolderInt(getChildAt(i)); 1336 if (holder != null && holder.getItemId() == id) { 1337 return holder; 1338 } 1339 } 1340 return mRecycler.findViewHolderForItemId(id); 1341 } 1342 1343 /** 1344 * Return the ViewHolder for the child view positioned underneath the coordinates (x, y). 1345 * 1346 * @param x Horizontal position in pixels to search 1347 * @param y Vertical position in pixels to search 1348 * @return The ViewHolder for the child under (x, y) or null if no child is found 1349 * 1350 * @deprecated This method will be removed. Use {@link #findChildViewUnder(float, float)} 1351 * along with {@link #getChildViewHolder(View)} 1352 */ 1353 public ViewHolder findViewHolderForChildUnder(int x, int y) { 1354 final View child = findChildViewUnder(x, y); 1355 if (child != null) { 1356 return getChildViewHolderInt(child); 1357 } 1358 return null; 1359 } 1360 1361 /** 1362 * Find the topmost view under the given point. 1363 * 1364 * @param x Horizontal position in pixels to search 1365 * @param y Vertical position in pixels to search 1366 * @return The child view under (x, y) or null if no matching child is found 1367 */ 1368 public View findChildViewUnder(float x, float y) { 1369 final int count = getChildCount(); 1370 for (int i = count - 1; i >= 0; i--) { 1371 final View child = getChildAt(i); 1372 final float translationX = ViewCompat.getTranslationX(child); 1373 final float translationY = ViewCompat.getTranslationY(child); 1374 if (x >= child.getLeft() + translationX && 1375 x <= child.getRight() + translationX && 1376 y >= child.getTop() + translationY && 1377 y <= child.getBottom() + translationY) { 1378 return child; 1379 } 1380 } 1381 return null; 1382 } 1383 1384 /** 1385 * Return the ViewHolder for the child view of the RecyclerView that is at the 1386 * given index. 1387 * 1388 * @param childIndex The index of the child in the RecyclerView's child list. 1389 * @return The ViewHolder for the given <code>childIndex</code> 1390 * 1391 * @deprecated Use {@link #getChildViewHolder(View)} and {@link #getChildAt(int)} 1392 */ 1393 public ViewHolder getViewHolderForChildAt(int childIndex) { 1394 return getChildViewHolderInt(getChildAt(childIndex)); 1395 } 1396 1397 /** 1398 * Offset the bounds of all child views by <code>dy</code> pixels. 1399 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 1400 * 1401 * @param dy Vertical pixel offset to apply to the bounds of all child views 1402 */ 1403 public void offsetChildrenVertical(int dy) { 1404 final int childCount = getChildCount(); 1405 for (int i = 0; i < childCount; i++) { 1406 getChildAt(i).offsetTopAndBottom(dy); 1407 } 1408 } 1409 1410 /** 1411 * Called when an item view is attached to this RecyclerView. 1412 * 1413 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 1414 * of child views as they become attached. This will be called before a 1415 * {@link LayoutManager} measures or lays out the view and is a good time to perform these 1416 * changes.</p> 1417 * 1418 * @param child Child view that is now attached to this RecyclerView and its associated window 1419 */ 1420 public void onChildAttachedToWindow(View child) { 1421 } 1422 1423 /** 1424 * Called when an item view is detached from this RecyclerView. 1425 * 1426 * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications 1427 * of child views as they become detached. This will be called as a 1428 * {@link LayoutManager} fully detaches the child view from the parent and its window.</p> 1429 * 1430 * @param child Child view that is now detached from this RecyclerView and its associated window 1431 */ 1432 public void onChildDetachedFromWindow(View child) { 1433 } 1434 1435 /** 1436 * Offset the bounds of all child views by <code>dx</code> pixels. 1437 * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. 1438 * 1439 * @param dx Horizontal pixel offset to apply to the bounds of all child views 1440 */ 1441 public void offsetChildrenHorizontal(int dx) { 1442 final int childCount = getChildCount(); 1443 for (int i = 0; i < childCount; i++) { 1444 getChildAt(i).offsetLeftAndRight(dx); 1445 } 1446 } 1447 1448 Rect getItemDecorInsetsForChild(View child) { 1449 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1450 if (!lp.mInsetsDirty) { 1451 return lp.mDecorInsets; 1452 } 1453 1454 final Rect insets = lp.mDecorInsets; 1455 insets.set(0, 0, 0, 0); 1456 final int decorCount = mItemDecorations.size(); 1457 for (int i = 0; i < decorCount; i++) { 1458 mTempRect.set(0, 0, 0, 0); 1459 mItemDecorations.get(i).getItemOffsets(mTempRect, lp.getViewPosition(), this); 1460 insets.left += mTempRect.left; 1461 insets.top += mTempRect.top; 1462 insets.right += mTempRect.right; 1463 insets.bottom += mTempRect.bottom; 1464 } 1465 lp.mInsetsDirty = false; 1466 return insets; 1467 } 1468 1469 private class ViewFlinger implements Runnable { 1470 private int mLastFlingX; 1471 private int mLastFlingY; 1472 private ScrollerCompat mScroller; 1473 private Interpolator mInterpolator = sQuinticInterpolator; 1474 1475 public ViewFlinger() { 1476 mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator); 1477 } 1478 1479 @Override 1480 public void run() { 1481 if (mScroller.computeScrollOffset()) { 1482 final int x = mScroller.getCurrX(); 1483 final int y = mScroller.getCurrY(); 1484 final int dx = x - mLastFlingX; 1485 final int dy = y - mLastFlingY; 1486 mLastFlingX = x; 1487 mLastFlingY = y; 1488 1489 int overscrollX = 0, overscrollY = 0; 1490 eatRequestLayout(); 1491 if (dx != 0) { 1492 final int hresult = mLayout.scrollHorizontallyBy(dx, getAdapter(), mRecycler); 1493 overscrollX = dx - hresult; 1494 } 1495 if (dy != 0) { 1496 final int vresult = mLayout.scrollVerticallyBy(dy, getAdapter(), mRecycler); 1497 overscrollY = dy - vresult; 1498 } 1499 resumeRequestLayout(false); 1500 1501 if (!mItemDecorations.isEmpty()) { 1502 invalidate(); 1503 } 1504 1505 if (overscrollX != 0 || overscrollY != 0) { 1506 final int vel = (int) mScroller.getCurrVelocity(); 1507 1508 int velX = 0; 1509 if (overscrollX != x) { 1510 velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0; 1511 } 1512 1513 int velY = 0; 1514 if (overscrollY != y) { 1515 velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0; 1516 } 1517 1518 absorbGlows(velX, velY); 1519 if ((velX != 0 || overscrollX == x || mScroller.getFinalX() == 0) && 1520 (velY != 0 || overscrollY == y || mScroller.getFinalY() == 0)) { 1521 mScroller.abortAnimation(); 1522 } 1523 } 1524 1525 if (mScrollListener != null && (x != 0 || y != 0)) { 1526 mScrollListener.onScrolled(dx, dy); 1527 } 1528 1529 if (mScroller.isFinished()) { 1530 setScrollState(SCROLL_STATE_IDLE); 1531 } else { 1532 ViewCompat.postOnAnimation(RecyclerView.this, this); 1533 } 1534 } 1535 } 1536 1537 public void fling(int velocityX, int velocityY) { 1538 setScrollState(SCROLL_STATE_SETTLING); 1539 mLastFlingX = mLastFlingY = 0; 1540 mScroller.fling(0, 0, velocityX, velocityY, 1541 Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 1542 ViewCompat.postOnAnimation(RecyclerView.this, this); 1543 } 1544 1545 public void smoothScrollBy(int dx, int dy) { 1546 smoothScrollBy(dx, dy, 0, 0); 1547 } 1548 1549 public void smoothScrollBy(int dx, int dy, int vx, int vy) { 1550 smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy)); 1551 } 1552 1553 private float distanceInfluenceForSnapDuration(float f) { 1554 f -= 0.5f; // center the values about 0. 1555 f *= 0.3f * Math.PI / 2.0f; 1556 return (float) Math.sin(f); 1557 } 1558 1559 private int computeScrollDuration(int dx, int dy, int vx, int vy) { 1560 final int absDx = Math.abs(dx); 1561 final int absDy = Math.abs(dy); 1562 final boolean horizontal = absDx > absDy; 1563 final int velocity = (int) Math.sqrt(vx * vx + vy * vy); 1564 final int delta = (int) Math.sqrt(dx * dx + dy * dy); 1565 final int containerSize = horizontal ? getWidth() : getHeight(); 1566 final int halfContainerSize = containerSize / 2; 1567 final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize); 1568 final float distance = halfContainerSize + halfContainerSize * 1569 distanceInfluenceForSnapDuration(distanceRatio); 1570 1571 final int duration; 1572 if (velocity > 0) { 1573 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 1574 } else { 1575 float absDelta = (float) (horizontal ? absDx : absDy); 1576 duration = (int) (((absDelta / containerSize) + 1) * 300); 1577 } 1578 return Math.min(duration, MAX_SCROLL_DURATION); 1579 } 1580 1581 public void smoothScrollBy(int dx, int dy, int duration) { 1582 smoothScrollBy(dx, dy, duration, sQuinticInterpolator); 1583 } 1584 1585 public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) { 1586 if (mInterpolator != interpolator) { 1587 mInterpolator = interpolator; 1588 mScroller = ScrollerCompat.create(getContext(), interpolator); 1589 } 1590 setScrollState(SCROLL_STATE_SETTLING); 1591 mLastFlingX = mLastFlingY = 0; 1592 mScroller.startScroll(0, 0, dx, dy, duration); 1593 ViewCompat.postOnAnimation(RecyclerView.this, this); 1594 } 1595 1596 public void stop() { 1597 removeCallbacks(this); 1598 } 1599 1600 } 1601 1602 private class RecyclerViewDataObserver extends AdapterDataObserver { 1603 @Override 1604 public void onChanged() { 1605 if (mAdapter.hasStableIds()) { 1606 // TODO Determine what actually changed 1607 markKnownViewsInvalid(); 1608 mStructureChanged = true; 1609 requestLayout(); 1610 } else { 1611 markKnownViewsInvalid(); 1612 mStructureChanged = true; 1613 requestLayout(); 1614 } 1615 } 1616 1617 @Override 1618 public void onItemRangeChanged(int positionStart, int itemCount) { 1619 postAdapterUpdate(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount)); 1620 } 1621 1622 @Override 1623 public void onItemRangeInserted(int positionStart, int itemCount) { 1624 postAdapterUpdate(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount)); 1625 } 1626 1627 @Override 1628 public void onItemRangeRemoved(int positionStart, int itemCount) { 1629 postAdapterUpdate(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount)); 1630 } 1631 } 1632 1633 public static class RecycledViewPool { 1634 private SparseArray<ArrayList<ViewHolder>> mScrap = 1635 new SparseArray<ArrayList<ViewHolder>>(); 1636 private SparseIntArray mMaxScrap = new SparseIntArray(); 1637 private int mAttachCount = 0; 1638 1639 private static final int DEFAULT_MAX_SCRAP = 5; 1640 1641 public void clear() { 1642 mScrap.clear(); 1643 } 1644 1645 /** @deprecated No longer needed */ 1646 public void reset(int typeCount) { 1647 } 1648 1649 public void setMaxRecycledViews(int viewType, int max) { 1650 mMaxScrap.put(viewType, max); 1651 final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); 1652 if (scrapHeap != null) { 1653 while (scrapHeap.size() > max) { 1654 scrapHeap.remove(scrapHeap.size() - 1); 1655 } 1656 } 1657 } 1658 1659 public ViewHolder getRecycledView(int viewType) { 1660 final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType); 1661 if (scrapHeap != null && !scrapHeap.isEmpty()) { 1662 final int index = scrapHeap.size() - 1; 1663 final ViewHolder scrap = scrapHeap.get(index); 1664 scrapHeap.remove(index); 1665 return scrap; 1666 } 1667 return null; 1668 } 1669 1670 public void putRecycledView(ViewHolder scrap) { 1671 final int viewType = scrap.getItemViewType(); 1672 final ArrayList scrapHeap = getScrapHeapForType(viewType); 1673 if (mMaxScrap.get(viewType) <= scrapHeap.size()) { 1674 return; 1675 } 1676 1677 scrap.mPosition = NO_POSITION; 1678 scrap.mItemId = NO_ID; 1679 scrap.clearFlagsForSharedPool(); 1680 scrapHeap.add(scrap); 1681 } 1682 1683 void attach(Adapter adapter) { 1684 mAttachCount++; 1685 } 1686 1687 void detach() { 1688 mAttachCount--; 1689 } 1690 1691 1692 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { 1693 if (mAttachCount == 1) { 1694 clear(); 1695 } 1696 } 1697 1698 private ArrayList<ViewHolder> getScrapHeapForType(int viewType) { 1699 ArrayList<ViewHolder> scrap = mScrap.get(viewType); 1700 if (scrap == null) { 1701 scrap = new ArrayList<ViewHolder>(); 1702 mScrap.put(viewType, scrap); 1703 if (mMaxScrap.indexOfKey(viewType) < 0) { 1704 mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); 1705 } 1706 } 1707 return scrap; 1708 } 1709 } 1710 1711 /** 1712 * A Recycler is responsible for managing scrapped or detached item views for reuse. 1713 * 1714 * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but 1715 * that has been marked for removal or reuse.</p> 1716 * 1717 * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for 1718 * an adapter's data set representing the data at a given position or item ID. 1719 * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. 1720 * If not, the view can be quickly reused by the LayoutManager with no further work. 1721 * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} 1722 * may be repositioned by a LayoutManager without remeasurement.</p> 1723 */ 1724 public final class Recycler { 1725 private final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>(); 1726 1727 private final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); 1728 private int mViewCacheMax = DEFAULT_CACHE_SIZE; 1729 1730 private RecycledViewPool mRecyclerPool; 1731 1732 private static final int DEFAULT_CACHE_SIZE = 2; 1733 1734 /** 1735 * Clear scrap views out of this recycler. Detached views contained within a 1736 * recycled view pool will remain. 1737 */ 1738 public void clear() { 1739 mAttachedScrap.clear(); 1740 mCachedViews.clear(); 1741 } 1742 1743 /** 1744 * Set the maximum number of detached, valid views we should retain for later use. 1745 * 1746 * @param viewCount Number of views to keep before sending views to the shared pool 1747 */ 1748 public void setViewCacheSize(int viewCount) { 1749 mViewCacheMax = viewCount; 1750 while (mCachedViews.size() > viewCount) { 1751 mCachedViews.remove(mCachedViews.size() - 1); 1752 } 1753 } 1754 1755 /** 1756 * @deprecated Use {@link 1757 * #getViewForPosition(android.support.v7.widget.RecyclerView.Adapter, int)} 1758 * instead. This method will be removed. 1759 */ 1760 public View getViewForPosition(int position) { 1761 return getViewForPosition(mAdapter, position); 1762 } 1763 1764 /** 1765 * Obtain a view initialized for the given position. 1766 * 1767 * <p>This method should be used by {@link LayoutManager} implementations to obtain 1768 * views to represent data from an {@link Adapter}.</p> 1769 * 1770 * <p>The Recycler may reuse a scrap or detached view from a shared pool if one is 1771 * available for the correct view type. If the adapter has not indicated that the 1772 * data at the given position has changed, the Recycler will attempt to hand back 1773 * a scrap view that was previously initialized for that data without rebinding.</p> 1774 * 1775 * @param adapter Adapter to use for binding and creating views 1776 * @param position Position to obtain a view for 1777 * @return A view representing the data at <code>position</code> from <code>adapter</code> 1778 */ 1779 public View getViewForPosition(Adapter adapter, int position) { 1780 ViewHolder holder; 1781 final int type = adapter.getItemViewType(position); 1782 if (adapter.hasStableIds()) { 1783 final long id = adapter.getItemId(position); 1784 holder = getScrapViewForId(id, type); 1785 } else { 1786 holder = getScrapViewForPosition(position, type); 1787 } 1788 1789 if (holder == null) { 1790 holder = adapter.createViewHolder(RecyclerView.this, type); 1791 if (DEBUG) Log.d(TAG, "getViewForPosition created new ViewHolder"); 1792 } 1793 1794 if (!holder.isBound() || holder.needsUpdate()) { 1795 if (DEBUG) { 1796 Log.d(TAG, "getViewForPosition unbound holder or needs update; updating..."); 1797 } 1798 adapter.bindViewHolder(holder, position); 1799 } 1800 1801 ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); 1802 if (lp == null) { 1803 lp = generateDefaultLayoutParams(); 1804 holder.itemView.setLayoutParams(lp); 1805 } else if (!checkLayoutParams(lp)) { 1806 lp = generateLayoutParams(lp); 1807 holder.itemView.setLayoutParams(lp); 1808 } 1809 ((LayoutParams) lp).mViewHolder = holder; 1810 1811 return holder.itemView; 1812 } 1813 1814 /** 1815 * @deprecated Renamed to {@link #recycleView(android.view.View)} to cause 1816 * less confusion between temporarily detached scrap views and fully detached 1817 * recycled views. This method will be removed. 1818 */ 1819 public void addDetachedScrapView(View scrap) { 1820 recycleView(scrap); 1821 } 1822 1823 /** 1824 * Recycle a detached view. The specified view will be added to a pool of views 1825 * for later rebinding and reuse. 1826 * 1827 * <p>A view must be fully detached before it may be recycled.</p> 1828 * 1829 * @param view Removed view for recycling 1830 */ 1831 public void recycleView(View view) { 1832 recycleViewHolder(getChildViewHolderInt(view)); 1833 } 1834 1835 void recycleViewHolder(ViewHolder holder) { 1836 if (holder.isScrap() || holder.itemView.getParent() != null) { 1837 throw new IllegalArgumentException( 1838 "Scrapped or attached views may not be recycled."); 1839 } 1840 1841 // Retire oldest cached views first 1842 if (mCachedViews.size() == mViewCacheMax && !mCachedViews.isEmpty()) { 1843 for (int i = 0; i < mCachedViews.size(); i++) { 1844 final ViewHolder cachedView = mCachedViews.get(i); 1845 if (cachedView.isRecyclable()) { 1846 mCachedViews.remove(i); 1847 getRecycledViewPool().putRecycledView(cachedView); 1848 dispatchViewRecycled(cachedView); 1849 break; 1850 } 1851 } 1852 } 1853 if (mCachedViews.size() < mViewCacheMax || !holder.isRecyclable()) { 1854 mCachedViews.add(holder); 1855 } else { 1856 getRecycledViewPool().putRecycledView(holder); 1857 dispatchViewRecycled(holder); 1858 } 1859 } 1860 1861 /** 1862 * Used as a fast path for unscrapping and recycling a view during a bulk operation. 1863 * The caller must call {@link #clearScrap()} when it's done to update the recycler's 1864 * internal bookkeeping. 1865 */ 1866 void quickRecycleScrapView(View view) { 1867 final ViewHolder holder = getChildViewHolderInt(view); 1868 holder.mScrapContainer = null; 1869 recycleViewHolder(holder); 1870 } 1871 1872 /** 1873 * @deprecated This method will be removed. Adding and removing views is the responsibility 1874 * of the LayoutManager; the Recycler will only be responsible for marking and tracking 1875 * views for reuse. This method no longer matches the definition of 'scrap'. 1876 */ 1877 public void detachAndScrapView(View scrap) { 1878 if (scrap.getParent() != RecyclerView.this) { 1879 throw new IllegalArgumentException("View " + scrap + " is not attached to " + 1880 RecyclerView.this); 1881 } 1882 mLayout.removeView(scrap); 1883 recycleView(scrap); 1884 } 1885 1886 /** 1887 * @deprecated This method will be removed. Moved to 1888 * {@link LayoutManager#detachAndScrapAttachedViews(android.support.v7.widget.RecyclerView.Recycler)} 1889 * to keep LayoutManager as the owner of attach/detach operations. 1890 */ 1891 public void scrapAllViewsAttached() { 1892 mLayout.detachAndScrapAttachedViews(this); 1893 } 1894 1895 /** 1896 * Mark an attached view as scrap. 1897 * 1898 * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible 1899 * for rebinding and reuse. Requests for a view for a given position may return a 1900 * reused or rebound scrap view instance.</p> 1901 * 1902 * @param view View to scrap 1903 */ 1904 void scrapView(View view) { 1905 final ViewHolder holder = getChildViewHolderInt(view); 1906 holder.setScrapContainer(this); 1907 mAttachedScrap.add(holder); 1908 } 1909 1910 /** 1911 * Remove a previously scrapped view from the pool of eligible scrap. 1912 * 1913 * <p>This view will no longer be eligible for reuse until re-scrapped or 1914 * until it is explicitly removed and recycled.</p> 1915 */ 1916 void unscrapView(ViewHolder holder) { 1917 mAttachedScrap.remove(holder); 1918 } 1919 1920 /** 1921 * @deprecated This method will be removed. Adding and removing views should be done 1922 * through the LayoutManager. Use 1923 * {@link LayoutManager#removeAndRecycleScrap(android.support.v7.widget.RecyclerView.Recycler)} 1924 * instead. 1925 */ 1926 public void detachDirtyScrapViews() { 1927 mLayout.removeAndRecycleScrap(this); 1928 } 1929 1930 int getScrapCount() { 1931 return mAttachedScrap.size(); 1932 } 1933 1934 View getScrapViewAt(int index) { 1935 return mAttachedScrap.get(index).itemView; 1936 } 1937 1938 void clearScrap() { 1939 mAttachedScrap.clear(); 1940 } 1941 1942 ViewHolder getScrapViewForPosition(int position, int type) { 1943 final int scrapCount = mAttachedScrap.size(); 1944 1945 // Try first for an exact, non-invalid match from scrap. 1946 for (int i = 0; i < scrapCount; i++) { 1947 final ViewHolder holder = mAttachedScrap.get(i); 1948 if (holder.getPosition() == position && !holder.isInvalid()) { 1949 if (holder.getItemViewType() != type) { 1950 Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" + 1951 " wrong view type! (found " + holder.getItemViewType() + 1952 " but expected " + type + ")"); 1953 break; 1954 } 1955 mAttachedScrap.remove(i); 1956 holder.setScrapContainer(null); 1957 if (DEBUG) { 1958 Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + 1959 ") found exact match in scrap: " + holder); 1960 } 1961 return holder; 1962 } 1963 } 1964 1965 // Search in our first-level recycled view cache. 1966 final int cacheSize = mCachedViews.size(); 1967 for (int i = 0; i < cacheSize; i++) { 1968 final ViewHolder holder = mCachedViews.get(i); 1969 if (holder.getPosition() == position) { 1970 mCachedViews.remove(i); 1971 if (holder.isInvalid() && holder.getItemViewType() != type) { 1972 // Can't use it. We don't know where it's been. 1973 if (DEBUG) { 1974 Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + 1975 ") found position match, but holder is invalid with type " + 1976 holder.getItemViewType()); 1977 } 1978 1979 if (holder.isRecyclable()) { 1980 getRecycledViewPool().putRecycledView(holder); 1981 } 1982 // Even if the holder wasn't officially recycleable, dispatch that it 1983 // was recycled anyway in case there are resources to unbind. 1984 dispatchViewRecycled(holder); 1985 1986 // Drop out of the cache search and try something else instead, 1987 // we won't find another match here. 1988 break; 1989 } 1990 if (DEBUG) { 1991 Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + 1992 ") found match in cache: " + holder); 1993 } 1994 return holder; 1995 } 1996 } 1997 1998 // Give up. Head to the shared pool. 1999 if (DEBUG) { 2000 Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + 2001 ") fetching from shared pool"); 2002 } 2003 return getRecycledViewPool().getRecycledView(type); 2004 } 2005 2006 ViewHolder getScrapViewForId(long id, int type) { 2007 // Look in our attached views first 2008 final int count = mAttachedScrap.size(); 2009 for (int i = 0; i < count; i++) { 2010 final ViewHolder holder = mAttachedScrap.get(i); 2011 if (holder.getItemId() == id) { 2012 if (type == holder.getItemViewType()) { 2013 mAttachedScrap.remove(i); 2014 holder.setScrapContainer(null); 2015 return holder; 2016 } else { 2017 break; 2018 } 2019 } 2020 } 2021 2022 // Search the first-level cache 2023 final int cacheSize = mCachedViews.size(); 2024 for (int i = 0; i < cacheSize; i++) { 2025 final ViewHolder holder = mCachedViews.get(i); 2026 if (holder.getItemId() == id) { 2027 mCachedViews.remove(i); 2028 return holder; 2029 } 2030 } 2031 2032 // That didn't work, look for an unordered view of the right type instead. 2033 // The holder's position won't match so the calling code will need to have 2034 // the adapter rebind it. 2035 return getRecycledViewPool().getRecycledView(type); 2036 } 2037 2038 void dispatchViewRecycled(ViewHolder holder) { 2039 if (mRecyclerListener != null) { 2040 mRecyclerListener.onViewRecycled(holder); 2041 } 2042 if (mAdapter != null) { 2043 mAdapter.onViewRecycled(holder); 2044 } 2045 if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); 2046 } 2047 2048 void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { 2049 clear(); 2050 getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter); 2051 } 2052 2053 void offsetPositionRecordsForInsert(int insertedAt, int count) { 2054 final int cachedCount = mCachedViews.size(); 2055 for (int i = 0; i < cachedCount; i++) { 2056 final ViewHolder holder = mCachedViews.get(i); 2057 if (holder != null && holder.getPosition() >= insertedAt) { 2058 if (DEBUG) { 2059 Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " + 2060 holder + " now at position " + (holder.mPosition + count)); 2061 } 2062 holder.mPosition += count; 2063 } 2064 } 2065 } 2066 2067 void offsetPositionRecordsForRemove(int removedFrom, int count) { 2068 final int removedEnd = removedFrom + count; 2069 final int cachedCount = mCachedViews.size(); 2070 for (int i = cachedCount - 1; i >= 0; i--) { 2071 final ViewHolder holder = mCachedViews.get(i); 2072 if (holder != null) { 2073 if (holder.getPosition() >= removedEnd) { 2074 if (DEBUG) { 2075 Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + 2076 " holder " + holder + " now at position " + 2077 (holder.mPosition - count)); 2078 } 2079 holder.mPosition -= count; 2080 } else if (holder.getPosition() >= removedFrom) { 2081 // Item for this view was removed. Dump it from the cache. 2082 if (DEBUG) { 2083 Log.d(TAG, "offsetPositionRecordsForRemove cached " + i + 2084 " holder " + holder + " now placed in pool"); 2085 } 2086 mCachedViews.remove(i); 2087 getRecycledViewPool().putRecycledView(holder); 2088 dispatchViewRecycled(holder); 2089 } 2090 } 2091 } 2092 } 2093 2094 void setRecycledViewPool(RecycledViewPool pool) { 2095 if (mRecyclerPool != null) { 2096 mRecyclerPool.detach(); 2097 } 2098 mRecyclerPool = pool; 2099 if (pool != null) { 2100 mRecyclerPool.attach(getAdapter()); 2101 } 2102 } 2103 2104 RecycledViewPool getRecycledViewPool() { 2105 if (mRecyclerPool == null) { 2106 mRecyclerPool = new RecycledViewPool(); 2107 } 2108 return mRecyclerPool; 2109 } 2110 2111 ViewHolder findViewHolderForPosition(int position) { 2112 final int cachedCount = mCachedViews.size(); 2113 for (int i = 0; i < cachedCount; i++) { 2114 final ViewHolder holder = mCachedViews.get(i); 2115 if (holder != null && holder.getPosition() == position) { 2116 mCachedViews.remove(i); 2117 return holder; 2118 } 2119 } 2120 return null; 2121 } 2122 2123 ViewHolder findViewHolderForItemId(long id) { 2124 if (!mAdapter.hasStableIds()) { 2125 return null; 2126 } 2127 2128 final int cachedCount = mCachedViews.size(); 2129 for (int i = 0; i < cachedCount; i++) { 2130 final ViewHolder holder = mCachedViews.get(i); 2131 if (holder != null && holder.getItemId() == id) { 2132 mCachedViews.remove(i); 2133 return holder; 2134 } 2135 } 2136 return null; 2137 } 2138 2139 void viewRangeUpdate(int positionStart, int itemCount) { 2140 final int positionEnd = positionStart + itemCount; 2141 final int cachedCount = mCachedViews.size(); 2142 for (int i = 0; i < cachedCount; i++) { 2143 final ViewHolder holder = mCachedViews.get(i); 2144 if (holder == null) { 2145 continue; 2146 } 2147 2148 final int pos = holder.getPosition(); 2149 if (pos >= positionStart && pos < positionEnd) { 2150 holder.addFlags(ViewHolder.FLAG_UPDATE); 2151 } 2152 } 2153 } 2154 2155 void markKnownViewsInvalid() { 2156 final int cachedCount = mCachedViews.size(); 2157 for (int i = 0; i < cachedCount; i++) { 2158 final ViewHolder holder = mCachedViews.get(i); 2159 if (holder != null) { 2160 holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 2161 } 2162 } 2163 } 2164 } 2165 2166 /** 2167 * Base class for an Adapter 2168 * 2169 * <p>Adapters provide a binding from an app-specific data set to views that are displayed 2170 * within a {@link RecyclerView}.</p> 2171 */ 2172 public static abstract class Adapter<VH extends ViewHolder> { 2173 private final AdapterDataObservable mObservable = new AdapterDataObservable(); 2174 private boolean mHasStableIds = false; 2175 2176 /** @deprecated */ 2177 private int mViewTypeCount = 1; 2178 2179 public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); 2180 public abstract void onBindViewHolder(VH holder, int position); 2181 2182 public final VH createViewHolder(ViewGroup parent, int viewType) { 2183 final VH holder = onCreateViewHolder(parent, viewType); 2184 holder.mItemViewType = viewType; 2185 return holder; 2186 } 2187 2188 public final void bindViewHolder(VH holder, int position) { 2189 holder.mPosition = position; 2190 if (hasStableIds()) { 2191 holder.mItemId = getItemId(position); 2192 } 2193 onBindViewHolder(holder, position); 2194 holder.setFlags(ViewHolder.FLAG_BOUND, 2195 ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); 2196 } 2197 2198 /** 2199 * Return the view type of the item at <code>position</code> for the purposes 2200 * of view recycling. 2201 * 2202 * <p>The default implementation of this method returns 0, making the assumption of 2203 * a single view type for the adapter. Unlike ListView adapters, types need not 2204 * be contiguous. Consider using id resources to uniquely identify item view types. 2205 * 2206 * @param position position to query 2207 * @return integer value identifying the type of the view needed to represent the item at 2208 * <code>position</code>. Type codes need not be contiguous. 2209 */ 2210 public int getItemViewType(int position) { 2211 return 0; 2212 } 2213 2214 /** 2215 * Set the number of item view types required by this adapter to display its data set. 2216 * This may not be changed while the adapter has observers - e.g. while the adapter 2217 * is set on a {#link RecyclerView}. 2218 * 2219 * @param count Number of item view types required 2220 * @see #getItemViewTypeCount() 2221 * @see #getItemViewType(int) 2222 * 2223 * @deprecated This method is no longer necessary. View types are now unbounded. 2224 */ 2225 public void setItemViewTypeCount(int count) { 2226 Log.w(TAG, "setItemViewTypeCount is deprecated and no longer needed."); 2227 mViewTypeCount = count; 2228 } 2229 2230 /** 2231 * Retrieve the number of item view types required by this adapter to display its data set. 2232 * 2233 * @return Number of item view types supported 2234 * @see #setItemViewTypeCount(int) 2235 * @see #getItemViewType(int) 2236 * 2237 * @deprecated This method is no longer necessary. View types are now unbounded. 2238 */ 2239 public final int getItemViewTypeCount() { 2240 Log.w(TAG, "getItemViewTypeCount is no longer needed. " + 2241 "View type count is now unbounded."); 2242 return mViewTypeCount; 2243 } 2244 2245 public void setHasStableIds(boolean hasStableIds) { 2246 if (hasObservers()) { 2247 throw new IllegalStateException("Cannot change whether this adapter has " + 2248 "stable IDs while the adapter has registered observers."); 2249 } 2250 mHasStableIds = hasStableIds; 2251 } 2252 2253 /** 2254 * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()} 2255 * would return false this method should return {@link #NO_ID}. The default implementation 2256 * of this method returns {@link #NO_ID}. 2257 * 2258 * @param position Adapter position to query 2259 * @return the stable ID of the item at position 2260 */ 2261 public long getItemId(int position) { 2262 return NO_ID; 2263 } 2264 2265 public abstract int getItemCount(); 2266 2267 /** 2268 * Returns true if this adapter publishes a unique <code>long</code> value that can 2269 * act as a key for the item at a given position in the data set. If that item is relocated 2270 * in the data set, the ID returned for that item should be the same. 2271 * 2272 * @return true if this adapter's items have stable IDs 2273 */ 2274 public final boolean hasStableIds() { 2275 return mHasStableIds; 2276 } 2277 2278 /** 2279 * Called when a view created by this adapter has been recycled. 2280 * 2281 * <p>A view is recycled when a {@link LayoutManager} decides that it no longer 2282 * needs to be attached to its parent {@link RecyclerView}. This can be because it has 2283 * fallen out of visibility or a set of cached views represented by views still 2284 * attached to the parent RecyclerView. If an item view has large or expensive data 2285 * bound to it such as large bitmaps, this may be a good place to release those 2286 * resources.</p> 2287 * 2288 * @param holder The ViewHolder for the view being recycled 2289 */ 2290 public void onViewRecycled(VH holder) { 2291 } 2292 2293 /** 2294 * Called when a view created by this adapter has been attached to a window. 2295 * 2296 * <p>This can be used as a reasonable signal that the view is about to be seen 2297 * by the user. If the adapter previously freed any resources in 2298 * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow} 2299 * those resources should be restored here.</p> 2300 * 2301 * @param holder Holder of the view being attached 2302 */ 2303 public void onViewAttachedToWindow(VH holder) { 2304 } 2305 2306 /** 2307 * Called when a view created by this adapter has been detached from its window. 2308 * 2309 * <p>Becoming detached from the window is not necessarily a permanent condition; 2310 * the consumer of an Adapter's views may choose to cache views offscreen while they 2311 * are not visible, attaching an detaching them as appropriate.</p> 2312 * 2313 * @param holder Holder of the view being detached 2314 */ 2315 public void onViewDetachedFromWindow(VH holder) { 2316 } 2317 2318 /** 2319 * Returns true if one or more observers are attached to this adapter. 2320 * @return true if this adapter has observers 2321 */ 2322 public final boolean hasObservers() { 2323 return mObservable.hasObservers(); 2324 } 2325 2326 /** 2327 * Register a new observer to listen for data changes. 2328 * 2329 * <p>The adapter may publish a variety of events describing specific changes. 2330 * Not all adapters may support all change types and some may fall back to a generic 2331 * {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged() 2332 * "something changed"} event if more specific data is not available.</p> 2333 * 2334 * <p>Components registering observers with an adapter are responsible for 2335 * {@link #unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver) 2336 * unregistering} those observers when finished.</p> 2337 * 2338 * @param observer Observer to register 2339 * 2340 * @see #unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver) 2341 */ 2342 public void registerAdapterDataObserver(AdapterDataObserver observer) { 2343 mObservable.registerObserver(observer); 2344 } 2345 2346 /** 2347 * Unregister an observer currently listening for data changes. 2348 * 2349 * <p>The unregistered observer will no longer receive events about changes 2350 * to the adapter.</p> 2351 * 2352 * @param observer Observer to unregister 2353 * 2354 * @see #registerAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver) 2355 */ 2356 public void unregisterAdapterDataObserver(AdapterDataObserver observer) { 2357 mObservable.unregisterObserver(observer); 2358 } 2359 2360 /** 2361 * Notify any registered observers that the data set has changed. 2362 * 2363 * <p>There are two different classes of data change events, item changes and structural 2364 * changes. Item changes are when a single item has its data updated but no positional 2365 * changes have occurred. Structural changes are when items are inserted, deleted or moved 2366 * within the data set.</p> 2367 * 2368 * <p>This event does not specify what about the data set has changed, forcing 2369 * any observers to assume that all existing items and structure may no longer be valid. 2370 * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> 2371 * 2372 * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events 2373 * for adapters that report that they have {@link #hasStableIds() stable IDs} when 2374 * this method is used. This can help for the purposes of animation and visual 2375 * object persistence but individual item views will still need to be rebound 2376 * and relaid out.</p> 2377 * 2378 * <p>If you are writing an adapter it will always be more efficient to use the more 2379 * specific change events if you can. Rely on <code>notifyDataSetChanged()</code> 2380 * as a last resort.</p> 2381 * 2382 * @see #notifyItemChanged(int) 2383 * @see #notifyItemInserted(int) 2384 * @see #notifyItemRemoved(int) 2385 * @see #notifyItemRangeChanged(int, int) 2386 * @see #notifyItemRangeInserted(int, int) 2387 * @see #notifyItemRangeRemoved(int, int) 2388 */ 2389 public final void notifyDataSetChanged() { 2390 mObservable.notifyChanged(); 2391 } 2392 2393 /** 2394 * Notify any registered observers that the item at <code>position</code> has changed. 2395 * 2396 * <p>This is an item change event, not a structural change event. It indicates that any 2397 * reflection of the data at <code>position</code> is out of date and should be updated. 2398 * The item at <code>position</code> retains the same identity.</p> 2399 * 2400 * @param position Position of the item that has changed 2401 * 2402 * @see #notifyItemRangeChanged(int, int) 2403 */ 2404 public final void notifyItemChanged(int position) { 2405 mObservable.notifyItemRangeChanged(position, 1); 2406 } 2407 2408 /** 2409 * Notify any registered observers that the <code>itemCount</code> items starting at 2410 * position <code>positionStart</code> have changed. 2411 * 2412 * <p>This is an item change event, not a structural change event. It indicates that 2413 * any reflection of the data in the given position range is out of date and should 2414 * be updated. The items in the given range retain the same identity.</p> 2415 * 2416 * @param positionStart Position of the first item that has changed 2417 * @param itemCount Number of items that have changed 2418 * 2419 * @see #notifyItemChanged(int) 2420 */ 2421 public final void notifyItemRangeChanged(int positionStart, int itemCount) { 2422 mObservable.notifyItemRangeChanged(positionStart, itemCount); 2423 } 2424 2425 /** 2426 * Notify any registered observers that the item reflected at <code>position</code> 2427 * has been newly inserted. The item previously at <code>position</code> is now at 2428 * position <code>position + 1</code>. 2429 * 2430 * <p>This is a structural change event. Representations of other existing items in the 2431 * data set are still considered up to date and will not be rebound, though their 2432 * positions may be altered.</p> 2433 * 2434 * @param position Position of the newly inserted item in the data set 2435 * 2436 * @see #notifyItemRangeInserted(int, int) 2437 */ 2438 public final void notifyItemInserted(int position) { 2439 mObservable.notifyItemRangeInserted(position, 1); 2440 } 2441 2442 /** 2443 * Notify any registered observers that the currently reflected <code>itemCount</code> 2444 * items starting at <code>positionStart</code> have been newly inserted. The items 2445 * previously located at <code>positionStart</code> and beyond can now be found starting 2446 * at position <code>positionStart + itemCount</code>. 2447 * 2448 * <p>This is a structural change event. Representations of other existing items in the 2449 * data set are still considered up to date and will not be rebound, though their positions 2450 * may be altered.</p> 2451 * 2452 * @param positionStart Position of the first item that was inserted 2453 * @param itemCount Number of items inserted 2454 * 2455 * @see #notifyItemInserted(int) 2456 */ 2457 public final void notifyItemRangeInserted(int positionStart, int itemCount) { 2458 mObservable.notifyItemRangeInserted(positionStart, itemCount); 2459 } 2460 2461 /** 2462 * Notify any registered observers that the item previously located at <code>position</code> 2463 * has been removed from the data set. The items previously located at and after 2464 * <code>position</code> may now be found at <code>oldPosition - 1</code>. 2465 * 2466 * <p>This is a structural change event. Representations of other existing items in the 2467 * data set are still considered up to date and will not be rebound, though their positions 2468 * may be altered.</p> 2469 * 2470 * @param position Position of the item that has now been removed 2471 * 2472 * @see #notifyItemRangeRemoved(int, int) 2473 */ 2474 public final void notifyItemRemoved(int position) { 2475 mObservable.notifyItemRangeRemoved(position, 1); 2476 } 2477 2478 /** 2479 * Notify any registered observers that the <code>itemCount</code> items previously 2480 * located at <code>positionStart</code> have been removed from the data set. The items 2481 * previously located at and after <code>positionStart + itemCount</code> may now be found 2482 * at <code>oldPosition - itemCount</code>. 2483 * 2484 * <p>This is a structural change event. Representations of other existing items in the data 2485 * set are still considered up to date and will not be rebound, though their positions 2486 * may be altered.</p> 2487 * 2488 * @param positionStart Previous position of the first item that was removed 2489 * @param itemCount Number of items removed from the data set 2490 */ 2491 public final void notifyItemRangeRemoved(int positionStart, int itemCount) { 2492 mObservable.notifyItemRangeRemoved(positionStart, itemCount); 2493 } 2494 } 2495 2496 /** 2497 * A <code>LayoutManager</code> is responsible for measuring and positioning item views 2498 * within a <code>RecyclerView</code> as well as determining the policy for when to recycle 2499 * item views that are no longer visible to the user. By changing the <code>LayoutManager</code> 2500 * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list, 2501 * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock 2502 * layout managers are provided for general use. 2503 */ 2504 public static abstract class LayoutManager { 2505 RecyclerView mRecyclerView; 2506 2507 /** 2508 * @deprecated LayoutManagers should not access the RecyclerView they are bound to directly. 2509 * See the other methods on LayoutManager for accessing child views and 2510 * container properties instead. <em>This method will be removed.</em> 2511 */ 2512 public final RecyclerView getRecyclerView() { 2513 return mRecyclerView; 2514 } 2515 2516 /** 2517 * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView 2518 * is attached to a window. 2519 * 2520 * <p>Subclass implementations should always call through to the superclass implementation. 2521 * </p> 2522 * 2523 * @param view The RecyclerView this LayoutManager is bound to 2524 */ 2525 public void onAttachedToWindow(RecyclerView view) { 2526 } 2527 2528 /** 2529 * Called when this LayoutManager is detached from its parent RecyclerView or when 2530 * its parent RecyclerView is detached from its window. 2531 * 2532 * <p>Subclass implementations should always call through to the superclass implementation. 2533 * </p> 2534 * 2535 * @param view The RecyclerView this LayoutManager is bound to 2536 */ 2537 public void onDetachedFromWindow(RecyclerView view) { 2538 } 2539 2540 /** 2541 * @deprecated Use 2542 * {@link #layoutChildren(RecyclerView.Adapter, RecyclerView.Recycler, boolean)} 2543 */ 2544 public void layoutChildren(Adapter adapter, Recycler recycler) { 2545 } 2546 2547 /** 2548 * Lay out all relevant child views from the given adapter. 2549 * 2550 * @param adapter Adapter that will supply and bind views from a data set 2551 * @param recycler Recycler to use for fetching potentially cached views for a position 2552 * @param structureChanged true if the structure of the data set has changed since 2553 * the last call to layoutChildren, false otherwise 2554 */ 2555 public void layoutChildren(Adapter adapter, Recycler recycler, boolean structureChanged) { 2556 layoutChildren(adapter, recycler); 2557 } 2558 2559 /** 2560 * Create a default <code>LayoutParams</code> object for a child of the RecyclerView. 2561 * 2562 * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type 2563 * to store extra information specific to the layout. Client code should subclass 2564 * {@link RecyclerView.LayoutParams} for this purpose.</p> 2565 * 2566 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 2567 * you must also override 2568 * {@link #checkLayoutParams(LayoutParams)}, 2569 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 2570 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 2571 * 2572 * @return A new LayoutParams for a child view 2573 */ 2574 public abstract LayoutParams generateDefaultLayoutParams(); 2575 2576 /** 2577 * Determines the validity of the supplied LayoutParams object. 2578 * 2579 * <p>This should check to make sure that the object is of the correct type 2580 * and all values are within acceptable ranges. The default implementation 2581 * returns <code>true</code> for non-null params.</p> 2582 * 2583 * @param lp LayoutParams object to check 2584 * @return true if this LayoutParams object is valid, false otherwise 2585 */ 2586 public boolean checkLayoutParams(LayoutParams lp) { 2587 return lp != null; 2588 } 2589 2590 /** 2591 * Create a LayoutParams object suitable for this LayoutManager, copying relevant 2592 * values from the supplied LayoutParams object if possible. 2593 * 2594 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 2595 * you must also override 2596 * {@link #checkLayoutParams(LayoutParams)}, 2597 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 2598 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 2599 * 2600 * @param lp Source LayoutParams object to copy values from 2601 * @return a new LayoutParams object 2602 */ 2603 public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { 2604 if (lp instanceof LayoutParams) { 2605 return new LayoutParams((LayoutParams) lp); 2606 } else if (lp instanceof MarginLayoutParams) { 2607 return new LayoutParams((MarginLayoutParams) lp); 2608 } else { 2609 return new LayoutParams(lp); 2610 } 2611 } 2612 2613 /** 2614 * Create a LayoutParams object suitable for this LayoutManager from 2615 * an inflated layout resource. 2616 * 2617 * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type 2618 * you must also override 2619 * {@link #checkLayoutParams(LayoutParams)}, 2620 * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and 2621 * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> 2622 * 2623 * @param c Context for obtaining styled attributes 2624 * @param attrs AttributeSet describing the supplied arguments 2625 * @return a new LayoutParams object 2626 */ 2627 public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { 2628 return new LayoutParams(c, attrs); 2629 } 2630 2631 /** 2632 * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled. 2633 * The default implementation does nothing and returns 0. 2634 * 2635 * @param dx distance to scroll by in pixels. X increases as scroll position 2636 * approaches the right. 2637 * @return The actual distance scrolled. The return value will be negative if dx was 2638 * negative and scrolling proceeeded in that direction. 2639 * <code>Math.abs(result)</code> may be less than dx if a boundary was reached. 2640 */ 2641 public int scrollHorizontallyBy(int dx, Adapter adapter, Recycler recycler) { 2642 return scrollHorizontallyBy(dx, recycler); 2643 } 2644 2645 /** 2646 * @deprecated API changed to include the Adapter to use. Override 2647 * {@link #scrollHorizontallyBy(int, android.support.v7.widget.RecyclerView.Adapter, 2648 * android.support.v7.widget.RecyclerView.Recycler)} instead. 2649 */ 2650 public int scrollHorizontallyBy(int dx, Recycler recycler) { 2651 return 0; 2652 } 2653 2654 /** 2655 * Scroll vertically by dy pixels in screen coordinates and return the distance traveled. 2656 * The default implementation does nothing and returns 0. 2657 * 2658 * @param dy distance to scroll in pixels. Y increases as scroll position 2659 * approaches the bottom. 2660 * @return The actual distance scrolled. The return value will be negative if dy was 2661 * negative and scrolling proceeeded in that direction. 2662 * <code>Math.abs(result)</code> may be less than dy if a boundary was reached. 2663 */ 2664 public int scrollVerticallyBy(int dy, Adapter adapter, Recycler recycler) { 2665 return scrollVerticallyBy(dy, recycler); 2666 } 2667 2668 /** 2669 * @deprecated API changed to include the Adapter to use. Override 2670 * {@link #scrollVerticallyBy(int, android.support.v7.widget.RecyclerView.Adapter, 2671 * android.support.v7.widget.RecyclerView.Recycler)} instead. 2672 */ 2673 public int scrollVerticallyBy(int dy, Recycler recycler) { 2674 return 0; 2675 } 2676 2677 /** 2678 * Query if horizontal scrolling is currently supported. The default implementation 2679 * returns false. 2680 * 2681 * @return True if this LayoutManager can scroll the current contents horizontally 2682 */ 2683 public boolean canScrollHorizontally() { 2684 return false; 2685 } 2686 2687 /** 2688 * Query if vertical scrolling is currently supported. The default implementation 2689 * returns false. 2690 * 2691 * @return True if this LayoutManager can scroll the current contents vertically 2692 */ 2693 public boolean canScrollVertically() { 2694 return false; 2695 } 2696 2697 /** 2698 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 2699 * use this method to add views obtained from a {@link Recycler} using 2700 * {@link Recycler#getViewForPosition(android.support.v7.widget.RecyclerView.Adapter, int)}. 2701 * 2702 * @param child View to add 2703 * @param index Index to add child at 2704 */ 2705 public void addView(View child, int index) { 2706 final ViewHolder holder = getChildViewHolderInt(child); 2707 if (holder.isScrap()) { 2708 holder.unScrap(); 2709 mRecyclerView.attachViewToParent(child, index, child.getLayoutParams()); 2710 if (DISPATCH_TEMP_DETACH) { 2711 ViewCompat.dispatchFinishTemporaryDetach(child); 2712 } 2713 } else { 2714 mRecyclerView.addView(child, index); 2715 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2716 lp.mInsetsDirty = true; 2717 final Adapter adapter = mRecyclerView.getAdapter(); 2718 if (adapter != null) { 2719 adapter.onViewAttachedToWindow(getChildViewHolderInt(child)); 2720 } 2721 mRecyclerView.onChildAttachedToWindow(child); 2722 } 2723 } 2724 2725 /** 2726 * Add a view to the currently attached RecyclerView if needed. LayoutManagers should 2727 * use this method to add views obtained from a {@link Recycler} using 2728 * {@link Recycler#getViewForPosition(android.support.v7.widget.RecyclerView.Adapter, int)}. 2729 * 2730 * @param child View to add 2731 */ 2732 public void addView(View child) { 2733 addView(child, -1); 2734 } 2735 2736 /** 2737 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 2738 * use this method to completely remove a child view that is no longer needed. 2739 * LayoutManagers should strongly consider recycling removed views using 2740 * {@link Recycler#recycleView(android.view.View)}. 2741 * 2742 * @param child View to remove 2743 */ 2744 public void removeView(View child) { 2745 final Adapter adapter = mRecyclerView.getAdapter(); 2746 if (adapter != null) { 2747 adapter.onViewDetachedFromWindow(getChildViewHolderInt(child)); 2748 } 2749 mRecyclerView.onChildDetachedFromWindow(child); 2750 mRecyclerView.removeView(child); 2751 } 2752 2753 /** 2754 * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should 2755 * use this method to completely remove a child view that is no longer needed. 2756 * LayoutManagers should strongly consider recycling removed views using 2757 * {@link Recycler#recycleView(android.view.View)}. 2758 * 2759 * @param index Index of the child view to remove 2760 */ 2761 public void removeViewAt(int index) { 2762 final View child = mRecyclerView.getChildAt(index); 2763 if (child != null) { 2764 final Adapter adapter = mRecyclerView.getAdapter(); 2765 if (adapter != null) { 2766 adapter.onViewDetachedFromWindow(getChildViewHolderInt(child)); 2767 } 2768 mRecyclerView.onChildDetachedFromWindow(child); 2769 mRecyclerView.removeViewAt(index); 2770 } 2771 } 2772 2773 /** 2774 * Remove all views from the currently attached RecyclerView. This will not recycle 2775 * any of the affected views; the LayoutManager is responsible for doing so if desired. 2776 */ 2777 public void removeAllViews() { 2778 final Adapter adapter = mRecyclerView.getAdapter(); 2779 if (adapter != null) { 2780 final int childCount = mRecyclerView.getChildCount(); 2781 for (int i = 0; i < childCount; i++) { 2782 final View child = mRecyclerView.getChildAt(i); 2783 adapter.onViewDetachedFromWindow(getChildViewHolderInt(child)); 2784 mRecyclerView.onChildDetachedFromWindow(child); 2785 } 2786 } 2787 mRecyclerView.removeAllViews(); 2788 } 2789 2790 /** 2791 * Temporarily detach a child view. 2792 * 2793 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 2794 * views currently attached to the RecyclerView. Generally LayoutManager implementations 2795 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 2796 * so that the detached view may be rebound and reused.</p> 2797 * 2798 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 2799 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 2800 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 2801 * before the LayoutManager entry point method called by RecyclerView returns.</p> 2802 * 2803 * @param child Child to detach 2804 */ 2805 public void detachView(View child) { 2806 if (DISPATCH_TEMP_DETACH) { 2807 ViewCompat.dispatchStartTemporaryDetach(child); 2808 } 2809 mRecyclerView.detachViewFromParent(child); 2810 } 2811 2812 /** 2813 * Temporarily detach a child view. 2814 * 2815 * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange 2816 * views currently attached to the RecyclerView. Generally LayoutManager implementations 2817 * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} 2818 * so that the detached view may be rebound and reused.</p> 2819 * 2820 * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> 2821 * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} 2822 * or {@link #removeDetachedView(android.view.View) fully remove} the detached view 2823 * before the LayoutManager entry point method called by RecyclerView returns.</p> 2824 * 2825 * @param index Index of the child to detach 2826 */ 2827 public void detachViewAt(int index) { 2828 if (DISPATCH_TEMP_DETACH) { 2829 ViewCompat.dispatchStartTemporaryDetach(mRecyclerView.getChildAt(index)); 2830 } 2831 mRecyclerView.detachViewFromParent(index); 2832 } 2833 2834 /** 2835 * Reattach a previously {@link #detachView(android.view.View) detached} view. 2836 * This method should not be used to reattach views that were previously 2837 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 2838 * 2839 * @param child Child to reattach 2840 * @param index Intended child index for child 2841 * @param lp LayoutParams for child 2842 */ 2843 public void attachView(View child, int index, LayoutParams lp) { 2844 mRecyclerView.attachViewToParent(child, index, lp); 2845 if (DISPATCH_TEMP_DETACH) { 2846 ViewCompat.dispatchFinishTemporaryDetach(child); 2847 } 2848 } 2849 2850 /** 2851 * Reattach a previously {@link #detachView(android.view.View) detached} view. 2852 * This method should not be used to reattach views that were previously 2853 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 2854 * 2855 * @param child Child to reattach 2856 * @param index Intended child index for child 2857 */ 2858 public void attachView(View child, int index) { 2859 attachView(child, index, (LayoutParams) child.getLayoutParams()); 2860 } 2861 2862 /** 2863 * Reattach a previously {@link #detachView(android.view.View) detached} view. 2864 * This method should not be used to reattach views that were previously 2865 * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. 2866 * 2867 * @param child Child to reattach 2868 */ 2869 public void attachView(View child) { 2870 attachView(child, -1); 2871 } 2872 2873 /** 2874 * Finish removing a view that was previously temporarily 2875 * {@link #detachView(android.view.View) detached}. 2876 * 2877 * @param child Detached child to remove 2878 */ 2879 public void removeDetachedView(View child) { 2880 mRecyclerView.removeDetachedView(child, false); 2881 } 2882 2883 /** 2884 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 2885 * 2886 * <p>Scrapping a view allows it to be rebound and reused to show updated or 2887 * different data.</p> 2888 * 2889 * @param child Child to detach and scrap 2890 * @param recycler Recycler to deposit the new scrap view into 2891 */ 2892 public void detachAndScrapView(View child, Recycler recycler) { 2893 detachView(child); 2894 recycler.scrapView(child); 2895 } 2896 2897 /** 2898 * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. 2899 * 2900 * <p>Scrapping a view allows it to be rebound and reused to show updated or 2901 * different data.</p> 2902 * 2903 * @param index Index of child to detach and scrap 2904 * @param recycler Recycler to deposit the new scrap view into 2905 */ 2906 public void detachAndScrapViewAt(int index, Recycler recycler) { 2907 final View child = getChildAt(index); 2908 detachViewAt(index); 2909 recycler.scrapView(child); 2910 } 2911 2912 /** 2913 * Remove a child view and recycle it using the given Recycler. 2914 * 2915 * @param child Child to remove and recycle 2916 * @param recycler Recycler to use to recycle child 2917 */ 2918 public void removeAndRecycleView(View child, Recycler recycler) { 2919 removeView(child); 2920 recycler.recycleView(child); 2921 } 2922 2923 /** 2924 * Remove a child view and recycle it using the given Recycler. 2925 * 2926 * @param index Index of child to remove and recycle 2927 * @param recycler Recycler to use to recycle child 2928 */ 2929 public void removeAndRecycleViewAt(int index, Recycler recycler) { 2930 final View view = getChildAt(index); 2931 removeViewAt(index); 2932 recycler.recycleView(view); 2933 } 2934 2935 /** 2936 * Return the current number of child views attached to the parent RecyclerView. 2937 * This does not include child views that were temporarily detached and/or scrapped. 2938 * 2939 * @return Number of attached children 2940 */ 2941 public int getChildCount() { 2942 return mRecyclerView != null ? mRecyclerView.getChildCount() : 0; 2943 } 2944 2945 /** 2946 * Return the child view at the given index 2947 * @param index Index of child to return 2948 * @return Child view at index 2949 */ 2950 public View getChildAt(int index) { 2951 return mRecyclerView != null ? mRecyclerView.getChildAt(index) : null; 2952 } 2953 2954 /** 2955 * Return the width of the parent RecyclerView 2956 * 2957 * @return Width in pixels 2958 */ 2959 public int getWidth() { 2960 return mRecyclerView != null ? mRecyclerView.getWidth() : 0; 2961 } 2962 2963 /** 2964 * Return the height of the parent RecyclerView 2965 * 2966 * @return Height in pixels 2967 */ 2968 public int getHeight() { 2969 return mRecyclerView != null ? mRecyclerView.getHeight() : 0; 2970 } 2971 2972 /** 2973 * Return the left padding of the parent RecyclerView 2974 * 2975 * @return Padding in pixels 2976 */ 2977 public int getPaddingLeft() { 2978 return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; 2979 } 2980 2981 /** 2982 * Return the top padding of the parent RecyclerView 2983 * 2984 * @return Padding in pixels 2985 */ 2986 public int getPaddingTop() { 2987 return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; 2988 } 2989 2990 /** 2991 * Return the right padding of the parent RecyclerView 2992 * 2993 * @return Padding in pixels 2994 */ 2995 public int getPaddingRight() { 2996 return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; 2997 } 2998 2999 /** 3000 * Return the bottom padding of the parent RecyclerView 3001 * 3002 * @return Padding in pixels 3003 */ 3004 public int getPaddingBottom() { 3005 return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; 3006 } 3007 3008 /** 3009 * Return the start padding of the parent RecyclerView 3010 * 3011 * @return Padding in pixels 3012 */ 3013 public int getPaddingStart() { 3014 return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0; 3015 } 3016 3017 /** 3018 * Return the end padding of the parent RecyclerView 3019 * 3020 * @return Padding in pixels 3021 */ 3022 public int getPaddingEnd() { 3023 return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0; 3024 } 3025 3026 /** 3027 * Returns true if the RecyclerView this LayoutManager is bound to has focus. 3028 * 3029 * @return True if the RecyclerView has focus, false otherwise. 3030 * @see View#isFocused() 3031 */ 3032 public boolean isFocused() { 3033 return mRecyclerView != null && mRecyclerView.isFocused(); 3034 } 3035 3036 /** 3037 * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus. 3038 * 3039 * @return true if the RecyclerView has or contains focus 3040 * @see View#hasFocus() 3041 */ 3042 public boolean hasFocus() { 3043 return mRecyclerView != null && mRecyclerView.hasFocus(); 3044 } 3045 3046 /** 3047 * Return the number of items in the adapter bound to the parent RecyclerView 3048 * 3049 * @return Items in the bound adapter 3050 */ 3051 public int getItemCount() { 3052 final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null; 3053 return a != null ? a.getItemCount() : 0; 3054 } 3055 3056 /** 3057 * Offset all child views attached to the parent RecyclerView by dx pixels along 3058 * the horizontal axis. 3059 * 3060 * @param dx Pixels to offset by 3061 */ 3062 public void offsetChildrenHorizontal(int dx) { 3063 if (mRecyclerView != null) { 3064 mRecyclerView.offsetChildrenHorizontal(dx); 3065 } 3066 } 3067 3068 /** 3069 * Offset all child views attached to the parent RecyclerView by dy pixels along 3070 * the vertical axis. 3071 * 3072 * @param dy Pixels to offset by 3073 */ 3074 public void offsetChildrenVertical(int dy) { 3075 if (mRecyclerView != null) { 3076 mRecyclerView.offsetChildrenVertical(dy); 3077 } 3078 } 3079 3080 /** 3081 * Temporarily detach and scrap all currently attached child views. Views will be scrapped 3082 * into the given Recycler. The Recycler may prefer to reuse scrap views before 3083 * other views that were previously recycled. 3084 * 3085 * @param recycler Recycler to scrap views into 3086 */ 3087 public void detachAndScrapAttachedViews(Recycler recycler) { 3088 final int childCount = getChildCount(); 3089 for (int i = childCount - 1; i >= 0; i--) { 3090 final View v = getChildAt(i); 3091 detachViewAt(i); 3092 recycler.scrapView(v); 3093 } 3094 } 3095 3096 /** 3097 * Remove and recycle all scrap views currently tracked by Recycler. Recycled views 3098 * will be made available for later reuse. 3099 * 3100 * @param recycler Recycler tracking scrap views to remove 3101 */ 3102 public void removeAndRecycleScrap(Recycler recycler) { 3103 final int scrapCount = recycler.getScrapCount(); 3104 for (int i = 0; i < scrapCount; i++) { 3105 final View scrap = recycler.getScrapViewAt(i); 3106 mRecyclerView.removeDetachedView(scrap, false); 3107 recycler.quickRecycleScrapView(scrap); 3108 } 3109 recycler.clearScrap(); 3110 } 3111 3112 /** 3113 * Measure a child view using standard measurement policy, taking the padding 3114 * of the parent RecyclerView and any added item decorations into account. 3115 * 3116 * <p>If the RecyclerView can be scrolled in either dimension the caller may 3117 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 3118 * 3119 * @param child Child view to measure 3120 * @param widthUsed Width in pixels currently consumed by other views, if relevant 3121 * @param heightUsed Height in pixels currently consumed by other views, if relevant 3122 */ 3123 public void measureChild(View child, int widthUsed, int heightUsed) { 3124 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 3125 3126 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 3127 widthUsed += insets.left + insets.right; 3128 heightUsed += insets.top + insets.bottom; 3129 3130 final int widthSpec = getChildMeasureSpec(getWidth(), 3131 getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, 3132 canScrollHorizontally()); 3133 final int heightSpec = getChildMeasureSpec(getHeight(), 3134 getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, 3135 canScrollVertically()); 3136 child.measure(widthSpec, heightSpec); 3137 } 3138 3139 /** 3140 * Measure a child view using standard measurement policy, taking the padding 3141 * of the parent RecyclerView, any added item decorations and the child margins 3142 * into account. 3143 * 3144 * <p>If the RecyclerView can be scrolled in either dimension the caller may 3145 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> 3146 * 3147 * @param child Child view to measure 3148 * @param widthUsed Width in pixels currently consumed by other views, if relevant 3149 * @param heightUsed Height in pixels currently consumed by other views, if relevant 3150 */ 3151 public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { 3152 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 3153 3154 final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 3155 widthUsed += insets.left + insets.right; 3156 heightUsed += insets.top + insets.bottom; 3157 3158 final int widthSpec = getChildMeasureSpec(getWidth(), 3159 getPaddingLeft() + getPaddingRight() + 3160 lp.leftMargin + lp.rightMargin + widthUsed, lp.width, 3161 canScrollHorizontally()); 3162 final int heightSpec = getChildMeasureSpec(getHeight(), 3163 getPaddingTop() + getPaddingBottom() + 3164 lp.topMargin + lp.bottomMargin + heightUsed, lp.height, 3165 canScrollVertically()); 3166 child.measure(widthSpec, heightSpec); 3167 } 3168 3169 /** 3170 * Calculate a MeasureSpec value for measuring a child view in one dimension. 3171 * 3172 * @param parentSize Size of the parent view where the child will be placed 3173 * @param padding Total space currently consumed by other elements of parent 3174 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. 3175 * Generally obtained from the child view's LayoutParams 3176 * @param canScroll true if the parent RecyclerView can scroll in this dimension 3177 * 3178 * @return a MeasureSpec value for the child view 3179 */ 3180 public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, 3181 boolean canScroll) { 3182 int size = Math.max(0, parentSize - padding); 3183 int resultSize = 0; 3184 int resultMode = 0; 3185 3186 if (canScroll) { 3187 if (childDimension >= 0) { 3188 resultSize = childDimension; 3189 resultMode = MeasureSpec.EXACTLY; 3190 } else { 3191 // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap 3192 // instead using UNSPECIFIED. 3193 resultSize = 0; 3194 resultMode = MeasureSpec.UNSPECIFIED; 3195 } 3196 } else { 3197 if (childDimension >= 0) { 3198 resultSize = childDimension; 3199 resultMode = MeasureSpec.EXACTLY; 3200 } else if (childDimension == LayoutParams.FILL_PARENT) { 3201 resultSize = size; 3202 resultMode = MeasureSpec.EXACTLY; 3203 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 3204 resultSize = size; 3205 resultMode = MeasureSpec.AT_MOST; 3206 } 3207 } 3208 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 3209 } 3210 3211 /** 3212 * Returns the measured width of the given child, plus the additional size of 3213 * any insets applied by {@link ItemDecoration ItemDecorations}. 3214 * 3215 * @param child Child view to query 3216 * @return child's measured width plus <code>ItemDecoration</code> insets 3217 * 3218 * @see View#getMeasuredWidth() 3219 */ 3220 public int getDecoratedMeasuredWidth(View child) { 3221 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 3222 return child.getMeasuredWidth() + insets.left + insets.right; 3223 } 3224 3225 /** 3226 * Returns the measured height of the given child, plus the additional size of 3227 * any insets applied by {@link ItemDecoration ItemDecorations}. 3228 * 3229 * @param child Child view to query 3230 * @return child's measured height plus <code>ItemDecoration</code> insets 3231 * 3232 * @see View#getMeasuredHeight() 3233 */ 3234 public int getDecoratedMeasuredHeight(View child) { 3235 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 3236 return child.getMeasuredHeight() + insets.top + insets.bottom; 3237 } 3238 3239 /** 3240 * Lay out the given child view within the RecyclerView using coordinates that 3241 * include any current {@link ItemDecoration ItemDecorations}. 3242 * 3243 * <p>LayoutManagers should prefer working in sizes and coordinates that include 3244 * item decoration insets whenever possible. This allows the LayoutManager to effectively 3245 * ignore decoration insets within measurement and layout code. See the following 3246 * methods:</p> 3247 * <ul> 3248 * <li>{@link #measureChild(View, int, int)}</li> 3249 * <li>{@link #measureChildWithMargins(View, int, int)}</li> 3250 * <li>{@link #getDecoratedLeft(View)}</li> 3251 * <li>{@link #getDecoratedTop(View)}</li> 3252 * <li>{@link #getDecoratedRight(View)}</li> 3253 * <li>{@link #getDecoratedBottom(View)}</li> 3254 * <li>{@link #getDecoratedMeasuredWidth(View)}</li> 3255 * <li>{@link #getDecoratedMeasuredHeight(View)}</li> 3256 * </ul> 3257 * 3258 * @param child Child to lay out 3259 * @param left Left edge, with item decoration insets included 3260 * @param top Top edge, with item decoration insets included 3261 * @param right Right edge, with item decoration insets included 3262 * @param bottom Bottom edge, with item decoration insets included 3263 * 3264 * @see View#layout(int, int, int, int) 3265 */ 3266 public void layoutDecorated(View child, int left, int top, int right, int bottom) { 3267 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 3268 child.layout(left + insets.left, top + insets.top, right - insets.right, 3269 bottom - insets.bottom); 3270 } 3271 3272 /** 3273 * Returns the left edge of the given child view within its parent, offset by any applied 3274 * {@link ItemDecoration ItemDecorations}. 3275 * 3276 * @param child Child to query 3277 * @return Child left edge with offsets applied 3278 */ 3279 public int getDecoratedLeft(View child) { 3280 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 3281 return child.getLeft() - insets.left; 3282 } 3283 3284 /** 3285 * Returns the top edge of the given child view within its parent, offset by any applied 3286 * {@link ItemDecoration ItemDecorations}. 3287 * 3288 * @param child Child to query 3289 * @return Child top edge with offsets applied 3290 */ 3291 public int getDecoratedTop(View child) { 3292 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 3293 return child.getTop() - insets.top; 3294 } 3295 3296 /** 3297 * Returns the right edge of the given child view within its parent, offset by any applied 3298 * {@link ItemDecoration ItemDecorations}. 3299 * 3300 * @param child Child to query 3301 * @return Child right edge with offsets applied 3302 */ 3303 public int getDecoratedRight(View child) { 3304 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 3305 return child.getRight() + insets.right; 3306 } 3307 3308 /** 3309 * Returns the bottom edge of the given child view within its parent, offset by any applied 3310 * {@link ItemDecoration ItemDecorations}. 3311 * 3312 * @param child Child to query 3313 * @return Child bottom edge with offsets applied 3314 */ 3315 public int getDecoratedBottom(View child) { 3316 final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; 3317 return child.getBottom() + insets.bottom; 3318 } 3319 3320 /** 3321 * Called when searching for a focusable view in the given direction has failed 3322 * for the current content of the RecyclerView. 3323 * 3324 * <p>This is the LayoutManager's opportunity to populate views in the given direction 3325 * to fulfill the request if it can. The LayoutManager should attach and return 3326 * the view to be focused. The default implementation returns null.</p> 3327 * 3328 * @param focused The currently focused view 3329 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 3330 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 3331 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 3332 * or 0 for not applicable 3333 * @param adapter Adapter to use for obtaining new views 3334 * @param recycler The recycler to use for obtaining views for currently offscreen items 3335 * @return The chosen view to be focused 3336 */ 3337 public View onFocusSearchFailed(View focused, int direction, Adapter adapter, 3338 Recycler recycler) { 3339 return onFocusSearchFailed(focused, direction, recycler); 3340 } 3341 3342 /** 3343 * @deprecated API changed to supply the Adapter. Override 3344 * {@link #onFocusSearchFailed(android.view.View, int, 3345 * android.support.v7.widget.RecyclerView.Adapter, 3346 * android.support.v7.widget.RecyclerView.Recycler)} instead. 3347 */ 3348 public View onFocusSearchFailed(View focused, int direction, Recycler recycler) { 3349 return null; 3350 } 3351 3352 /** 3353 * This method gives a LayoutManager an opportunity to intercept the initial focus search 3354 * before the default behavior of {@link FocusFinder} is used. If this method returns 3355 * null FocusFinder will attempt to find a focusable child view. If it fails 3356 * then {@link #onFocusSearchFailed(View, int, RecyclerView.Adapter, RecyclerView.Recycler)} 3357 * will be called to give the LayoutManager an opportunity to add new views for items 3358 * that did not have attached views representing them. The LayoutManager should not add 3359 * or remove views from this method. 3360 * 3361 * @param focused The currently focused view 3362 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 3363 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 3364 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 3365 * @return A descendant view to focus or null to fall back to default behavior. 3366 * The default implementation returns null. 3367 */ 3368 public View onInterceptFocusSearch(View focused, int direction) { 3369 return null; 3370 } 3371 3372 /** 3373 * @deprecated This method will be removed. Override {@link #requestChildRectangleOnScreen( 3374 * RecyclerView, android.view.View, android.graphics.Rect, boolean)} instead. 3375 */ 3376 public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { 3377 return requestChildRectangleOnScreen(mRecyclerView, child, rect, immediate); 3378 } 3379 3380 /** 3381 * Called when a child of the RecyclerView wants a particular rectangle to be positioned 3382 * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View, 3383 * android.graphics.Rect, boolean)} for more details. 3384 * 3385 * <p>The base implementation will attempt to perform a standard programmatic scroll 3386 * to bring the given rect into view, within the padded area of the RecyclerView.</p> 3387 * 3388 * @param child The direct child making the request. 3389 * @param rect The rectangle in the child's coordinates the child 3390 * wishes to be on the screen. 3391 * @param immediate True to forbid animated or delayed scrolling, 3392 * false otherwise 3393 * @return Whether the group scrolled to handle the operation 3394 */ 3395 public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect, 3396 boolean immediate) { 3397 final int parentLeft = getPaddingLeft(); 3398 final int parentTop = getPaddingTop(); 3399 final int parentRight = getWidth() - getPaddingRight(); 3400 final int parentBottom = getHeight() - getPaddingBottom(); 3401 final int childLeft = child.getLeft() + rect.left; 3402 final int childTop = child.getTop() + rect.top; 3403 final int childRight = childLeft + rect.right; 3404 final int childBottom = childTop + rect.bottom; 3405 3406 final int offScreenLeft = Math.min(0, childLeft - parentLeft); 3407 final int offScreenTop = Math.min(0, childTop - parentTop); 3408 final int offScreenRight = Math.max(0, childRight - parentRight); 3409 final int offScreenBottom = Math.max(0, childBottom - parentBottom); 3410 3411 // Favor the "start" layout direction over the end when bringing one side or the other 3412 // of a large rect into view. 3413 final int dx; 3414 if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) { 3415 dx = offScreenRight != 0 ? offScreenRight : offScreenLeft; 3416 } else { 3417 dx = offScreenLeft != 0 ? offScreenLeft : offScreenRight; 3418 } 3419 3420 // Favor bringing the top into view over the bottom 3421 final int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom; 3422 3423 if (dx != 0 || dy != 0) { 3424 if (immediate) { 3425 parent.scrollBy(dx, dy); 3426 } else { 3427 parent.smoothScrollBy(dx, dy); 3428 } 3429 return true; 3430 } 3431 return false; 3432 } 3433 3434 /** 3435 * Called when a descendant view of the RecyclerView requests focus. 3436 * 3437 * <p>A LayoutManager wishing to keep focused views aligned in a specific 3438 * portion of the view may implement that behavior in an override of this method.</p> 3439 * 3440 * <p>If the LayoutManager executes different behavior that should override the default 3441 * behavior of scrolling the focused child on screen instead of running alongside it, 3442 * this method should return true.</p> 3443 * 3444 * @param parent The RecyclerView hosting this LayoutManager 3445 * @param child Direct child of the RecyclerView containing the newly focused view 3446 * @param focused The newly focused view. This may be the same view as child 3447 * @return true if the default scroll behavior should be suppressed 3448 */ 3449 public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) { 3450 return onRequestChildFocus(child, focused); 3451 } 3452 3453 /** 3454 * @deprecated This method will be removed. Override 3455 * {@link #onRequestChildFocus(RecyclerView, android.view.View, android.view.View)} 3456 * instead. 3457 */ 3458 public boolean onRequestChildFocus(View child, View focused) { 3459 return false; 3460 } 3461 3462 /** 3463 * Called if the RecyclerView this LayoutManager is bound to has a different adapter set. 3464 * The LayoutManager may use this opportunity to clear caches and configure state such 3465 * that it can relayout appropriately with the new data and potentially new view types. 3466 * 3467 * <p>The default implementation removes all currently attached views.</p> 3468 */ 3469 public void onAdapterChanged() { 3470 removeAllViews(); 3471 } 3472 3473 /** 3474 * Called to populate focusable views within the RecyclerView. 3475 * 3476 * <p>The LayoutManager implementation should return <code>true</code> if the default 3477 * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be 3478 * suppressed.</p> 3479 * 3480 * <p>The default implementation returns <code>false</code> to trigger RecyclerView 3481 * to fall back to the default ViewGroup behavior.</p> 3482 * 3483 * @param views List of output views. This method should add valid focusable views 3484 * to this list. 3485 * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, 3486 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, 3487 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} 3488 * @param focusableMode The type of focusables to be added. 3489 * 3490 * @return true to suppress the default behavior, false to add default focusables after 3491 * this method returns. 3492 * 3493 * @see #FOCUSABLES_ALL 3494 * @see #FOCUSABLES_TOUCH_MODE 3495 */ 3496 public boolean onAddFocusables(List<View> views, int direction, int focusableMode) { 3497 return false; 3498 } 3499 3500 /** 3501 * Called when items have been added to the adapter. The LayoutManager may choose to 3502 * requestLayout if the inserted items would require refreshing the currently visible set 3503 * of child views. (e.g. currently empty space would be filled by appended items, etc.) 3504 * 3505 * @param recyclerView 3506 * @param positionStart 3507 * @param itemCount 3508 */ 3509 public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { 3510 } 3511 3512 /** 3513 * Called when items have been removed from the adapter. 3514 * 3515 * @param recyclerView 3516 * @param positionStart 3517 * @param itemCount 3518 */ 3519 public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { 3520 } 3521 } 3522 3523 /** 3524 * An ItemDecoration allows the application to add a special drawing and layout offset 3525 * to specific item views from the adapter's data set. This can be useful for drawing dividers 3526 * between items, highlights, visual grouping boundaries and more. 3527 * 3528 * <p>All ItemDecorations are drawn in the order they were added, before the item 3529 * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView) onDraw()} and after the items 3530 * (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView)}.</p> 3531 */ 3532 public static abstract class ItemDecoration { 3533 /** 3534 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 3535 * Any content drawn by this method will be drawn before the item views are drawn, 3536 * and will thus appear underneath the views. 3537 * 3538 * @param c Canvas to draw into 3539 * @param parent RecyclerView this ItemDecoration is drawing into 3540 */ 3541 public void onDraw(Canvas c, RecyclerView parent) { 3542 } 3543 3544 /** 3545 * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. 3546 * Any content drawn by this method will be drawn after the item views are drawn 3547 * and will thus appear over the views. 3548 * 3549 * @param c Canvas to draw into 3550 * @param parent RecyclerView this ItemDecoration is drawing into 3551 */ 3552 public void onDrawOver(Canvas c, RecyclerView parent) { 3553 } 3554 3555 /** 3556 * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies 3557 * the number of pixels that the item view should be inset by, similar to padding or margin. 3558 * The default implementation sets the bounds of outRect to 0 and returns. 3559 * 3560 * <p>If this ItemDecoration does not affect the positioning of item views it should set 3561 * all four fields of <code>outRect</code> (left, top, right, bottom) to zero 3562 * before returning.</p> 3563 * 3564 * @param outRect Rect to receive the output. 3565 * @param itemPosition Adapter position of the item to offset 3566 * @param parent RecyclerView this ItemDecoration is decorating 3567 */ 3568 public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 3569 outRect.set(0, 0, 0, 0); 3570 } 3571 } 3572 3573 /** 3574 * An OnItemTouchListener allows the application to intercept touch events in progress at the 3575 * view hierarchy level of the RecyclerView before those touch events are considered for 3576 * RecyclerView's own scrolling behavior. 3577 * 3578 * <p>This can be useful for applications that wish to implement various forms of gestural 3579 * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept 3580 * a touch interaction already in progress even if the RecyclerView is already handling that 3581 * gesture stream itself for the purposes of scrolling.</p> 3582 */ 3583 public interface OnItemTouchListener { 3584 /** 3585 * Silently observe and/or take over touch events sent to the RecyclerView 3586 * before they are handled by either the RecyclerView itself or its child views. 3587 * 3588 * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run 3589 * in the order in which each listener was added, before any other touch processing 3590 * by the RecyclerView itself or child views occurs.</p> 3591 * 3592 * @param e MotionEvent describing the touch event. All coordinates are in 3593 * the RecyclerView's coordinate system. 3594 * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false 3595 * to continue with the current behavior and continue observing future events in 3596 * the gesture. 3597 */ 3598 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e); 3599 3600 /** 3601 * Process a touch event as part of a gesture that was claimed by returning true from 3602 * a previous call to {@link #onInterceptTouchEvent}. 3603 * 3604 * @param e MotionEvent describing the touch event. All coordinates are in 3605 * the RecyclerView's coordinate system. 3606 */ 3607 public void onTouchEvent(RecyclerView rv, MotionEvent e); 3608 } 3609 3610 /** 3611 * An OnScrollListener can be set on a RecyclerView to receive messages 3612 * when a scrolling event has occurred on that RecyclerView. 3613 * 3614 * @see RecyclerView#setOnScrollListener(OnScrollListener) 3615 */ 3616 public interface OnScrollListener { 3617 public void onScrollStateChanged(int newState); 3618 public void onScrolled(int dx, int dy); 3619 } 3620 3621 /** 3622 * A RecyclerListener can be set on a RecyclerView to receive messages whenever 3623 * a view is recycled. 3624 * 3625 * @see RecyclerView#setRecyclerListener(RecyclerListener) 3626 */ 3627 public interface RecyclerListener { 3628 3629 /** 3630 * This method is called whenever the view in the ViewHolder is recycled. 3631 * 3632 * @param holder The ViewHolder containing the view that was recycled 3633 */ 3634 public void onViewRecycled(ViewHolder holder); 3635 } 3636 3637 /** 3638 * A ViewHolder describes an item view and metadata about its place within the RecyclerView. 3639 * 3640 * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching 3641 * potentially expensive {@link View#findViewById(int)} results.</p> 3642 * 3643 * <p>While {@link LayoutParams} belong to the {@link LayoutManager}, 3644 * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use 3645 * their own custom ViewHolder implementations to store data that makes binding view contents 3646 * easier. Implementations should assume that individual item views will hold strong references 3647 * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold 3648 * strong references to extra off-screen item views for caching purposes</p> 3649 */ 3650 public static abstract class ViewHolder { 3651 public final View itemView; 3652 3653 int mPosition = NO_POSITION; 3654 long mItemId = NO_ID; 3655 int mItemViewType = INVALID_TYPE; 3656 3657 /** 3658 * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType 3659 * are all valid. 3660 */ 3661 static final int FLAG_BOUND = 1 << 0; 3662 3663 /** 3664 * The data this ViewHolder's view reflects is stale and needs to be rebound 3665 * by the adapter. mPosition and mItemId are consistent. 3666 */ 3667 static final int FLAG_UPDATE = 1 << 1; 3668 3669 /** 3670 * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId 3671 * are not to be trusted and may no longer match the item view type. 3672 * This ViewHolder must be fully rebound to different data. 3673 */ 3674 static final int FLAG_INVALID = 1 << 2; 3675 3676 /** 3677 * This ViewHolder points at data that represents an item previously removed from the 3678 * data set. Its view may still be used for things like outgoing animations. 3679 */ 3680 static final int FLAG_REMOVED = 1 << 3; 3681 3682 /** 3683 * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() 3684 * and is intended to keep views around during animations. 3685 */ 3686 static final int FLAG_NOT_RECYCLABLE = 1 << 4; 3687 3688 private int mFlags; 3689 3690 // If non-null, view is currently considered scrap and may be reused for other data by the 3691 // scrap container. 3692 private Recycler mScrapContainer = null; 3693 3694 public ViewHolder(View itemView) { 3695 if (itemView == null) { 3696 throw new IllegalArgumentException("itemView may not be null"); 3697 } 3698 this.itemView = itemView; 3699 } 3700 3701 public final int getPosition() { 3702 return mPosition; 3703 } 3704 3705 public final long getItemId() { 3706 return mItemId; 3707 } 3708 3709 public final int getItemViewType() { 3710 return mItemViewType; 3711 } 3712 3713 boolean isScrap() { 3714 return mScrapContainer != null; 3715 } 3716 3717 void unScrap() { 3718 mScrapContainer.unscrapView(this); 3719 mScrapContainer = null; 3720 } 3721 3722 void setScrapContainer(Recycler recycler) { 3723 mScrapContainer = recycler; 3724 } 3725 3726 boolean isInvalid() { 3727 return (mFlags & FLAG_INVALID) != 0; 3728 } 3729 3730 boolean needsUpdate() { 3731 return (mFlags & FLAG_UPDATE) != 0; 3732 } 3733 3734 boolean isBound() { 3735 return (mFlags & FLAG_BOUND) != 0; 3736 } 3737 3738 boolean isRemoved() { 3739 return (mFlags & FLAG_REMOVED) != 0; 3740 } 3741 3742 void setFlags(int flags, int mask) { 3743 mFlags = (mFlags & ~mask) | (flags & mask); 3744 } 3745 3746 void addFlags(int flags) { 3747 mFlags |= flags; 3748 } 3749 3750 void clearFlagsForSharedPool() { 3751 mFlags = 0; 3752 } 3753 3754 @Override 3755 public String toString() { 3756 final StringBuilder sb = new StringBuilder("ViewHolder{" + 3757 Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId); 3758 if (isScrap()) sb.append(" scrap"); 3759 if (isInvalid()) sb.append(" invalid"); 3760 if (!isBound()) sb.append(" unbound"); 3761 if (needsUpdate()) sb.append(" update"); 3762 if (isRemoved()) sb.append(" removed"); 3763 sb.append("}"); 3764 return sb.toString(); 3765 } 3766 3767 public final void setIsRecyclable(boolean recyclable) { 3768 // TODO: might want this to be a refcount instead 3769 if (recyclable) { 3770 mFlags &= ~FLAG_NOT_RECYCLABLE; 3771 } else { 3772 mFlags |= FLAG_NOT_RECYCLABLE; 3773 } 3774 } 3775 3776 public final boolean isRecyclable() { 3777 return (mFlags & FLAG_NOT_RECYCLABLE) == 0 || 3778 !ViewCompat.hasTransientState(itemView); 3779 } 3780 } 3781 3782 /** 3783 * Queued operation to happen when child views are updated. 3784 */ 3785 private static class UpdateOp { 3786 public static final int ADD = 0; 3787 public static final int REMOVE = 1; 3788 public static final int UPDATE = 2; 3789 3790 static final int POOL_SIZE = 30; 3791 3792 public int cmd; 3793 public int positionStart; 3794 public int itemCount; 3795 3796 public UpdateOp(int cmd, int positionStart, int itemCount) { 3797 this.cmd = cmd; 3798 this.positionStart = positionStart; 3799 this.itemCount = itemCount; 3800 } 3801 } 3802 3803 UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) { 3804 UpdateOp op = mUpdateOpPool.acquire(); 3805 if (op == null) { 3806 op = new UpdateOp(cmd, positionStart, itemCount); 3807 } else { 3808 op.cmd = cmd; 3809 op.positionStart = positionStart; 3810 op.itemCount = itemCount; 3811 } 3812 return op; 3813 } 3814 3815 void recycleUpdateOp(UpdateOp op) { 3816 mUpdateOpPool.release(op); 3817 } 3818 3819 /** 3820 * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of 3821 * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged 3822 * to create their own subclass of this <code>LayoutParams</code> class 3823 * to store any additional required per-child view metadata about the layout. 3824 */ 3825 public static class LayoutParams extends MarginLayoutParams { 3826 ViewHolder mViewHolder; 3827 final Rect mDecorInsets = new Rect(); 3828 boolean mInsetsDirty = true; 3829 3830 public LayoutParams(Context c, AttributeSet attrs) { 3831 super(c, attrs); 3832 } 3833 3834 public LayoutParams(int width, int height) { 3835 super(width, height); 3836 } 3837 3838 public LayoutParams(MarginLayoutParams source) { 3839 super(source); 3840 } 3841 3842 public LayoutParams(ViewGroup.LayoutParams source) { 3843 super(source); 3844 } 3845 3846 public LayoutParams(LayoutParams source) { 3847 super((ViewGroup.LayoutParams) source); 3848 } 3849 3850 /** 3851 * Returns true if the view this LayoutParams is attached to needs to have its content 3852 * updated from the corresponding adapter. 3853 * 3854 * @return true if the view should have its content updated 3855 */ 3856 public boolean viewNeedsUpdate() { 3857 return mViewHolder.needsUpdate(); 3858 } 3859 3860 /** 3861 * Returns true if the view this LayoutParams is attached to is now representing 3862 * potentially invalid data. A LayoutManager should scrap/recycle it. 3863 * 3864 * @return true if the view is invalid 3865 */ 3866 public boolean isViewInvalid() { 3867 return mViewHolder.isInvalid(); 3868 } 3869 3870 /** 3871 * Returns true if the adapter data item corresponding to the view this LayoutParams 3872 * is attached to has been removed from the data set. A LayoutManager may choose to 3873 * treat it differently in order to animate its outgoing or disappearing state. 3874 * 3875 * @return true if the item the view corresponds to was removed from the data set 3876 */ 3877 public boolean isItemRemoved() { 3878 return mViewHolder.isRemoved(); 3879 } 3880 3881 /** 3882 * Returns the position that the view this LayoutParams is attached to corresponds to. 3883 * 3884 * @return the adapter position this view was bound from 3885 */ 3886 public int getViewPosition() { 3887 return mViewHolder.getPosition(); 3888 } 3889 } 3890 3891 /** 3892 * Observer base class for watching changes to an {@link Adapter}. 3893 * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. 3894 */ 3895 public static abstract class AdapterDataObserver { 3896 public void onChanged() { 3897 // Do nothing 3898 } 3899 3900 public void onItemRangeChanged(int positionStart, int itemCount) { 3901 // do nothing 3902 } 3903 3904 public void onItemRangeInserted(int positionStart, int itemCount) { 3905 // do nothing 3906 } 3907 3908 public void onItemRangeRemoved(int positionStart, int itemCount) { 3909 // do nothing 3910 } 3911 } 3912 3913 static class AdapterDataObservable extends Observable<AdapterDataObserver> { 3914 public boolean hasObservers() { 3915 return !mObservers.isEmpty(); 3916 } 3917 3918 public void notifyChanged() { 3919 // since onChanged() is implemented by the app, it could do anything, including 3920 // removing itself from {@link mObservers} - and that could cause problems if 3921 // an iterator is used on the ArrayList {@link mObservers}. 3922 // to avoid such problems, just march thru the list in the reverse order. 3923 for (int i = mObservers.size() - 1; i >= 0; i--) { 3924 mObservers.get(i).onChanged(); 3925 } 3926 } 3927 3928 public void notifyItemRangeChanged(int positionStart, int itemCount) { 3929 // since onItemRangeChanged() is implemented by the app, it could do anything, including 3930 // removing itself from {@link mObservers} - and that could cause problems if 3931 // an iterator is used on the ArrayList {@link mObservers}. 3932 // to avoid such problems, just march thru the list in the reverse order. 3933 for (int i = mObservers.size() - 1; i >= 0; i--) { 3934 mObservers.get(i).onItemRangeChanged(positionStart, itemCount); 3935 } 3936 } 3937 3938 public void notifyItemRangeInserted(int positionStart, int itemCount) { 3939 // since onItemRangeInserted() is implemented by the app, it could do anything, 3940 // including removing itself from {@link mObservers} - and that could cause problems if 3941 // an iterator is used on the ArrayList {@link mObservers}. 3942 // to avoid such problems, just march thru the list in the reverse order. 3943 for (int i = mObservers.size() - 1; i >= 0; i--) { 3944 mObservers.get(i).onItemRangeInserted(positionStart, itemCount); 3945 } 3946 } 3947 3948 public void notifyItemRangeRemoved(int positionStart, int itemCount) { 3949 // since onItemRangeRemoved() is implemented by the app, it could do anything, including 3950 // removing itself from {@link mObservers} - and that could cause problems if 3951 // an iterator is used on the ArrayList {@link mObservers}. 3952 // to avoid such problems, just march thru the list in the reverse order. 3953 for (int i = mObservers.size() - 1; i >= 0; i--) { 3954 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); 3955 } 3956 } 3957 } 3958} 3959