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