Aurimas Liutikas | 7149a63 | 2017-01-18 17:36:10 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2017 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 | package com.android.internal.widget; |
| 18 | |
| 19 | import android.annotation.CallSuper; |
| 20 | import android.annotation.IntDef; |
| 21 | import android.annotation.NonNull; |
| 22 | import android.annotation.Nullable; |
Andrei Onea | 1588439 | 2019-03-22 17:28:11 +0000 | [diff] [blame^] | 23 | import android.annotation.UnsupportedAppUsage; |
Aurimas Liutikas | 7149a63 | 2017-01-18 17:36:10 -0800 | [diff] [blame] | 24 | import android.content.Context; |
| 25 | import android.content.res.TypedArray; |
| 26 | import android.database.Observable; |
| 27 | import android.graphics.Canvas; |
| 28 | import android.graphics.Matrix; |
| 29 | import android.graphics.PointF; |
| 30 | import android.graphics.Rect; |
| 31 | import android.graphics.RectF; |
| 32 | import android.os.Build; |
| 33 | import android.os.Bundle; |
| 34 | import android.os.Parcel; |
| 35 | import android.os.Parcelable; |
| 36 | import android.os.SystemClock; |
| 37 | import android.os.Trace; |
| 38 | import android.util.AttributeSet; |
| 39 | import android.util.Log; |
| 40 | import android.util.SparseArray; |
| 41 | import android.util.TypedValue; |
| 42 | import android.view.AbsSavedState; |
| 43 | import android.view.Display; |
| 44 | import android.view.FocusFinder; |
| 45 | import android.view.InputDevice; |
| 46 | import android.view.MotionEvent; |
| 47 | import android.view.VelocityTracker; |
| 48 | import android.view.View; |
| 49 | import android.view.ViewConfiguration; |
| 50 | import android.view.ViewGroup; |
| 51 | import android.view.ViewParent; |
| 52 | import android.view.accessibility.AccessibilityEvent; |
| 53 | import android.view.accessibility.AccessibilityManager; |
| 54 | import android.view.accessibility.AccessibilityNodeInfo; |
| 55 | import android.view.animation.Interpolator; |
| 56 | import android.widget.EdgeEffect; |
| 57 | import android.widget.OverScroller; |
| 58 | |
| 59 | import com.android.internal.R; |
| 60 | import com.android.internal.annotations.VisibleForTesting; |
| 61 | import com.android.internal.widget.RecyclerView.ItemAnimator.ItemHolderInfo; |
| 62 | |
| 63 | import java.lang.annotation.Retention; |
| 64 | import java.lang.annotation.RetentionPolicy; |
| 65 | import java.lang.ref.WeakReference; |
| 66 | import java.lang.reflect.Constructor; |
| 67 | import java.lang.reflect.InvocationTargetException; |
| 68 | import java.util.ArrayList; |
| 69 | import java.util.Collections; |
| 70 | import java.util.List; |
| 71 | |
| 72 | /** |
| 73 | * A flexible view for providing a limited window into a large data set. |
| 74 | * |
| 75 | * <h3>Glossary of terms:</h3> |
| 76 | * |
| 77 | * <ul> |
| 78 | * <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views |
| 79 | * that represent items in a data set.</li> |
| 80 | * <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li> |
| 81 | * <li><em>Index:</em> The index of an attached child view as used in a call to |
| 82 | * {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li> |
| 83 | * <li><em>Binding:</em> The process of preparing a child view to display data corresponding |
| 84 | * to a <em>position</em> within the adapter.</li> |
| 85 | * <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter |
| 86 | * position may be placed in a cache for later reuse to display the same type of data again |
| 87 | * later. This can drastically improve performance by skipping initial layout inflation |
| 88 | * or construction.</li> |
| 89 | * <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached |
| 90 | * state during layout. Scrap views may be reused without becoming fully detached |
| 91 | * from the parent RecyclerView, either unmodified if no rebinding is required or modified |
| 92 | * by the adapter if the view was considered <em>dirty</em>.</li> |
| 93 | * <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before |
| 94 | * being displayed.</li> |
| 95 | * </ul> |
| 96 | * |
| 97 | * <h4>Positions in RecyclerView:</h4> |
| 98 | * <p> |
| 99 | * RecyclerView introduces an additional level of abstraction between the {@link Adapter} and |
| 100 | * {@link LayoutManager} to be able to detect data set changes in batches during a layout |
| 101 | * calculation. This saves LayoutManager from tracking adapter changes to calculate animations. |
| 102 | * It also helps with performance because all view bindings happen at the same time and unnecessary |
| 103 | * bindings are avoided. |
| 104 | * <p> |
| 105 | * For this reason, there are two types of <code>position</code> related methods in RecyclerView: |
| 106 | * <ul> |
| 107 | * <li>layout position: Position of an item in the latest layout calculation. This is the |
| 108 | * position from the LayoutManager's perspective.</li> |
| 109 | * <li>adapter position: Position of an item in the adapter. This is the position from |
| 110 | * the Adapter's perspective.</li> |
| 111 | * </ul> |
| 112 | * <p> |
| 113 | * These two positions are the same except the time between dispatching <code>adapter.notify* |
| 114 | * </code> events and calculating the updated layout. |
| 115 | * <p> |
| 116 | * Methods that return or receive <code>*LayoutPosition*</code> use position as of the latest |
| 117 | * layout calculation (e.g. {@link ViewHolder#getLayoutPosition()}, |
| 118 | * {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the |
| 119 | * last layout calculation. You can rely on these positions to be consistent with what user is |
| 120 | * currently seeing on the screen. For example, if you have a list of items on the screen and user |
| 121 | * asks for the 5<sup>th</sup> element, you should use these methods as they'll match what user |
| 122 | * is seeing. |
| 123 | * <p> |
| 124 | * The other set of position related methods are in the form of |
| 125 | * <code>*AdapterPosition*</code>. (e.g. {@link ViewHolder#getAdapterPosition()}, |
| 126 | * {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to |
| 127 | * work with up-to-date adapter positions even if they may not have been reflected to layout yet. |
| 128 | * For example, if you want to access the item in the adapter on a ViewHolder click, you should use |
| 129 | * {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate |
| 130 | * adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has |
| 131 | * not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or |
| 132 | * <code>null</code> results from these methods. |
| 133 | * <p> |
| 134 | * When writing a {@link LayoutManager} you almost always want to use layout positions whereas when |
| 135 | * writing an {@link Adapter}, you probably want to use adapter positions. |
Aurimas Liutikas | 7149a63 | 2017-01-18 17:36:10 -0800 | [diff] [blame] | 136 | */ |
| 137 | public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild { |
| 138 | |
| 139 | static final String TAG = "RecyclerView"; |
| 140 | |
| 141 | static final boolean DEBUG = false; |
| 142 | |
| 143 | private static final int[] NESTED_SCROLLING_ATTRS = { android.R.attr.nestedScrollingEnabled }; |
| 144 | |
| 145 | private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding}; |
| 146 | |
| 147 | /** |
| 148 | * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if |
| 149 | * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by |
| 150 | * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler |
| 151 | * recursively traverses itemView and invalidates display list for each ViewGroup that matches |
| 152 | * this criteria. |
| 153 | */ |
| 154 | static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18 |
| 155 | || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20; |
| 156 | /** |
| 157 | * On M+, an unspecified measure spec may include a hint which we can use. On older platforms, |
| 158 | * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to |
| 159 | * 0 when mode is unspecified. |
| 160 | */ |
| 161 | static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23; |
| 162 | |
| 163 | static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16; |
| 164 | |
| 165 | /** |
| 166 | * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to |
| 167 | * RenderThread but before the next frame begins. We schedule prefetch work in this window. |
| 168 | */ |
| 169 | private static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21; |
| 170 | |
| 171 | /** |
| 172 | * FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction. |
| 173 | * We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT. |
| 174 | */ |
| 175 | private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15; |
| 176 | |
| 177 | /** |
| 178 | * on API 15-, a focused child can still be considered a focused child of RV even after |
| 179 | * it's being removed or its focusable flag is set to false. This is because when this focused |
| 180 | * child is detached, the reference to this child is not removed in clearFocus. API 16 and above |
| 181 | * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus |
| 182 | * to request focus on a new child, which will clear the focus on the old (detached) child as a |
| 183 | * side-effect. |
| 184 | */ |
| 185 | private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15; |
| 186 | |
| 187 | static final boolean DISPATCH_TEMP_DETACH = false; |
| 188 | public static final int HORIZONTAL = 0; |
| 189 | public static final int VERTICAL = 1; |
| 190 | |
| 191 | public static final int NO_POSITION = -1; |
| 192 | public static final long NO_ID = -1; |
| 193 | public static final int INVALID_TYPE = -1; |
| 194 | |
| 195 | /** |
| 196 | * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates |
| 197 | * that the RecyclerView should use the standard touch slop for smooth, |
| 198 | * continuous scrolling. |
| 199 | */ |
| 200 | public static final int TOUCH_SLOP_DEFAULT = 0; |
| 201 | |
| 202 | /** |
| 203 | * Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates |
| 204 | * that the RecyclerView should use the standard touch slop for scrolling |
| 205 | * widgets that snap to a page or other coarse-grained barrier. |
| 206 | */ |
| 207 | public static final int TOUCH_SLOP_PAGING = 1; |
| 208 | |
| 209 | static final int MAX_SCROLL_DURATION = 2000; |
| 210 | |
| 211 | /** |
| 212 | * RecyclerView is calculating a scroll. |
| 213 | * If there are too many of these in Systrace, some Views inside RecyclerView might be causing |
| 214 | * it. Try to avoid using EditText, focusable views or handle them with care. |
| 215 | */ |
| 216 | static final String TRACE_SCROLL_TAG = "RV Scroll"; |
| 217 | |
| 218 | /** |
| 219 | * OnLayout has been called by the View system. |
| 220 | * If this shows up too many times in Systrace, make sure the children of RecyclerView do not |
| 221 | * update themselves directly. This will cause a full re-layout but when it happens via the |
| 222 | * Adapter notifyItemChanged, RecyclerView can avoid full layout calculation. |
| 223 | */ |
| 224 | private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout"; |
| 225 | |
| 226 | /** |
| 227 | * NotifyDataSetChanged or equal has been called. |
| 228 | * If this is taking a long time, try sending granular notify adapter changes instead of just |
| 229 | * calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter |
| 230 | * might help. |
| 231 | */ |
| 232 | private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate"; |
| 233 | |
| 234 | /** |
| 235 | * RecyclerView is doing a layout for partial adapter updates (we know what has changed) |
| 236 | * If this is taking a long time, you may have dispatched too many Adapter updates causing too |
| 237 | * many Views being rebind. Make sure all are necessary and also prefer using notify*Range |
| 238 | * methods. |
| 239 | */ |
| 240 | private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate"; |
| 241 | |
| 242 | /** |
| 243 | * RecyclerView is rebinding a View. |
| 244 | * If this is taking a lot of time, consider optimizing your layout or make sure you are not |
| 245 | * doing extra operations in onBindViewHolder call. |
| 246 | */ |
| 247 | static final String TRACE_BIND_VIEW_TAG = "RV OnBindView"; |
| 248 | |
| 249 | /** |
| 250 | * RecyclerView is attempting to pre-populate off screen views. |
| 251 | */ |
| 252 | static final String TRACE_PREFETCH_TAG = "RV Prefetch"; |
| 253 | |
| 254 | /** |
| 255 | * RecyclerView is attempting to pre-populate off screen itemviews within an off screen |
| 256 | * RecyclerView. |
| 257 | */ |
| 258 | static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch"; |
| 259 | |
| 260 | /** |
| 261 | * RecyclerView is creating a new View. |
| 262 | * If too many of these present in Systrace: |
| 263 | * - There might be a problem in Recycling (e.g. custom Animations that set transient state and |
| 264 | * prevent recycling or ItemAnimator not implementing the contract properly. ({@link |
| 265 | * > Adapter#onFailedToRecycleView(ViewHolder)}) |
| 266 | * |
| 267 | * - There might be too many item view types. |
| 268 | * > Try merging them |
| 269 | * |
| 270 | * - There might be too many itemChange animations and not enough space in RecyclerPool. |
| 271 | * >Try increasing your pool size and item cache size. |
| 272 | */ |
| 273 | static final String TRACE_CREATE_VIEW_TAG = "RV CreateView"; |
| 274 | private static final Class<?>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE = |
| 275 | new Class[]{Context.class, AttributeSet.class, int.class, int.class}; |
| 276 | |
| 277 | private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); |
| 278 | |
| 279 | final Recycler mRecycler = new Recycler(); |
| 280 | |
| 281 | private SavedState mPendingSavedState; |
| 282 | |
| 283 | /** |
| 284 | * Handles adapter updates |
| 285 | */ |
| 286 | AdapterHelper mAdapterHelper; |
| 287 | |
| 288 | /** |
| 289 | * Handles abstraction between LayoutManager children and RecyclerView children |
| 290 | */ |
| 291 | ChildHelper mChildHelper; |
| 292 | |
| 293 | /** |
| 294 | * Keeps data about views to be used for animations |
| 295 | */ |
| 296 | final ViewInfoStore mViewInfoStore = new ViewInfoStore(); |
| 297 | |
| 298 | /** |
| 299 | * Prior to L, there is no way to query this variable which is why we override the setter and |
| 300 | * track it here. |
| 301 | */ |
| 302 | boolean mClipToPadding; |
| 303 | |
| 304 | /** |
| 305 | * Note: this Runnable is only ever posted if: |
| 306 | * 1) We've been through first layout |
| 307 | * 2) We know we have a fixed size (mHasFixedSize) |
| 308 | * 3) We're attached |
| 309 | */ |
| 310 | final Runnable mUpdateChildViewsRunnable = new Runnable() { |
| 311 | @Override |
| 312 | public void run() { |
| 313 | if (!mFirstLayoutComplete || isLayoutRequested()) { |
| 314 | // a layout request will happen, we should not do layout here. |
| 315 | return; |
| 316 | } |
| 317 | if (!mIsAttached) { |
| 318 | requestLayout(); |
| 319 | // if we are not attached yet, mark us as requiring layout and skip |
| 320 | return; |
| 321 | } |
| 322 | if (mLayoutFrozen) { |
| 323 | mLayoutRequestEaten = true; |
| 324 | return; //we'll process updates when ice age ends. |
| 325 | } |
| 326 | consumePendingUpdateOperations(); |
| 327 | } |
| 328 | }; |
| 329 | |
| 330 | final Rect mTempRect = new Rect(); |
| 331 | private final Rect mTempRect2 = new Rect(); |
| 332 | final RectF mTempRectF = new RectF(); |
| 333 | Adapter mAdapter; |
| 334 | @VisibleForTesting LayoutManager mLayout; |
| 335 | RecyclerListener mRecyclerListener; |
| 336 | final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>(); |
| 337 | private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = |
| 338 | new ArrayList<>(); |
| 339 | private OnItemTouchListener mActiveOnItemTouchListener; |
| 340 | boolean mIsAttached; |
| 341 | boolean mHasFixedSize; |
| 342 | @VisibleForTesting boolean mFirstLayoutComplete; |
| 343 | |
| 344 | // Counting lock to control whether we should ignore requestLayout calls from children or not. |
| 345 | private int mEatRequestLayout = 0; |
| 346 | |
| 347 | boolean mLayoutRequestEaten; |
| 348 | boolean mLayoutFrozen; |
| 349 | private boolean mIgnoreMotionEventTillDown; |
| 350 | |
| 351 | // binary OR of change events that were eaten during a layout or scroll. |
| 352 | private int mEatenAccessibilityChangeFlags; |
| 353 | boolean mAdapterUpdateDuringMeasure; |
| 354 | |
| 355 | private final AccessibilityManager mAccessibilityManager; |
| 356 | private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners; |
| 357 | |
| 358 | /** |
| 359 | * Set to true when an adapter data set changed notification is received. |
| 360 | * In that case, we cannot run any animations since we don't know what happened until layout. |
| 361 | * |
| 362 | * Attached items are invalid until next layout, at which point layout will animate/replace |
| 363 | * items as necessary, building up content from the (effectively) new adapter from scratch. |
| 364 | * |
| 365 | * Cached items must be discarded when setting this to true, so that the cache may be freely |
| 366 | * used by prefetching until the next layout occurs. |
| 367 | * |
| 368 | * @see #setDataSetChangedAfterLayout() |
| 369 | */ |
| 370 | boolean mDataSetHasChangedAfterLayout = false; |
| 371 | |
| 372 | /** |
| 373 | * This variable is incremented during a dispatchLayout and/or scroll. |
| 374 | * Some methods should not be called during these periods (e.g. adapter data change). |
| 375 | * Doing so will create hard to find bugs so we better check it and throw an exception. |
| 376 | * |
| 377 | * @see #assertInLayoutOrScroll(String) |
| 378 | * @see #assertNotInLayoutOrScroll(String) |
| 379 | */ |
| 380 | private int mLayoutOrScrollCounter = 0; |
| 381 | |
| 382 | /** |
| 383 | * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception |
| 384 | * (for API compatibility). |
| 385 | * <p> |
| 386 | * It is a bad practice for a developer to update the data in a scroll callback since it is |
| 387 | * potentially called during a layout. |
| 388 | */ |
| 389 | private int mDispatchScrollCounter = 0; |
| 390 | |
| 391 | private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; |
| 392 | |
| 393 | ItemAnimator mItemAnimator = new DefaultItemAnimator(); |
| 394 | |
| 395 | private static final int INVALID_POINTER = -1; |
| 396 | |
| 397 | /** |
| 398 | * The RecyclerView is not currently scrolling. |
| 399 | * @see #getScrollState() |
| 400 | */ |
| 401 | public static final int SCROLL_STATE_IDLE = 0; |
| 402 | |
| 403 | /** |
| 404 | * The RecyclerView is currently being dragged by outside input such as user touch input. |
| 405 | * @see #getScrollState() |
| 406 | */ |
| 407 | public static final int SCROLL_STATE_DRAGGING = 1; |
| 408 | |
| 409 | /** |
| 410 | * The RecyclerView is currently animating to a final position while not under |
| 411 | * outside control. |
| 412 | * @see #getScrollState() |
| 413 | */ |
| 414 | public static final int SCROLL_STATE_SETTLING = 2; |
| 415 | |
| 416 | static final long FOREVER_NS = Long.MAX_VALUE; |
| 417 | |
| 418 | // Touch/scrolling handling |
| 419 | |
| 420 | private int mScrollState = SCROLL_STATE_IDLE; |
| 421 | private int mScrollPointerId = INVALID_POINTER; |
| 422 | private VelocityTracker mVelocityTracker; |
| 423 | private int mInitialTouchX; |
| 424 | private int mInitialTouchY; |
| 425 | private int mLastTouchX; |
| 426 | private int mLastTouchY; |
| 427 | private int mTouchSlop; |
| 428 | private OnFlingListener mOnFlingListener; |
| 429 | private final int mMinFlingVelocity; |
| 430 | private final int mMaxFlingVelocity; |
| 431 | // This value is used when handling generic motion events. |
| 432 | private float mScrollFactor = Float.MIN_VALUE; |
| 433 | private boolean mPreserveFocusAfterLayout = true; |
| 434 | |
| 435 | final ViewFlinger mViewFlinger = new ViewFlinger(); |
| 436 | |
| 437 | GapWorker mGapWorker; |
| 438 | GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry = |
| 439 | ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null; |
| 440 | |
| 441 | final State mState = new State(); |
| 442 | |
| 443 | private OnScrollListener mScrollListener; |
| 444 | private List<OnScrollListener> mScrollListeners; |
| 445 | |
| 446 | // For use in item animations |
| 447 | boolean mItemsAddedOrRemoved = false; |
| 448 | boolean mItemsChanged = false; |
| 449 | private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = |
| 450 | new ItemAnimatorRestoreListener(); |
| 451 | boolean mPostedAnimatorRunner = false; |
| 452 | RecyclerViewAccessibilityDelegate mAccessibilityDelegate; |
| 453 | private ChildDrawingOrderCallback mChildDrawingOrderCallback; |
| 454 | |
| 455 | // simple array to keep min and max child position during a layout calculation |
| 456 | // preserved not to create a new one in each layout pass |
| 457 | private final int[] mMinMaxLayoutPositions = new int[2]; |
| 458 | |
| 459 | private final int[] mScrollOffset = new int[2]; |
| 460 | private final int[] mScrollConsumed = new int[2]; |
| 461 | private final int[] mNestedOffsets = new int[2]; |
| 462 | |
| 463 | /** |
| 464 | * These are views that had their a11y importance changed during a layout. We defer these events |
| 465 | * until the end of the layout because a11y service may make sync calls back to the RV while |
| 466 | * the View's state is undefined. |
| 467 | */ |
| 468 | @VisibleForTesting |
| 469 | final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList(); |
| 470 | |
| 471 | private Runnable mItemAnimatorRunner = new Runnable() { |
| 472 | @Override |
| 473 | public void run() { |
| 474 | if (mItemAnimator != null) { |
| 475 | mItemAnimator.runPendingAnimations(); |
| 476 | } |
| 477 | mPostedAnimatorRunner = false; |
| 478 | } |
| 479 | }; |
| 480 | |
| 481 | static final Interpolator sQuinticInterpolator = new Interpolator() { |
| 482 | @Override |
| 483 | public float getInterpolation(float t) { |
| 484 | t -= 1.0f; |
| 485 | return t * t * t * t * t + 1.0f; |
| 486 | } |
| 487 | }; |
| 488 | |
| 489 | /** |
| 490 | * The callback to convert view info diffs into animations. |
| 491 | */ |
| 492 | private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = |
| 493 | new ViewInfoStore.ProcessCallback() { |
| 494 | @Override |
| 495 | public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info, |
| 496 | @Nullable ItemHolderInfo postInfo) { |
| 497 | mRecycler.unscrapView(viewHolder); |
| 498 | animateDisappearance(viewHolder, info, postInfo); |
| 499 | } |
| 500 | @Override |
| 501 | public void processAppeared(ViewHolder viewHolder, |
| 502 | ItemHolderInfo preInfo, ItemHolderInfo info) { |
| 503 | animateAppearance(viewHolder, preInfo, info); |
| 504 | } |
| 505 | |
| 506 | @Override |
| 507 | public void processPersistent(ViewHolder viewHolder, |
| 508 | @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { |
| 509 | viewHolder.setIsRecyclable(false); |
| 510 | if (mDataSetHasChangedAfterLayout) { |
| 511 | // since it was rebound, use change instead as we'll be mapping them from |
| 512 | // stable ids. If stable ids were false, we would not be running any |
| 513 | // animations |
| 514 | if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, postInfo)) { |
| 515 | postAnimationRunner(); |
| 516 | } |
| 517 | } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) { |
| 518 | postAnimationRunner(); |
| 519 | } |
| 520 | } |
| 521 | @Override |
| 522 | public void unused(ViewHolder viewHolder) { |
| 523 | mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); |
| 524 | } |
| 525 | }; |
| 526 | |
| 527 | public RecyclerView(Context context) { |
| 528 | this(context, null); |
| 529 | } |
| 530 | |
| 531 | public RecyclerView(Context context, @Nullable AttributeSet attrs) { |
| 532 | this(context, attrs, 0); |
| 533 | } |
| 534 | |
| 535 | public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { |
| 536 | super(context, attrs, defStyle); |
| 537 | if (attrs != null) { |
| 538 | TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0); |
| 539 | mClipToPadding = a.getBoolean(0, true); |
| 540 | a.recycle(); |
| 541 | } else { |
| 542 | mClipToPadding = true; |
| 543 | } |
| 544 | setScrollContainer(true); |
| 545 | setFocusableInTouchMode(true); |
| 546 | |
| 547 | final ViewConfiguration vc = ViewConfiguration.get(context); |
| 548 | mTouchSlop = vc.getScaledTouchSlop(); |
| 549 | mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); |
| 550 | mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); |
| 551 | setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); |
| 552 | |
| 553 | mItemAnimator.setListener(mItemAnimatorListener); |
| 554 | initAdapterManager(); |
| 555 | initChildrenHelper(); |
| 556 | // If not explicitly specified this view is important for accessibility. |
| 557 | if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { |
| 558 | setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); |
| 559 | } |
| 560 | mAccessibilityManager = (AccessibilityManager) getContext() |
| 561 | .getSystemService(Context.ACCESSIBILITY_SERVICE); |
| 562 | setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); |
| 563 | // Create the layoutManager if specified. |
| 564 | |
| 565 | boolean nestedScrollingEnabled = true; |
| 566 | |
| 567 | if (attrs != null) { |
| 568 | int defStyleRes = 0; |
| 569 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, |
| 570 | defStyle, defStyleRes); |
| 571 | String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager); |
| 572 | int descendantFocusability = a.getInt( |
| 573 | R.styleable.RecyclerView_descendantFocusability, -1); |
| 574 | if (descendantFocusability == -1) { |
| 575 | setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); |
| 576 | } |
| 577 | a.recycle(); |
| 578 | createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes); |
| 579 | |
| 580 | if (Build.VERSION.SDK_INT >= 21) { |
| 581 | a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS, |
| 582 | defStyle, defStyleRes); |
| 583 | nestedScrollingEnabled = a.getBoolean(0, true); |
| 584 | a.recycle(); |
| 585 | } |
| 586 | } else { |
| 587 | setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); |
| 588 | } |
| 589 | |
| 590 | // Re-set whether nested scrolling is enabled so that it is set on all API levels |
| 591 | setNestedScrollingEnabled(nestedScrollingEnabled); |
| 592 | } |
| 593 | |
| 594 | /** |
| 595 | * Returns the accessibility delegate compatibility implementation used by the RecyclerView. |
| 596 | * @return An instance of AccessibilityDelegateCompat used by RecyclerView |
| 597 | */ |
| 598 | public RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate() { |
| 599 | return mAccessibilityDelegate; |
| 600 | } |
| 601 | |
| 602 | /** |
| 603 | * Sets the accessibility delegate compatibility implementation used by RecyclerView. |
| 604 | * @param accessibilityDelegate The accessibility delegate to be used by RecyclerView. |
| 605 | */ |
| 606 | public void setAccessibilityDelegateCompat( |
| 607 | RecyclerViewAccessibilityDelegate accessibilityDelegate) { |
| 608 | mAccessibilityDelegate = accessibilityDelegate; |
| 609 | setAccessibilityDelegate(mAccessibilityDelegate); |
| 610 | } |
| 611 | |
| 612 | /** |
| 613 | * Instantiate and set a LayoutManager, if specified in the attributes. |
| 614 | */ |
| 615 | private void createLayoutManager(Context context, String className, AttributeSet attrs, |
| 616 | int defStyleAttr, int defStyleRes) { |
| 617 | if (className != null) { |
| 618 | className = className.trim(); |
| 619 | if (className.length() != 0) { // Can't use isEmpty since it was added in API 9. |
| 620 | className = getFullClassName(context, className); |
| 621 | try { |
| 622 | ClassLoader classLoader; |
| 623 | if (isInEditMode()) { |
| 624 | // Stupid layoutlib cannot handle simple class loaders. |
| 625 | classLoader = this.getClass().getClassLoader(); |
| 626 | } else { |
| 627 | classLoader = context.getClassLoader(); |
| 628 | } |
| 629 | Class<? extends LayoutManager> layoutManagerClass = |
| 630 | classLoader.loadClass(className).asSubclass(LayoutManager.class); |
| 631 | Constructor<? extends LayoutManager> constructor; |
| 632 | Object[] constructorArgs = null; |
| 633 | try { |
| 634 | constructor = layoutManagerClass |
| 635 | .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE); |
| 636 | constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes}; |
| 637 | } catch (NoSuchMethodException e) { |
| 638 | try { |
| 639 | constructor = layoutManagerClass.getConstructor(); |
| 640 | } catch (NoSuchMethodException e1) { |
| 641 | e1.initCause(e); |
| 642 | throw new IllegalStateException(attrs.getPositionDescription() |
| 643 | + ": Error creating LayoutManager " + className, e1); |
| 644 | } |
| 645 | } |
| 646 | constructor.setAccessible(true); |
| 647 | setLayoutManager(constructor.newInstance(constructorArgs)); |
| 648 | } catch (ClassNotFoundException e) { |
| 649 | throw new IllegalStateException(attrs.getPositionDescription() |
| 650 | + ": Unable to find LayoutManager " + className, e); |
| 651 | } catch (InvocationTargetException e) { |
| 652 | throw new IllegalStateException(attrs.getPositionDescription() |
| 653 | + ": Could not instantiate the LayoutManager: " + className, e); |
| 654 | } catch (InstantiationException e) { |
| 655 | throw new IllegalStateException(attrs.getPositionDescription() |
| 656 | + ": Could not instantiate the LayoutManager: " + className, e); |
| 657 | } catch (IllegalAccessException e) { |
| 658 | throw new IllegalStateException(attrs.getPositionDescription() |
| 659 | + ": Cannot access non-public constructor " + className, e); |
| 660 | } catch (ClassCastException e) { |
| 661 | throw new IllegalStateException(attrs.getPositionDescription() |
| 662 | + ": Class is not a LayoutManager " + className, e); |
| 663 | } |
| 664 | } |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | private String getFullClassName(Context context, String className) { |
| 669 | if (className.charAt(0) == '.') { |
| 670 | return context.getPackageName() + className; |
| 671 | } |
| 672 | if (className.contains(".")) { |
| 673 | return className; |
| 674 | } |
| 675 | return RecyclerView.class.getPackage().getName() + '.' + className; |
| 676 | } |
| 677 | |
| 678 | private void initChildrenHelper() { |
| 679 | mChildHelper = new ChildHelper(new ChildHelper.Callback() { |
| 680 | @Override |
| 681 | public int getChildCount() { |
| 682 | return RecyclerView.this.getChildCount(); |
| 683 | } |
| 684 | |
| 685 | @Override |
| 686 | public void addView(View child, int index) { |
| 687 | RecyclerView.this.addView(child, index); |
| 688 | dispatchChildAttached(child); |
| 689 | } |
| 690 | |
| 691 | @Override |
| 692 | public int indexOfChild(View view) { |
| 693 | return RecyclerView.this.indexOfChild(view); |
| 694 | } |
| 695 | |
| 696 | @Override |
| 697 | public void removeViewAt(int index) { |
| 698 | final View child = RecyclerView.this.getChildAt(index); |
| 699 | if (child != null) { |
| 700 | dispatchChildDetached(child); |
| 701 | } |
| 702 | RecyclerView.this.removeViewAt(index); |
| 703 | } |
| 704 | |
| 705 | @Override |
| 706 | public View getChildAt(int offset) { |
| 707 | return RecyclerView.this.getChildAt(offset); |
| 708 | } |
| 709 | |
| 710 | @Override |
| 711 | public void removeAllViews() { |
| 712 | final int count = getChildCount(); |
| 713 | for (int i = 0; i < count; i++) { |
| 714 | dispatchChildDetached(getChildAt(i)); |
| 715 | } |
| 716 | RecyclerView.this.removeAllViews(); |
| 717 | } |
| 718 | |
| 719 | @Override |
| 720 | public ViewHolder getChildViewHolder(View view) { |
| 721 | return getChildViewHolderInt(view); |
| 722 | } |
| 723 | |
| 724 | @Override |
| 725 | public void attachViewToParent(View child, int index, |
| 726 | ViewGroup.LayoutParams layoutParams) { |
| 727 | final ViewHolder vh = getChildViewHolderInt(child); |
| 728 | if (vh != null) { |
| 729 | if (!vh.isTmpDetached() && !vh.shouldIgnore()) { |
| 730 | throw new IllegalArgumentException("Called attach on a child which is not" |
| 731 | + " detached: " + vh); |
| 732 | } |
| 733 | if (DEBUG) { |
| 734 | Log.d(TAG, "reAttach " + vh); |
| 735 | } |
| 736 | vh.clearTmpDetachFlag(); |
| 737 | } |
| 738 | RecyclerView.this.attachViewToParent(child, index, layoutParams); |
| 739 | } |
| 740 | |
| 741 | @Override |
| 742 | public void detachViewFromParent(int offset) { |
| 743 | final View view = getChildAt(offset); |
| 744 | if (view != null) { |
| 745 | final ViewHolder vh = getChildViewHolderInt(view); |
| 746 | if (vh != null) { |
| 747 | if (vh.isTmpDetached() && !vh.shouldIgnore()) { |
| 748 | throw new IllegalArgumentException("called detach on an already" |
| 749 | + " detached child " + vh); |
| 750 | } |
| 751 | if (DEBUG) { |
| 752 | Log.d(TAG, "tmpDetach " + vh); |
| 753 | } |
| 754 | vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); |
| 755 | } |
| 756 | } |
| 757 | RecyclerView.this.detachViewFromParent(offset); |
| 758 | } |
| 759 | |
| 760 | @Override |
| 761 | public void onEnteredHiddenState(View child) { |
| 762 | final ViewHolder vh = getChildViewHolderInt(child); |
| 763 | if (vh != null) { |
| 764 | vh.onEnteredHiddenState(RecyclerView.this); |
| 765 | } |
| 766 | } |
| 767 | |
| 768 | @Override |
| 769 | public void onLeftHiddenState(View child) { |
| 770 | final ViewHolder vh = getChildViewHolderInt(child); |
| 771 | if (vh != null) { |
| 772 | vh.onLeftHiddenState(RecyclerView.this); |
| 773 | } |
| 774 | } |
| 775 | }); |
| 776 | } |
| 777 | |
| 778 | void initAdapterManager() { |
| 779 | mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { |
| 780 | @Override |
| 781 | public ViewHolder findViewHolder(int position) { |
| 782 | final ViewHolder vh = findViewHolderForPosition(position, true); |
| 783 | if (vh == null) { |
| 784 | return null; |
| 785 | } |
| 786 | // ensure it is not hidden because for adapter helper, the only thing matter is that |
| 787 | // LM thinks view is a child. |
| 788 | if (mChildHelper.isHidden(vh.itemView)) { |
| 789 | if (DEBUG) { |
| 790 | Log.d(TAG, "assuming view holder cannot be find because it is hidden"); |
| 791 | } |
| 792 | return null; |
| 793 | } |
| 794 | return vh; |
| 795 | } |
| 796 | |
| 797 | @Override |
| 798 | public void offsetPositionsForRemovingInvisible(int start, int count) { |
| 799 | offsetPositionRecordsForRemove(start, count, true); |
| 800 | mItemsAddedOrRemoved = true; |
| 801 | mState.mDeletedInvisibleItemCountSincePreviousLayout += count; |
| 802 | } |
| 803 | |
| 804 | @Override |
| 805 | public void offsetPositionsForRemovingLaidOutOrNewView( |
| 806 | int positionStart, int itemCount) { |
| 807 | offsetPositionRecordsForRemove(positionStart, itemCount, false); |
| 808 | mItemsAddedOrRemoved = true; |
| 809 | } |
| 810 | |
| 811 | @Override |
| 812 | public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) { |
| 813 | viewRangeUpdate(positionStart, itemCount, payload); |
| 814 | mItemsChanged = true; |
| 815 | } |
| 816 | |
| 817 | @Override |
| 818 | public void onDispatchFirstPass(AdapterHelper.UpdateOp op) { |
| 819 | dispatchUpdate(op); |
| 820 | } |
| 821 | |
| 822 | void dispatchUpdate(AdapterHelper.UpdateOp op) { |
| 823 | switch (op.cmd) { |
| 824 | case AdapterHelper.UpdateOp.ADD: |
| 825 | mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); |
| 826 | break; |
| 827 | case AdapterHelper.UpdateOp.REMOVE: |
| 828 | mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); |
| 829 | break; |
| 830 | case AdapterHelper.UpdateOp.UPDATE: |
| 831 | mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount, |
| 832 | op.payload); |
| 833 | break; |
| 834 | case AdapterHelper.UpdateOp.MOVE: |
| 835 | mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); |
| 836 | break; |
| 837 | } |
| 838 | } |
| 839 | |
| 840 | @Override |
| 841 | public void onDispatchSecondPass(AdapterHelper.UpdateOp op) { |
| 842 | dispatchUpdate(op); |
| 843 | } |
| 844 | |
| 845 | @Override |
| 846 | public void offsetPositionsForAdd(int positionStart, int itemCount) { |
| 847 | offsetPositionRecordsForInsert(positionStart, itemCount); |
| 848 | mItemsAddedOrRemoved = true; |
| 849 | } |
| 850 | |
| 851 | @Override |
| 852 | public void offsetPositionsForMove(int from, int to) { |
| 853 | offsetPositionRecordsForMove(from, to); |
| 854 | // should we create mItemsMoved ? |
| 855 | mItemsAddedOrRemoved = true; |
| 856 | } |
| 857 | }); |
| 858 | } |
| 859 | |
| 860 | /** |
| 861 | * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's |
| 862 | * size is not affected by the adapter contents. RecyclerView can still change its size based |
| 863 | * on other factors (e.g. its parent's size) but this size calculation cannot depend on the |
| 864 | * size of its children or contents of its adapter (except the number of items in the adapter). |
| 865 | * <p> |
| 866 | * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow |
| 867 | * RecyclerView to avoid invalidating the whole layout when its adapter contents change. |
| 868 | * |
| 869 | * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView. |
| 870 | */ |
| 871 | public void setHasFixedSize(boolean hasFixedSize) { |
| 872 | mHasFixedSize = hasFixedSize; |
| 873 | } |
| 874 | |
| 875 | /** |
| 876 | * @return true if the app has specified that changes in adapter content cannot change |
| 877 | * the size of the RecyclerView itself. |
| 878 | */ |
| 879 | public boolean hasFixedSize() { |
| 880 | return mHasFixedSize; |
| 881 | } |
| 882 | |
| 883 | @Override |
| 884 | public void setClipToPadding(boolean clipToPadding) { |
| 885 | if (clipToPadding != mClipToPadding) { |
| 886 | invalidateGlows(); |
| 887 | } |
| 888 | mClipToPadding = clipToPadding; |
| 889 | super.setClipToPadding(clipToPadding); |
| 890 | if (mFirstLayoutComplete) { |
| 891 | requestLayout(); |
| 892 | } |
| 893 | } |
| 894 | |
| 895 | /** |
| 896 | * Returns whether this RecyclerView will clip its children to its padding, and resize (but |
| 897 | * not clip) any EdgeEffect to the padded region, if padding is present. |
| 898 | * <p> |
| 899 | * By default, children are clipped to the padding of their parent |
| 900 | * RecyclerView. This clipping behavior is only enabled if padding is non-zero. |
| 901 | * |
| 902 | * @return true if this RecyclerView clips children to its padding and resizes (but doesn't |
| 903 | * clip) any EdgeEffect to the padded region, false otherwise. |
| 904 | * |
| 905 | * @attr name android:clipToPadding |
| 906 | */ |
| 907 | @Override |
| 908 | public boolean getClipToPadding() { |
| 909 | return mClipToPadding; |
| 910 | } |
| 911 | |
| 912 | /** |
| 913 | * Configure the scrolling touch slop for a specific use case. |
| 914 | * |
| 915 | * Set up the RecyclerView's scrolling motion threshold based on common usages. |
| 916 | * Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}. |
| 917 | * |
| 918 | * @param slopConstant One of the <code>TOUCH_SLOP_</code> constants representing |
| 919 | * the intended usage of this RecyclerView |
| 920 | */ |
| 921 | public void setScrollingTouchSlop(int slopConstant) { |
| 922 | final ViewConfiguration vc = ViewConfiguration.get(getContext()); |
| 923 | switch (slopConstant) { |
| 924 | default: |
| 925 | Log.w(TAG, "setScrollingTouchSlop(): bad argument constant " |
| 926 | + slopConstant + "; using default value"); |
| 927 | // fall-through |
| 928 | case TOUCH_SLOP_DEFAULT: |
| 929 | mTouchSlop = vc.getScaledTouchSlop(); |
| 930 | break; |
| 931 | |
| 932 | case TOUCH_SLOP_PAGING: |
| 933 | mTouchSlop = vc.getScaledPagingTouchSlop(); |
| 934 | break; |
| 935 | } |
| 936 | } |
| 937 | |
| 938 | /** |
| 939 | * Swaps the current adapter with the provided one. It is similar to |
| 940 | * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same |
| 941 | * {@link ViewHolder} and does not clear the RecycledViewPool. |
| 942 | * <p> |
| 943 | * Note that it still calls onAdapterChanged callbacks. |
| 944 | * |
| 945 | * @param adapter The new adapter to set, or null to set no adapter. |
| 946 | * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing |
| 947 | * Views. If adapters have stable ids and/or you want to |
| 948 | * animate the disappearing views, you may prefer to set |
| 949 | * this to false. |
| 950 | * @see #setAdapter(Adapter) |
| 951 | */ |
| 952 | public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) { |
| 953 | // bail out if layout is frozen |
| 954 | setLayoutFrozen(false); |
| 955 | setAdapterInternal(adapter, true, removeAndRecycleExistingViews); |
| 956 | setDataSetChangedAfterLayout(); |
| 957 | requestLayout(); |
| 958 | } |
| 959 | /** |
| 960 | * Set a new adapter to provide child views on demand. |
| 961 | * <p> |
| 962 | * When adapter is changed, all existing views are recycled back to the pool. If the pool has |
| 963 | * only one adapter, it will be cleared. |
| 964 | * |
| 965 | * @param adapter The new adapter to set, or null to set no adapter. |
| 966 | * @see #swapAdapter(Adapter, boolean) |
| 967 | */ |
| 968 | public void setAdapter(Adapter adapter) { |
| 969 | // bail out if layout is frozen |
| 970 | setLayoutFrozen(false); |
| 971 | setAdapterInternal(adapter, false, true); |
| 972 | requestLayout(); |
| 973 | } |
| 974 | |
| 975 | /** |
| 976 | * Removes and recycles all views - both those currently attached, and those in the Recycler. |
| 977 | */ |
| 978 | void removeAndRecycleViews() { |
| 979 | // end all running animations |
| 980 | if (mItemAnimator != null) { |
| 981 | mItemAnimator.endAnimations(); |
| 982 | } |
| 983 | // Since animations are ended, mLayout.children should be equal to |
| 984 | // recyclerView.children. This may not be true if item animator's end does not work as |
| 985 | // expected. (e.g. not release children instantly). It is safer to use mLayout's child |
| 986 | // count. |
| 987 | if (mLayout != null) { |
| 988 | mLayout.removeAndRecycleAllViews(mRecycler); |
| 989 | mLayout.removeAndRecycleScrapInt(mRecycler); |
| 990 | } |
| 991 | // we should clear it here before adapters are swapped to ensure correct callbacks. |
| 992 | mRecycler.clear(); |
| 993 | } |
| 994 | |
| 995 | /** |
| 996 | * Replaces the current adapter with the new one and triggers listeners. |
| 997 | * @param adapter The new adapter |
| 998 | * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and |
| 999 | * item types with the current adapter (helps us avoid cache |
| 1000 | * invalidation). |
| 1001 | * @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If |
| 1002 | * compatibleWithPrevious is false, this parameter is ignored. |
| 1003 | */ |
| 1004 | private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, |
| 1005 | boolean removeAndRecycleViews) { |
| 1006 | if (mAdapter != null) { |
| 1007 | mAdapter.unregisterAdapterDataObserver(mObserver); |
| 1008 | mAdapter.onDetachedFromRecyclerView(this); |
| 1009 | } |
| 1010 | if (!compatibleWithPrevious || removeAndRecycleViews) { |
| 1011 | removeAndRecycleViews(); |
| 1012 | } |
| 1013 | mAdapterHelper.reset(); |
| 1014 | final Adapter oldAdapter = mAdapter; |
| 1015 | mAdapter = adapter; |
| 1016 | if (adapter != null) { |
| 1017 | adapter.registerAdapterDataObserver(mObserver); |
| 1018 | adapter.onAttachedToRecyclerView(this); |
| 1019 | } |
| 1020 | if (mLayout != null) { |
| 1021 | mLayout.onAdapterChanged(oldAdapter, mAdapter); |
| 1022 | } |
| 1023 | mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); |
| 1024 | mState.mStructureChanged = true; |
| 1025 | markKnownViewsInvalid(); |
| 1026 | } |
| 1027 | |
| 1028 | /** |
| 1029 | * Retrieves the previously set adapter or null if no adapter is set. |
| 1030 | * |
| 1031 | * @return The previously set adapter |
| 1032 | * @see #setAdapter(Adapter) |
| 1033 | */ |
| 1034 | public Adapter getAdapter() { |
| 1035 | return mAdapter; |
| 1036 | } |
| 1037 | |
| 1038 | /** |
| 1039 | * Register a listener that will be notified whenever a child view is recycled. |
| 1040 | * |
| 1041 | * <p>This listener will be called when a LayoutManager or the RecyclerView decides |
| 1042 | * that a child view is no longer needed. If an application associates expensive |
| 1043 | * or heavyweight data with item views, this may be a good place to release |
| 1044 | * or free those resources.</p> |
| 1045 | * |
| 1046 | * @param listener Listener to register, or null to clear |
| 1047 | */ |
| 1048 | public void setRecyclerListener(RecyclerListener listener) { |
| 1049 | mRecyclerListener = listener; |
| 1050 | } |
| 1051 | |
| 1052 | /** |
| 1053 | * <p>Return the offset of the RecyclerView's text baseline from the its top |
| 1054 | * boundary. If the LayoutManager of this RecyclerView does not support baseline alignment, |
| 1055 | * this method returns -1.</p> |
| 1056 | * |
| 1057 | * @return the offset of the baseline within the RecyclerView's bounds or -1 |
| 1058 | * if baseline alignment is not supported |
| 1059 | */ |
| 1060 | @Override |
| 1061 | public int getBaseline() { |
| 1062 | if (mLayout != null) { |
| 1063 | return mLayout.getBaseline(); |
| 1064 | } else { |
| 1065 | return super.getBaseline(); |
| 1066 | } |
| 1067 | } |
| 1068 | |
| 1069 | /** |
| 1070 | * Register a listener that will be notified whenever a child view is attached to or detached |
| 1071 | * from RecyclerView. |
| 1072 | * |
| 1073 | * <p>This listener will be called when a LayoutManager or the RecyclerView decides |
| 1074 | * that a child view is no longer needed. If an application associates expensive |
| 1075 | * or heavyweight data with item views, this may be a good place to release |
| 1076 | * or free those resources.</p> |
| 1077 | * |
| 1078 | * @param listener Listener to register |
| 1079 | */ |
| 1080 | public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) { |
| 1081 | if (mOnChildAttachStateListeners == null) { |
| 1082 | mOnChildAttachStateListeners = new ArrayList<>(); |
| 1083 | } |
| 1084 | mOnChildAttachStateListeners.add(listener); |
| 1085 | } |
| 1086 | |
| 1087 | /** |
| 1088 | * Removes the provided listener from child attached state listeners list. |
| 1089 | * |
| 1090 | * @param listener Listener to unregister |
| 1091 | */ |
| 1092 | public void removeOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) { |
| 1093 | if (mOnChildAttachStateListeners == null) { |
| 1094 | return; |
| 1095 | } |
| 1096 | mOnChildAttachStateListeners.remove(listener); |
| 1097 | } |
| 1098 | |
| 1099 | /** |
| 1100 | * Removes all listeners that were added via |
| 1101 | * {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}. |
| 1102 | */ |
| 1103 | public void clearOnChildAttachStateChangeListeners() { |
| 1104 | if (mOnChildAttachStateListeners != null) { |
| 1105 | mOnChildAttachStateListeners.clear(); |
| 1106 | } |
| 1107 | } |
| 1108 | |
| 1109 | /** |
| 1110 | * Set the {@link LayoutManager} that this RecyclerView will use. |
| 1111 | * |
| 1112 | * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView} |
| 1113 | * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom |
| 1114 | * layout arrangements for child views. These arrangements are controlled by the |
| 1115 | * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p> |
| 1116 | * |
| 1117 | * <p>Several default strategies are provided for common uses such as lists and grids.</p> |
| 1118 | * |
| 1119 | * @param layout LayoutManager to use |
| 1120 | */ |
| 1121 | public void setLayoutManager(LayoutManager layout) { |
| 1122 | if (layout == mLayout) { |
| 1123 | return; |
| 1124 | } |
| 1125 | stopScroll(); |
| 1126 | // TODO We should do this switch a dispatchLayout pass and animate children. There is a good |
| 1127 | // chance that LayoutManagers will re-use views. |
| 1128 | if (mLayout != null) { |
| 1129 | // end all running animations |
| 1130 | if (mItemAnimator != null) { |
| 1131 | mItemAnimator.endAnimations(); |
| 1132 | } |
| 1133 | mLayout.removeAndRecycleAllViews(mRecycler); |
| 1134 | mLayout.removeAndRecycleScrapInt(mRecycler); |
| 1135 | mRecycler.clear(); |
| 1136 | |
| 1137 | if (mIsAttached) { |
| 1138 | mLayout.dispatchDetachedFromWindow(this, mRecycler); |
| 1139 | } |
| 1140 | mLayout.setRecyclerView(null); |
| 1141 | mLayout = null; |
| 1142 | } else { |
| 1143 | mRecycler.clear(); |
| 1144 | } |
| 1145 | // this is just a defensive measure for faulty item animators. |
| 1146 | mChildHelper.removeAllViewsUnfiltered(); |
| 1147 | mLayout = layout; |
| 1148 | if (layout != null) { |
| 1149 | if (layout.mRecyclerView != null) { |
| 1150 | throw new IllegalArgumentException("LayoutManager " + layout |
| 1151 | + " is already attached to a RecyclerView: " + layout.mRecyclerView); |
| 1152 | } |
| 1153 | mLayout.setRecyclerView(this); |
| 1154 | if (mIsAttached) { |
| 1155 | mLayout.dispatchAttachedToWindow(this); |
| 1156 | } |
| 1157 | } |
| 1158 | mRecycler.updateViewCacheSize(); |
| 1159 | requestLayout(); |
| 1160 | } |
| 1161 | |
| 1162 | /** |
| 1163 | * Set a {@link OnFlingListener} for this {@link RecyclerView}. |
| 1164 | * <p> |
| 1165 | * If the {@link OnFlingListener} is set then it will receive |
| 1166 | * calls to {@link #fling(int,int)} and will be able to intercept them. |
| 1167 | * |
| 1168 | * @param onFlingListener The {@link OnFlingListener} instance. |
| 1169 | */ |
| 1170 | public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) { |
| 1171 | mOnFlingListener = onFlingListener; |
| 1172 | } |
| 1173 | |
| 1174 | /** |
| 1175 | * Get the current {@link OnFlingListener} from this {@link RecyclerView}. |
| 1176 | * |
| 1177 | * @return The {@link OnFlingListener} instance currently set (can be null). |
| 1178 | */ |
| 1179 | @Nullable |
| 1180 | public OnFlingListener getOnFlingListener() { |
| 1181 | return mOnFlingListener; |
| 1182 | } |
| 1183 | |
| 1184 | @Override |
| 1185 | protected Parcelable onSaveInstanceState() { |
| 1186 | SavedState state = new SavedState(super.onSaveInstanceState()); |
| 1187 | if (mPendingSavedState != null) { |
| 1188 | state.copyFrom(mPendingSavedState); |
| 1189 | } else if (mLayout != null) { |
| 1190 | state.mLayoutState = mLayout.onSaveInstanceState(); |
| 1191 | } else { |
| 1192 | state.mLayoutState = null; |
| 1193 | } |
| 1194 | |
| 1195 | return state; |
| 1196 | } |
| 1197 | |
| 1198 | @Override |
| 1199 | protected void onRestoreInstanceState(Parcelable state) { |
| 1200 | if (!(state instanceof SavedState)) { |
| 1201 | super.onRestoreInstanceState(state); |
| 1202 | return; |
| 1203 | } |
| 1204 | |
| 1205 | mPendingSavedState = (SavedState) state; |
| 1206 | super.onRestoreInstanceState(mPendingSavedState.getSuperState()); |
| 1207 | if (mLayout != null && mPendingSavedState.mLayoutState != null) { |
| 1208 | mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState); |
| 1209 | } |
| 1210 | } |
| 1211 | |
| 1212 | /** |
| 1213 | * Override to prevent freezing of any views created by the adapter. |
| 1214 | */ |
| 1215 | @Override |
| 1216 | protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { |
| 1217 | dispatchFreezeSelfOnly(container); |
| 1218 | } |
| 1219 | |
| 1220 | /** |
| 1221 | * Override to prevent thawing of any views created by the adapter. |
| 1222 | */ |
| 1223 | @Override |
| 1224 | protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { |
| 1225 | dispatchThawSelfOnly(container); |
| 1226 | } |
| 1227 | |
| 1228 | /** |
| 1229 | * Adds a view to the animatingViews list. |
| 1230 | * mAnimatingViews holds the child views that are currently being kept around |
| 1231 | * purely for the purpose of being animated out of view. They are drawn as a regular |
| 1232 | * part of the child list of the RecyclerView, but they are invisible to the LayoutManager |
| 1233 | * as they are managed separately from the regular child views. |
| 1234 | * @param viewHolder The ViewHolder to be removed |
| 1235 | */ |
| 1236 | private void addAnimatingView(ViewHolder viewHolder) { |
| 1237 | final View view = viewHolder.itemView; |
| 1238 | final boolean alreadyParented = view.getParent() == this; |
| 1239 | mRecycler.unscrapView(getChildViewHolder(view)); |
| 1240 | if (viewHolder.isTmpDetached()) { |
| 1241 | // re-attach |
| 1242 | mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true); |
| 1243 | } else if (!alreadyParented) { |
| 1244 | mChildHelper.addView(view, true); |
| 1245 | } else { |
| 1246 | mChildHelper.hide(view); |
| 1247 | } |
| 1248 | } |
| 1249 | |
| 1250 | /** |
| 1251 | * Removes a view from the animatingViews list. |
| 1252 | * @param view The view to be removed |
| 1253 | * @see #addAnimatingView(RecyclerView.ViewHolder) |
| 1254 | * @return true if an animating view is removed |
| 1255 | */ |
| 1256 | boolean removeAnimatingView(View view) { |
| 1257 | eatRequestLayout(); |
| 1258 | final boolean removed = mChildHelper.removeViewIfHidden(view); |
| 1259 | if (removed) { |
| 1260 | final ViewHolder viewHolder = getChildViewHolderInt(view); |
| 1261 | mRecycler.unscrapView(viewHolder); |
| 1262 | mRecycler.recycleViewHolderInternal(viewHolder); |
| 1263 | if (DEBUG) { |
| 1264 | Log.d(TAG, "after removing animated view: " + view + ", " + this); |
| 1265 | } |
| 1266 | } |
| 1267 | // only clear request eaten flag if we removed the view. |
| 1268 | resumeRequestLayout(!removed); |
| 1269 | return removed; |
| 1270 | } |
| 1271 | |
| 1272 | /** |
| 1273 | * Return the {@link LayoutManager} currently responsible for |
| 1274 | * layout policy for this RecyclerView. |
| 1275 | * |
| 1276 | * @return The currently bound LayoutManager |
| 1277 | */ |
| 1278 | public LayoutManager getLayoutManager() { |
| 1279 | return mLayout; |
| 1280 | } |
| 1281 | |
| 1282 | /** |
| 1283 | * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null; |
| 1284 | * if no pool is set for this view a new one will be created. See |
| 1285 | * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information. |
| 1286 | * |
| 1287 | * @return The pool used to store recycled item views for reuse. |
| 1288 | * @see #setRecycledViewPool(RecycledViewPool) |
| 1289 | */ |
| 1290 | public RecycledViewPool getRecycledViewPool() { |
| 1291 | return mRecycler.getRecycledViewPool(); |
| 1292 | } |
| 1293 | |
| 1294 | /** |
| 1295 | * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. |
| 1296 | * This can be useful if you have multiple RecyclerViews with adapters that use the same |
| 1297 | * view types, for example if you have several data sets with the same kinds of item views |
| 1298 | * displayed by a {@link android.support.v4.view.ViewPager ViewPager}. |
| 1299 | * |
| 1300 | * @param pool Pool to set. If this parameter is null a new pool will be created and used. |
| 1301 | */ |
| 1302 | public void setRecycledViewPool(RecycledViewPool pool) { |
| 1303 | mRecycler.setRecycledViewPool(pool); |
| 1304 | } |
| 1305 | |
| 1306 | /** |
| 1307 | * Sets a new {@link ViewCacheExtension} to be used by the Recycler. |
| 1308 | * |
| 1309 | * @param extension ViewCacheExtension to be used or null if you want to clear the existing one. |
| 1310 | * |
| 1311 | * @see {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)} |
| 1312 | */ |
| 1313 | public void setViewCacheExtension(ViewCacheExtension extension) { |
| 1314 | mRecycler.setViewCacheExtension(extension); |
| 1315 | } |
| 1316 | |
| 1317 | /** |
| 1318 | * Set the number of offscreen views to retain before adding them to the potentially shared |
| 1319 | * {@link #getRecycledViewPool() recycled view pool}. |
| 1320 | * |
| 1321 | * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing |
| 1322 | * a LayoutManager to reuse those views unmodified without needing to return to the adapter |
| 1323 | * to rebind them.</p> |
| 1324 | * |
| 1325 | * @param size Number of views to cache offscreen before returning them to the general |
| 1326 | * recycled view pool |
| 1327 | */ |
| 1328 | public void setItemViewCacheSize(int size) { |
| 1329 | mRecycler.setViewCacheSize(size); |
| 1330 | } |
| 1331 | |
| 1332 | /** |
| 1333 | * Return the current scrolling state of the RecyclerView. |
| 1334 | * |
| 1335 | * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or |
| 1336 | * {@link #SCROLL_STATE_SETTLING} |
| 1337 | */ |
| 1338 | public int getScrollState() { |
| 1339 | return mScrollState; |
| 1340 | } |
| 1341 | |
| 1342 | void setScrollState(int state) { |
| 1343 | if (state == mScrollState) { |
| 1344 | return; |
| 1345 | } |
| 1346 | if (DEBUG) { |
| 1347 | Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState, |
| 1348 | new Exception()); |
| 1349 | } |
| 1350 | mScrollState = state; |
| 1351 | if (state != SCROLL_STATE_SETTLING) { |
| 1352 | stopScrollersInternal(); |
| 1353 | } |
| 1354 | dispatchOnScrollStateChanged(state); |
| 1355 | } |
| 1356 | |
| 1357 | /** |
| 1358 | * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can |
| 1359 | * affect both measurement and drawing of individual item views. |
| 1360 | * |
| 1361 | * <p>Item decorations are ordered. Decorations placed earlier in the list will |
| 1362 | * be run/queried/drawn first for their effects on item views. Padding added to views |
| 1363 | * will be nested; a padding added by an earlier decoration will mean further |
| 1364 | * item decorations in the list will be asked to draw/pad within the previous decoration's |
| 1365 | * given area.</p> |
| 1366 | * |
| 1367 | * @param decor Decoration to add |
| 1368 | * @param index Position in the decoration chain to insert this decoration at. If this value |
| 1369 | * is negative the decoration will be added at the end. |
| 1370 | */ |
| 1371 | public void addItemDecoration(ItemDecoration decor, int index) { |
| 1372 | if (mLayout != null) { |
| 1373 | mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" |
| 1374 | + " layout"); |
| 1375 | } |
| 1376 | if (mItemDecorations.isEmpty()) { |
| 1377 | setWillNotDraw(false); |
| 1378 | } |
| 1379 | if (index < 0) { |
| 1380 | mItemDecorations.add(decor); |
| 1381 | } else { |
| 1382 | mItemDecorations.add(index, decor); |
| 1383 | } |
| 1384 | markItemDecorInsetsDirty(); |
| 1385 | requestLayout(); |
| 1386 | } |
| 1387 | |
| 1388 | /** |
| 1389 | * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can |
| 1390 | * affect both measurement and drawing of individual item views. |
| 1391 | * |
| 1392 | * <p>Item decorations are ordered. Decorations placed earlier in the list will |
| 1393 | * be run/queried/drawn first for their effects on item views. Padding added to views |
| 1394 | * will be nested; a padding added by an earlier decoration will mean further |
| 1395 | * item decorations in the list will be asked to draw/pad within the previous decoration's |
| 1396 | * given area.</p> |
| 1397 | * |
| 1398 | * @param decor Decoration to add |
| 1399 | */ |
| 1400 | public void addItemDecoration(ItemDecoration decor) { |
| 1401 | addItemDecoration(decor, -1); |
| 1402 | } |
| 1403 | |
| 1404 | /** |
| 1405 | * Remove an {@link ItemDecoration} from this RecyclerView. |
| 1406 | * |
| 1407 | * <p>The given decoration will no longer impact the measurement and drawing of |
| 1408 | * item views.</p> |
| 1409 | * |
| 1410 | * @param decor Decoration to remove |
| 1411 | * @see #addItemDecoration(ItemDecoration) |
| 1412 | */ |
| 1413 | public void removeItemDecoration(ItemDecoration decor) { |
| 1414 | if (mLayout != null) { |
| 1415 | mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll or" |
| 1416 | + " layout"); |
| 1417 | } |
| 1418 | mItemDecorations.remove(decor); |
| 1419 | if (mItemDecorations.isEmpty()) { |
| 1420 | setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); |
| 1421 | } |
| 1422 | markItemDecorInsetsDirty(); |
| 1423 | requestLayout(); |
| 1424 | } |
| 1425 | |
| 1426 | /** |
| 1427 | * Sets the {@link ChildDrawingOrderCallback} to be used for drawing children. |
| 1428 | * <p> |
| 1429 | * See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will |
| 1430 | * always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be |
| 1431 | * true if childDrawingOrderCallback is not null, false otherwise. |
| 1432 | * <p> |
| 1433 | * Note that child drawing order may be overridden by View's elevation. |
| 1434 | * |
| 1435 | * @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing |
| 1436 | * system. |
| 1437 | */ |
| 1438 | public void setChildDrawingOrderCallback(ChildDrawingOrderCallback childDrawingOrderCallback) { |
| 1439 | if (childDrawingOrderCallback == mChildDrawingOrderCallback) { |
| 1440 | return; |
| 1441 | } |
| 1442 | mChildDrawingOrderCallback = childDrawingOrderCallback; |
| 1443 | setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null); |
| 1444 | } |
| 1445 | |
| 1446 | /** |
| 1447 | * Set a listener that will be notified of any changes in scroll state or position. |
| 1448 | * |
| 1449 | * @param listener Listener to set or null to clear |
| 1450 | * |
| 1451 | * @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and |
| 1452 | * {@link #removeOnScrollListener(OnScrollListener)} |
| 1453 | */ |
| 1454 | @Deprecated |
| 1455 | public void setOnScrollListener(OnScrollListener listener) { |
| 1456 | mScrollListener = listener; |
| 1457 | } |
| 1458 | |
| 1459 | /** |
| 1460 | * Add a listener that will be notified of any changes in scroll state or position. |
| 1461 | * |
| 1462 | * <p>Components that add a listener should take care to remove it when finished. |
| 1463 | * Other components that take ownership of a view may call {@link #clearOnScrollListeners()} |
| 1464 | * to remove all attached listeners.</p> |
| 1465 | * |
| 1466 | * @param listener listener to set or null to clear |
| 1467 | */ |
| 1468 | public void addOnScrollListener(OnScrollListener listener) { |
| 1469 | if (mScrollListeners == null) { |
| 1470 | mScrollListeners = new ArrayList<>(); |
| 1471 | } |
| 1472 | mScrollListeners.add(listener); |
| 1473 | } |
| 1474 | |
| 1475 | /** |
| 1476 | * Remove a listener that was notified of any changes in scroll state or position. |
| 1477 | * |
| 1478 | * @param listener listener to set or null to clear |
| 1479 | */ |
| 1480 | public void removeOnScrollListener(OnScrollListener listener) { |
| 1481 | if (mScrollListeners != null) { |
| 1482 | mScrollListeners.remove(listener); |
| 1483 | } |
| 1484 | } |
| 1485 | |
| 1486 | /** |
| 1487 | * Remove all secondary listener that were notified of any changes in scroll state or position. |
| 1488 | */ |
| 1489 | public void clearOnScrollListeners() { |
| 1490 | if (mScrollListeners != null) { |
| 1491 | mScrollListeners.clear(); |
| 1492 | } |
| 1493 | } |
| 1494 | |
| 1495 | /** |
| 1496 | * Convenience method to scroll to a certain position. |
| 1497 | * |
| 1498 | * RecyclerView does not implement scrolling logic, rather forwards the call to |
| 1499 | * {@link com.android.internal.widget.RecyclerView.LayoutManager#scrollToPosition(int)} |
| 1500 | * @param position Scroll to this adapter position |
| 1501 | * @see com.android.internal.widget.RecyclerView.LayoutManager#scrollToPosition(int) |
| 1502 | */ |
| 1503 | public void scrollToPosition(int position) { |
| 1504 | if (mLayoutFrozen) { |
| 1505 | return; |
| 1506 | } |
| 1507 | stopScroll(); |
| 1508 | if (mLayout == null) { |
| 1509 | Log.e(TAG, "Cannot scroll to position a LayoutManager set. " |
| 1510 | + "Call setLayoutManager with a non-null argument."); |
| 1511 | return; |
| 1512 | } |
| 1513 | mLayout.scrollToPosition(position); |
| 1514 | awakenScrollBars(); |
| 1515 | } |
| 1516 | |
| 1517 | void jumpToPositionForSmoothScroller(int position) { |
| 1518 | if (mLayout == null) { |
| 1519 | return; |
| 1520 | } |
| 1521 | mLayout.scrollToPosition(position); |
| 1522 | awakenScrollBars(); |
| 1523 | } |
| 1524 | |
| 1525 | /** |
| 1526 | * Starts a smooth scroll to an adapter position. |
| 1527 | * <p> |
| 1528 | * To support smooth scrolling, you must override |
| 1529 | * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a |
| 1530 | * {@link SmoothScroller}. |
| 1531 | * <p> |
| 1532 | * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to |
| 1533 | * provide a custom smooth scroll logic, override |
| 1534 | * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your |
| 1535 | * LayoutManager. |
| 1536 | * |
| 1537 | * @param position The adapter position to scroll to |
| 1538 | * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int) |
| 1539 | */ |
| 1540 | public void smoothScrollToPosition(int position) { |
| 1541 | if (mLayoutFrozen) { |
| 1542 | return; |
| 1543 | } |
| 1544 | if (mLayout == null) { |
| 1545 | Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " |
| 1546 | + "Call setLayoutManager with a non-null argument."); |
| 1547 | return; |
| 1548 | } |
| 1549 | mLayout.smoothScrollToPosition(this, mState, position); |
| 1550 | } |
| 1551 | |
| 1552 | @Override |
| 1553 | public void scrollTo(int x, int y) { |
| 1554 | Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. " |
| 1555 | + "Use scrollToPosition instead"); |
| 1556 | } |
| 1557 | |
| 1558 | @Override |
| 1559 | public void scrollBy(int x, int y) { |
| 1560 | if (mLayout == null) { |
| 1561 | Log.e(TAG, "Cannot scroll without a LayoutManager set. " |
| 1562 | + "Call setLayoutManager with a non-null argument."); |
| 1563 | return; |
| 1564 | } |
| 1565 | if (mLayoutFrozen) { |
| 1566 | return; |
| 1567 | } |
| 1568 | final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); |
| 1569 | final boolean canScrollVertical = mLayout.canScrollVertically(); |
| 1570 | if (canScrollHorizontal || canScrollVertical) { |
| 1571 | scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null); |
| 1572 | } |
| 1573 | } |
| 1574 | |
| 1575 | /** |
| 1576 | * Helper method reflect data changes to the state. |
| 1577 | * <p> |
| 1578 | * Adapter changes during a scroll may trigger a crash because scroll assumes no data change |
| 1579 | * but data actually changed. |
| 1580 | * <p> |
| 1581 | * This method consumes all deferred changes to avoid that case. |
| 1582 | */ |
| 1583 | void consumePendingUpdateOperations() { |
| 1584 | if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) { |
| 1585 | Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); |
| 1586 | dispatchLayout(); |
| 1587 | Trace.endSection(); |
| 1588 | return; |
| 1589 | } |
| 1590 | if (!mAdapterHelper.hasPendingUpdates()) { |
| 1591 | return; |
| 1592 | } |
| 1593 | |
| 1594 | // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any |
| 1595 | // of the visible items is affected and if not, just ignore the change. |
| 1596 | if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper |
| 1597 | .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE |
| 1598 | | AdapterHelper.UpdateOp.MOVE)) { |
| 1599 | Trace.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG); |
| 1600 | eatRequestLayout(); |
| 1601 | onEnterLayoutOrScroll(); |
| 1602 | mAdapterHelper.preProcess(); |
| 1603 | if (!mLayoutRequestEaten) { |
| 1604 | if (hasUpdatedView()) { |
| 1605 | dispatchLayout(); |
| 1606 | } else { |
| 1607 | // no need to layout, clean state |
| 1608 | mAdapterHelper.consumePostponedUpdates(); |
| 1609 | } |
| 1610 | } |
| 1611 | resumeRequestLayout(true); |
| 1612 | onExitLayoutOrScroll(); |
| 1613 | Trace.endSection(); |
| 1614 | } else if (mAdapterHelper.hasPendingUpdates()) { |
| 1615 | Trace.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); |
| 1616 | dispatchLayout(); |
| 1617 | Trace.endSection(); |
| 1618 | } |
| 1619 | } |
| 1620 | |
| 1621 | /** |
| 1622 | * @return True if an existing view holder needs to be updated |
| 1623 | */ |
| 1624 | private boolean hasUpdatedView() { |
| 1625 | final int childCount = mChildHelper.getChildCount(); |
| 1626 | for (int i = 0; i < childCount; i++) { |
| 1627 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); |
| 1628 | if (holder == null || holder.shouldIgnore()) { |
| 1629 | continue; |
| 1630 | } |
| 1631 | if (holder.isUpdated()) { |
| 1632 | return true; |
| 1633 | } |
| 1634 | } |
| 1635 | return false; |
| 1636 | } |
| 1637 | |
| 1638 | /** |
| 1639 | * Does not perform bounds checking. Used by internal methods that have already validated input. |
| 1640 | * <p> |
| 1641 | * It also reports any unused scroll request to the related EdgeEffect. |
| 1642 | * |
| 1643 | * @param x The amount of horizontal scroll request |
| 1644 | * @param y The amount of vertical scroll request |
| 1645 | * @param ev The originating MotionEvent, or null if not from a touch event. |
| 1646 | * |
| 1647 | * @return Whether any scroll was consumed in either direction. |
| 1648 | */ |
| 1649 | boolean scrollByInternal(int x, int y, MotionEvent ev) { |
| 1650 | int unconsumedX = 0, unconsumedY = 0; |
| 1651 | int consumedX = 0, consumedY = 0; |
| 1652 | |
| 1653 | consumePendingUpdateOperations(); |
| 1654 | if (mAdapter != null) { |
| 1655 | eatRequestLayout(); |
| 1656 | onEnterLayoutOrScroll(); |
| 1657 | Trace.beginSection(TRACE_SCROLL_TAG); |
| 1658 | if (x != 0) { |
| 1659 | consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); |
| 1660 | unconsumedX = x - consumedX; |
| 1661 | } |
| 1662 | if (y != 0) { |
| 1663 | consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); |
| 1664 | unconsumedY = y - consumedY; |
| 1665 | } |
| 1666 | Trace.endSection(); |
| 1667 | repositionShadowingViews(); |
| 1668 | onExitLayoutOrScroll(); |
| 1669 | resumeRequestLayout(false); |
| 1670 | } |
| 1671 | if (!mItemDecorations.isEmpty()) { |
| 1672 | invalidate(); |
| 1673 | } |
| 1674 | |
| 1675 | if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) { |
| 1676 | // Update the last touch co-ords, taking any scroll offset into account |
| 1677 | mLastTouchX -= mScrollOffset[0]; |
| 1678 | mLastTouchY -= mScrollOffset[1]; |
| 1679 | if (ev != null) { |
| 1680 | ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); |
| 1681 | } |
| 1682 | mNestedOffsets[0] += mScrollOffset[0]; |
| 1683 | mNestedOffsets[1] += mScrollOffset[1]; |
| 1684 | } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { |
| 1685 | if (ev != null) { |
| 1686 | pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY); |
| 1687 | } |
| 1688 | considerReleasingGlowsOnScroll(x, y); |
| 1689 | } |
| 1690 | if (consumedX != 0 || consumedY != 0) { |
| 1691 | dispatchOnScrolled(consumedX, consumedY); |
| 1692 | } |
| 1693 | if (!awakenScrollBars()) { |
| 1694 | invalidate(); |
| 1695 | } |
| 1696 | return consumedX != 0 || consumedY != 0; |
| 1697 | } |
| 1698 | |
| 1699 | /** |
| 1700 | * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal |
| 1701 | * range. This value is used to compute the length of the thumb within the scrollbar's track. |
| 1702 | * </p> |
| 1703 | * |
| 1704 | * <p>The range is expressed in arbitrary units that must be the same as the units used by |
| 1705 | * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p> |
| 1706 | * |
| 1707 | * <p>Default implementation returns 0.</p> |
| 1708 | * |
| 1709 | * <p>If you want to support scroll bars, override |
| 1710 | * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your |
| 1711 | * LayoutManager. </p> |
| 1712 | * |
| 1713 | * @return The horizontal offset of the scrollbar's thumb |
| 1714 | * @see com.android.internal.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset |
| 1715 | * (RecyclerView.State) |
| 1716 | */ |
| 1717 | @Override |
| 1718 | public int computeHorizontalScrollOffset() { |
| 1719 | if (mLayout == null) { |
| 1720 | return 0; |
| 1721 | } |
| 1722 | return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0; |
| 1723 | } |
| 1724 | |
| 1725 | /** |
| 1726 | * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the |
| 1727 | * horizontal range. This value is used to compute the length of the thumb within the |
| 1728 | * scrollbar's track.</p> |
| 1729 | * |
| 1730 | * <p>The range is expressed in arbitrary units that must be the same as the units used by |
| 1731 | * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p> |
| 1732 | * |
| 1733 | * <p>Default implementation returns 0.</p> |
| 1734 | * |
| 1735 | * <p>If you want to support scroll bars, override |
| 1736 | * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your |
| 1737 | * LayoutManager.</p> |
| 1738 | * |
| 1739 | * @return The horizontal extent of the scrollbar's thumb |
| 1740 | * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State) |
| 1741 | */ |
| 1742 | @Override |
| 1743 | public int computeHorizontalScrollExtent() { |
| 1744 | if (mLayout == null) { |
| 1745 | return 0; |
| 1746 | } |
| 1747 | return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0; |
| 1748 | } |
| 1749 | |
| 1750 | /** |
| 1751 | * <p>Compute the horizontal range that the horizontal scrollbar represents.</p> |
| 1752 | * |
| 1753 | * <p>The range is expressed in arbitrary units that must be the same as the units used by |
| 1754 | * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p> |
| 1755 | * |
| 1756 | * <p>Default implementation returns 0.</p> |
| 1757 | * |
| 1758 | * <p>If you want to support scroll bars, override |
| 1759 | * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your |
| 1760 | * LayoutManager.</p> |
| 1761 | * |
| 1762 | * @return The total horizontal range represented by the vertical scrollbar |
| 1763 | * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State) |
| 1764 | */ |
| 1765 | @Override |
| 1766 | public int computeHorizontalScrollRange() { |
| 1767 | if (mLayout == null) { |
| 1768 | return 0; |
| 1769 | } |
| 1770 | return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0; |
| 1771 | } |
| 1772 | |
| 1773 | /** |
| 1774 | * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range. |
| 1775 | * This value is used to compute the length of the thumb within the scrollbar's track. </p> |
| 1776 | * |
| 1777 | * <p>The range is expressed in arbitrary units that must be the same as the units used by |
| 1778 | * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p> |
| 1779 | * |
| 1780 | * <p>Default implementation returns 0.</p> |
| 1781 | * |
| 1782 | * <p>If you want to support scroll bars, override |
| 1783 | * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your |
| 1784 | * LayoutManager.</p> |
| 1785 | * |
| 1786 | * @return The vertical offset of the scrollbar's thumb |
| 1787 | * @see com.android.internal.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset |
| 1788 | * (RecyclerView.State) |
| 1789 | */ |
| 1790 | @Override |
| 1791 | public int computeVerticalScrollOffset() { |
| 1792 | if (mLayout == null) { |
| 1793 | return 0; |
| 1794 | } |
| 1795 | return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0; |
| 1796 | } |
| 1797 | |
| 1798 | /** |
| 1799 | * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. |
| 1800 | * This value is used to compute the length of the thumb within the scrollbar's track.</p> |
| 1801 | * |
| 1802 | * <p>The range is expressed in arbitrary units that must be the same as the units used by |
| 1803 | * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p> |
| 1804 | * |
| 1805 | * <p>Default implementation returns 0.</p> |
| 1806 | * |
| 1807 | * <p>If you want to support scroll bars, override |
| 1808 | * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your |
| 1809 | * LayoutManager.</p> |
| 1810 | * |
| 1811 | * @return The vertical extent of the scrollbar's thumb |
| 1812 | * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State) |
| 1813 | */ |
| 1814 | @Override |
| 1815 | public int computeVerticalScrollExtent() { |
| 1816 | if (mLayout == null) { |
| 1817 | return 0; |
| 1818 | } |
| 1819 | return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0; |
| 1820 | } |
| 1821 | |
| 1822 | /** |
| 1823 | * <p>Compute the vertical range that the vertical scrollbar represents.</p> |
| 1824 | * |
| 1825 | * <p>The range is expressed in arbitrary units that must be the same as the units used by |
| 1826 | * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p> |
| 1827 | * |
| 1828 | * <p>Default implementation returns 0.</p> |
| 1829 | * |
| 1830 | * <p>If you want to support scroll bars, override |
| 1831 | * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your |
| 1832 | * LayoutManager.</p> |
| 1833 | * |
| 1834 | * @return The total vertical range represented by the vertical scrollbar |
| 1835 | * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State) |
| 1836 | */ |
| 1837 | @Override |
| 1838 | public int computeVerticalScrollRange() { |
| 1839 | if (mLayout == null) { |
| 1840 | return 0; |
| 1841 | } |
| 1842 | return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0; |
| 1843 | } |
| 1844 | |
| 1845 | |
| 1846 | void eatRequestLayout() { |
| 1847 | mEatRequestLayout++; |
| 1848 | if (mEatRequestLayout == 1 && !mLayoutFrozen) { |
| 1849 | mLayoutRequestEaten = false; |
| 1850 | } |
| 1851 | } |
| 1852 | |
| 1853 | void resumeRequestLayout(boolean performLayoutChildren) { |
| 1854 | if (mEatRequestLayout < 1) { |
| 1855 | //noinspection PointlessBooleanExpression |
| 1856 | if (DEBUG) { |
| 1857 | throw new IllegalStateException("invalid eat request layout count"); |
| 1858 | } |
| 1859 | mEatRequestLayout = 1; |
| 1860 | } |
| 1861 | if (!performLayoutChildren) { |
| 1862 | // Reset the layout request eaten counter. |
| 1863 | // This is necessary since eatRequest calls can be nested in which case the other |
| 1864 | // call will override the inner one. |
| 1865 | // for instance: |
| 1866 | // eat layout for process adapter updates |
| 1867 | // eat layout for dispatchLayout |
| 1868 | // a bunch of req layout calls arrive |
| 1869 | |
| 1870 | mLayoutRequestEaten = false; |
| 1871 | } |
| 1872 | if (mEatRequestLayout == 1) { |
| 1873 | // when layout is frozen we should delay dispatchLayout() |
| 1874 | if (performLayoutChildren && mLayoutRequestEaten && !mLayoutFrozen |
| 1875 | && mLayout != null && mAdapter != null) { |
| 1876 | dispatchLayout(); |
| 1877 | } |
| 1878 | if (!mLayoutFrozen) { |
| 1879 | mLayoutRequestEaten = false; |
| 1880 | } |
| 1881 | } |
| 1882 | mEatRequestLayout--; |
| 1883 | } |
| 1884 | |
| 1885 | /** |
| 1886 | * Enable or disable layout and scroll. After <code>setLayoutFrozen(true)</code> is called, |
| 1887 | * Layout requests will be postponed until <code>setLayoutFrozen(false)</code> is called; |
| 1888 | * child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)}, |
| 1889 | * {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and |
| 1890 | * {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are |
| 1891 | * dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be |
| 1892 | * called. |
| 1893 | * |
| 1894 | * <p> |
| 1895 | * <code>setLayoutFrozen(true)</code> does not prevent app from directly calling {@link |
| 1896 | * LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition( |
| 1897 | * RecyclerView, State, int)}. |
| 1898 | * <p> |
| 1899 | * {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically |
| 1900 | * stop frozen. |
| 1901 | * <p> |
| 1902 | * Note: Running ItemAnimator is not stopped automatically, it's caller's |
| 1903 | * responsibility to call ItemAnimator.end(). |
| 1904 | * |
| 1905 | * @param frozen true to freeze layout and scroll, false to re-enable. |
| 1906 | */ |
| 1907 | public void setLayoutFrozen(boolean frozen) { |
| 1908 | if (frozen != mLayoutFrozen) { |
| 1909 | assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll"); |
| 1910 | if (!frozen) { |
| 1911 | mLayoutFrozen = false; |
| 1912 | if (mLayoutRequestEaten && mLayout != null && mAdapter != null) { |
| 1913 | requestLayout(); |
| 1914 | } |
| 1915 | mLayoutRequestEaten = false; |
| 1916 | } else { |
| 1917 | final long now = SystemClock.uptimeMillis(); |
| 1918 | MotionEvent cancelEvent = MotionEvent.obtain(now, now, |
| 1919 | MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); |
| 1920 | onTouchEvent(cancelEvent); |
| 1921 | mLayoutFrozen = true; |
| 1922 | mIgnoreMotionEventTillDown = true; |
| 1923 | stopScroll(); |
| 1924 | } |
| 1925 | } |
| 1926 | } |
| 1927 | |
| 1928 | /** |
| 1929 | * Returns true if layout and scroll are frozen. |
| 1930 | * |
| 1931 | * @return true if layout and scroll are frozen |
| 1932 | * @see #setLayoutFrozen(boolean) |
| 1933 | */ |
| 1934 | public boolean isLayoutFrozen() { |
| 1935 | return mLayoutFrozen; |
| 1936 | } |
| 1937 | |
| 1938 | /** |
| 1939 | * Animate a scroll by the given amount of pixels along either axis. |
| 1940 | * |
| 1941 | * @param dx Pixels to scroll horizontally |
| 1942 | * @param dy Pixels to scroll vertically |
| 1943 | */ |
| 1944 | public void smoothScrollBy(int dx, int dy) { |
| 1945 | smoothScrollBy(dx, dy, null); |
| 1946 | } |
| 1947 | |
| 1948 | /** |
| 1949 | * Animate a scroll by the given amount of pixels along either axis. |
| 1950 | * |
| 1951 | * @param dx Pixels to scroll horizontally |
| 1952 | * @param dy Pixels to scroll vertically |
| 1953 | * @param interpolator {@link Interpolator} to be used for scrolling. If it is |
| 1954 | * {@code null}, RecyclerView is going to use the default interpolator. |
| 1955 | */ |
| 1956 | public void smoothScrollBy(int dx, int dy, Interpolator interpolator) { |
| 1957 | if (mLayout == null) { |
| 1958 | Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " |
| 1959 | + "Call setLayoutManager with a non-null argument."); |
| 1960 | return; |
| 1961 | } |
| 1962 | if (mLayoutFrozen) { |
| 1963 | return; |
| 1964 | } |
| 1965 | if (!mLayout.canScrollHorizontally()) { |
| 1966 | dx = 0; |
| 1967 | } |
| 1968 | if (!mLayout.canScrollVertically()) { |
| 1969 | dy = 0; |
| 1970 | } |
| 1971 | if (dx != 0 || dy != 0) { |
| 1972 | mViewFlinger.smoothScrollBy(dx, dy, interpolator); |
| 1973 | } |
| 1974 | } |
| 1975 | |
| 1976 | /** |
| 1977 | * Begin a standard fling with an initial velocity along each axis in pixels per second. |
| 1978 | * If the velocity given is below the system-defined minimum this method will return false |
| 1979 | * and no fling will occur. |
| 1980 | * |
| 1981 | * @param velocityX Initial horizontal velocity in pixels per second |
| 1982 | * @param velocityY Initial vertical velocity in pixels per second |
| 1983 | * @return true if the fling was started, false if the velocity was too low to fling or |
| 1984 | * LayoutManager does not support scrolling in the axis fling is issued. |
| 1985 | * |
| 1986 | * @see LayoutManager#canScrollVertically() |
| 1987 | * @see LayoutManager#canScrollHorizontally() |
| 1988 | */ |
| 1989 | public boolean fling(int velocityX, int velocityY) { |
| 1990 | if (mLayout == null) { |
| 1991 | Log.e(TAG, "Cannot fling without a LayoutManager set. " |
| 1992 | + "Call setLayoutManager with a non-null argument."); |
| 1993 | return false; |
| 1994 | } |
| 1995 | if (mLayoutFrozen) { |
| 1996 | return false; |
| 1997 | } |
| 1998 | |
| 1999 | final boolean canScrollHorizontal = mLayout.canScrollHorizontally(); |
| 2000 | final boolean canScrollVertical = mLayout.canScrollVertically(); |
| 2001 | |
| 2002 | if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { |
| 2003 | velocityX = 0; |
| 2004 | } |
| 2005 | if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { |
| 2006 | velocityY = 0; |
| 2007 | } |
| 2008 | if (velocityX == 0 && velocityY == 0) { |
| 2009 | // If we don't have any velocity, return false |
| 2010 | return false; |
| 2011 | } |
| 2012 | |
| 2013 | if (!dispatchNestedPreFling(velocityX, velocityY)) { |
| 2014 | final boolean canScroll = canScrollHorizontal || canScrollVertical; |
| 2015 | dispatchNestedFling(velocityX, velocityY, canScroll); |
| 2016 | |
| 2017 | if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { |
| 2018 | return true; |
| 2019 | } |
| 2020 | |
| 2021 | if (canScroll) { |
| 2022 | velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); |
| 2023 | velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); |
| 2024 | mViewFlinger.fling(velocityX, velocityY); |
| 2025 | return true; |
| 2026 | } |
| 2027 | } |
| 2028 | return false; |
| 2029 | } |
| 2030 | |
| 2031 | /** |
| 2032 | * Stop any current scroll in progress, such as one started by |
| 2033 | * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling. |
| 2034 | */ |
| 2035 | public void stopScroll() { |
| 2036 | setScrollState(SCROLL_STATE_IDLE); |
| 2037 | stopScrollersInternal(); |
| 2038 | } |
| 2039 | |
| 2040 | /** |
| 2041 | * Similar to {@link #stopScroll()} but does not set the state. |
| 2042 | */ |
| 2043 | private void stopScrollersInternal() { |
| 2044 | mViewFlinger.stop(); |
| 2045 | if (mLayout != null) { |
| 2046 | mLayout.stopSmoothScroller(); |
| 2047 | } |
| 2048 | } |
| 2049 | |
| 2050 | /** |
| 2051 | * Returns the minimum velocity to start a fling. |
| 2052 | * |
| 2053 | * @return The minimum velocity to start a fling |
| 2054 | */ |
| 2055 | public int getMinFlingVelocity() { |
| 2056 | return mMinFlingVelocity; |
| 2057 | } |
| 2058 | |
| 2059 | |
| 2060 | /** |
| 2061 | * Returns the maximum fling velocity used by this RecyclerView. |
| 2062 | * |
| 2063 | * @return The maximum fling velocity used by this RecyclerView. |
| 2064 | */ |
| 2065 | public int getMaxFlingVelocity() { |
| 2066 | return mMaxFlingVelocity; |
| 2067 | } |
| 2068 | |
| 2069 | /** |
| 2070 | * Apply a pull to relevant overscroll glow effects |
| 2071 | */ |
| 2072 | private void pullGlows(float x, float overscrollX, float y, float overscrollY) { |
| 2073 | boolean invalidate = false; |
| 2074 | if (overscrollX < 0) { |
| 2075 | ensureLeftGlow(); |
| 2076 | mLeftGlow.onPull(-overscrollX / getWidth(), 1f - y / getHeight()); |
| 2077 | invalidate = true; |
| 2078 | } else if (overscrollX > 0) { |
| 2079 | ensureRightGlow(); |
| 2080 | mRightGlow.onPull(overscrollX / getWidth(), y / getHeight()); |
| 2081 | invalidate = true; |
| 2082 | } |
| 2083 | |
| 2084 | if (overscrollY < 0) { |
| 2085 | ensureTopGlow(); |
| 2086 | mTopGlow.onPull(-overscrollY / getHeight(), x / getWidth()); |
| 2087 | invalidate = true; |
| 2088 | } else if (overscrollY > 0) { |
| 2089 | ensureBottomGlow(); |
| 2090 | mBottomGlow.onPull(overscrollY / getHeight(), 1f - x / getWidth()); |
| 2091 | invalidate = true; |
| 2092 | } |
| 2093 | |
| 2094 | if (invalidate || overscrollX != 0 || overscrollY != 0) { |
| 2095 | postInvalidateOnAnimation(); |
| 2096 | } |
| 2097 | } |
| 2098 | |
| 2099 | private void releaseGlows() { |
| 2100 | boolean needsInvalidate = false; |
| 2101 | if (mLeftGlow != null) { |
| 2102 | mLeftGlow.onRelease(); |
| 2103 | needsInvalidate = true; |
| 2104 | } |
| 2105 | if (mTopGlow != null) { |
| 2106 | mTopGlow.onRelease(); |
| 2107 | needsInvalidate = true; |
| 2108 | } |
| 2109 | if (mRightGlow != null) { |
| 2110 | mRightGlow.onRelease(); |
| 2111 | needsInvalidate = true; |
| 2112 | } |
| 2113 | if (mBottomGlow != null) { |
| 2114 | mBottomGlow.onRelease(); |
| 2115 | needsInvalidate = true; |
| 2116 | } |
| 2117 | if (needsInvalidate) { |
| 2118 | postInvalidateOnAnimation(); |
| 2119 | } |
| 2120 | } |
| 2121 | |
| 2122 | void considerReleasingGlowsOnScroll(int dx, int dy) { |
| 2123 | boolean needsInvalidate = false; |
| 2124 | if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { |
| 2125 | mLeftGlow.onRelease(); |
| 2126 | needsInvalidate = true; |
| 2127 | } |
| 2128 | if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) { |
| 2129 | mRightGlow.onRelease(); |
| 2130 | needsInvalidate = true; |
| 2131 | } |
| 2132 | if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) { |
| 2133 | mTopGlow.onRelease(); |
| 2134 | needsInvalidate = true; |
| 2135 | } |
| 2136 | if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) { |
| 2137 | mBottomGlow.onRelease(); |
| 2138 | needsInvalidate = true; |
| 2139 | } |
| 2140 | if (needsInvalidate) { |
| 2141 | postInvalidateOnAnimation(); |
| 2142 | } |
| 2143 | } |
| 2144 | |
| 2145 | void absorbGlows(int velocityX, int velocityY) { |
| 2146 | if (velocityX < 0) { |
| 2147 | ensureLeftGlow(); |
| 2148 | mLeftGlow.onAbsorb(-velocityX); |
| 2149 | } else if (velocityX > 0) { |
| 2150 | ensureRightGlow(); |
| 2151 | mRightGlow.onAbsorb(velocityX); |
| 2152 | } |
| 2153 | |
| 2154 | if (velocityY < 0) { |
| 2155 | ensureTopGlow(); |
| 2156 | mTopGlow.onAbsorb(-velocityY); |
| 2157 | } else if (velocityY > 0) { |
| 2158 | ensureBottomGlow(); |
| 2159 | mBottomGlow.onAbsorb(velocityY); |
| 2160 | } |
| 2161 | |
| 2162 | if (velocityX != 0 || velocityY != 0) { |
| 2163 | postInvalidateOnAnimation(); |
| 2164 | } |
| 2165 | } |
| 2166 | |
| 2167 | void ensureLeftGlow() { |
| 2168 | if (mLeftGlow != null) { |
| 2169 | return; |
| 2170 | } |
| 2171 | mLeftGlow = new EdgeEffect(getContext()); |
| 2172 | if (mClipToPadding) { |
| 2173 | mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), |
| 2174 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); |
| 2175 | } else { |
| 2176 | mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); |
| 2177 | } |
| 2178 | } |
| 2179 | |
| 2180 | void ensureRightGlow() { |
| 2181 | if (mRightGlow != null) { |
| 2182 | return; |
| 2183 | } |
| 2184 | mRightGlow = new EdgeEffect(getContext()); |
| 2185 | if (mClipToPadding) { |
| 2186 | mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), |
| 2187 | getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); |
| 2188 | } else { |
| 2189 | mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth()); |
| 2190 | } |
| 2191 | } |
| 2192 | |
| 2193 | void ensureTopGlow() { |
| 2194 | if (mTopGlow != null) { |
| 2195 | return; |
| 2196 | } |
| 2197 | mTopGlow = new EdgeEffect(getContext()); |
| 2198 | if (mClipToPadding) { |
| 2199 | mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), |
| 2200 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); |
| 2201 | } else { |
| 2202 | mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); |
| 2203 | } |
| 2204 | |
| 2205 | } |
| 2206 | |
| 2207 | void ensureBottomGlow() { |
| 2208 | if (mBottomGlow != null) { |
| 2209 | return; |
| 2210 | } |
| 2211 | mBottomGlow = new EdgeEffect(getContext()); |
| 2212 | if (mClipToPadding) { |
| 2213 | mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), |
| 2214 | getMeasuredHeight() - getPaddingTop() - getPaddingBottom()); |
| 2215 | } else { |
| 2216 | mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight()); |
| 2217 | } |
| 2218 | } |
| 2219 | |
| 2220 | void invalidateGlows() { |
| 2221 | mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; |
| 2222 | } |
| 2223 | |
| 2224 | /** |
| 2225 | * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are |
| 2226 | * in the Adapter but not visible in the UI), it employs a more involved focus search strategy |
| 2227 | * that differs from other ViewGroups. |
| 2228 | * <p> |
| 2229 | * It first does a focus search within the RecyclerView. If this search finds a View that is in |
| 2230 | * the focus direction with respect to the currently focused View, RecyclerView returns that |
| 2231 | * child as the next focus target. When it cannot find such child, it calls |
| 2232 | * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views |
| 2233 | * in the focus search direction. If LayoutManager adds a View that matches the |
| 2234 | * focus search criteria, it will be returned as the focus search result. Otherwise, |
| 2235 | * RecyclerView will call parent to handle the focus search like a regular ViewGroup. |
| 2236 | * <p> |
| 2237 | * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that |
| 2238 | * is not in the focus direction is still valid focus target which may not be the desired |
| 2239 | * behavior if the Adapter has more children in the focus direction. To handle this case, |
| 2240 | * RecyclerView converts the focus direction to an absolute direction and makes a preliminary |
| 2241 | * focus search in that direction. If there are no Views to gain focus, it will call |
| 2242 | * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a |
| 2243 | * focus search with the original (relative) direction. This allows RecyclerView to provide |
| 2244 | * better candidates to the focus search while still allowing the view system to take focus from |
| 2245 | * the RecyclerView and give it to a more suitable child if such child exists. |
| 2246 | * |
| 2247 | * @param focused The view that currently has focus |
| 2248 | * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, |
| 2249 | * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}, |
| 2250 | * {@link View#FOCUS_BACKWARD} or 0 for not applicable. |
| 2251 | * |
| 2252 | * @return A new View that can be the next focus after the focused View |
| 2253 | */ |
| 2254 | @Override |
| 2255 | public View focusSearch(View focused, int direction) { |
| 2256 | View result = mLayout.onInterceptFocusSearch(focused, direction); |
| 2257 | if (result != null) { |
| 2258 | return result; |
| 2259 | } |
| 2260 | final boolean canRunFocusFailure = mAdapter != null && mLayout != null |
| 2261 | && !isComputingLayout() && !mLayoutFrozen; |
| 2262 | |
| 2263 | final FocusFinder ff = FocusFinder.getInstance(); |
| 2264 | if (canRunFocusFailure |
| 2265 | && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) { |
| 2266 | // convert direction to absolute direction and see if we have a view there and if not |
| 2267 | // tell LayoutManager to add if it can. |
| 2268 | boolean needsFocusFailureLayout = false; |
| 2269 | if (mLayout.canScrollVertically()) { |
| 2270 | final int absDir = |
| 2271 | direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP; |
| 2272 | final View found = ff.findNextFocus(this, focused, absDir); |
| 2273 | needsFocusFailureLayout = found == null; |
| 2274 | if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { |
| 2275 | // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. |
| 2276 | direction = absDir; |
| 2277 | } |
| 2278 | } |
| 2279 | if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) { |
| 2280 | boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; |
| 2281 | final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl |
| 2282 | ? View.FOCUS_RIGHT : View.FOCUS_LEFT; |
| 2283 | final View found = ff.findNextFocus(this, focused, absDir); |
| 2284 | needsFocusFailureLayout = found == null; |
| 2285 | if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { |
| 2286 | // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. |
| 2287 | direction = absDir; |
| 2288 | } |
| 2289 | } |
| 2290 | if (needsFocusFailureLayout) { |
| 2291 | consumePendingUpdateOperations(); |
| 2292 | final View focusedItemView = findContainingItemView(focused); |
| 2293 | if (focusedItemView == null) { |
| 2294 | // panic, focused view is not a child anymore, cannot call super. |
| 2295 | return null; |
| 2296 | } |
| 2297 | eatRequestLayout(); |
| 2298 | mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); |
| 2299 | resumeRequestLayout(false); |
| 2300 | } |
| 2301 | result = ff.findNextFocus(this, focused, direction); |
| 2302 | } else { |
| 2303 | result = ff.findNextFocus(this, focused, direction); |
| 2304 | if (result == null && canRunFocusFailure) { |
| 2305 | consumePendingUpdateOperations(); |
| 2306 | final View focusedItemView = findContainingItemView(focused); |
| 2307 | if (focusedItemView == null) { |
| 2308 | // panic, focused view is not a child anymore, cannot call super. |
| 2309 | return null; |
| 2310 | } |
| 2311 | eatRequestLayout(); |
| 2312 | result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); |
| 2313 | resumeRequestLayout(false); |
| 2314 | } |
| 2315 | } |
| 2316 | return isPreferredNextFocus(focused, result, direction) |
| 2317 | ? result : super.focusSearch(focused, direction); |
| 2318 | } |
| 2319 | |
| 2320 | /** |
| 2321 | * Checks if the new focus candidate is a good enough candidate such that RecyclerView will |
| 2322 | * assign it as the next focus View instead of letting view hierarchy decide. |
| 2323 | * A good candidate means a View that is aligned in the focus direction wrt the focused View |
| 2324 | * and is not the RecyclerView itself. |
| 2325 | * When this method returns false, RecyclerView will let the parent make the decision so the |
| 2326 | * same View may still get the focus as a result of that search. |
| 2327 | */ |
| 2328 | private boolean isPreferredNextFocus(View focused, View next, int direction) { |
| 2329 | if (next == null || next == this) { |
| 2330 | return false; |
| 2331 | } |
| 2332 | if (focused == null) { |
| 2333 | return true; |
| 2334 | } |
| 2335 | |
| 2336 | if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) { |
| 2337 | final boolean rtl = mLayout.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; |
| 2338 | final int absHorizontal = (direction == View.FOCUS_FORWARD) ^ rtl |
| 2339 | ? View.FOCUS_RIGHT : View.FOCUS_LEFT; |
| 2340 | if (isPreferredNextFocusAbsolute(focused, next, absHorizontal)) { |
| 2341 | return true; |
| 2342 | } |
| 2343 | if (direction == View.FOCUS_FORWARD) { |
| 2344 | return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_DOWN); |
| 2345 | } else { |
| 2346 | return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_UP); |
| 2347 | } |
| 2348 | } else { |
| 2349 | return isPreferredNextFocusAbsolute(focused, next, direction); |
| 2350 | } |
| 2351 | |
| 2352 | } |
| 2353 | |
| 2354 | /** |
| 2355 | * Logic taken from FocusSearch#isCandidate |
| 2356 | */ |
| 2357 | private boolean isPreferredNextFocusAbsolute(View focused, View next, int direction) { |
| 2358 | mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); |
| 2359 | mTempRect2.set(0, 0, next.getWidth(), next.getHeight()); |
| 2360 | offsetDescendantRectToMyCoords(focused, mTempRect); |
| 2361 | offsetDescendantRectToMyCoords(next, mTempRect2); |
| 2362 | switch (direction) { |
| 2363 | case View.FOCUS_LEFT: |
| 2364 | return (mTempRect.right > mTempRect2.right |
| 2365 | || mTempRect.left >= mTempRect2.right) |
| 2366 | && mTempRect.left > mTempRect2.left; |
| 2367 | case View.FOCUS_RIGHT: |
| 2368 | return (mTempRect.left < mTempRect2.left |
| 2369 | || mTempRect.right <= mTempRect2.left) |
| 2370 | && mTempRect.right < mTempRect2.right; |
| 2371 | case View.FOCUS_UP: |
| 2372 | return (mTempRect.bottom > mTempRect2.bottom |
| 2373 | || mTempRect.top >= mTempRect2.bottom) |
| 2374 | && mTempRect.top > mTempRect2.top; |
| 2375 | case View.FOCUS_DOWN: |
| 2376 | return (mTempRect.top < mTempRect2.top |
| 2377 | || mTempRect.bottom <= mTempRect2.top) |
| 2378 | && mTempRect.bottom < mTempRect2.bottom; |
| 2379 | } |
| 2380 | throw new IllegalArgumentException("direction must be absolute. received:" + direction); |
| 2381 | } |
| 2382 | |
| 2383 | @Override |
| 2384 | public void requestChildFocus(View child, View focused) { |
| 2385 | if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) { |
| 2386 | mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); |
| 2387 | |
| 2388 | // get item decor offsets w/o refreshing. If they are invalid, there will be another |
| 2389 | // layout pass to fix them, then it is LayoutManager's responsibility to keep focused |
| 2390 | // View in viewport. |
| 2391 | final ViewGroup.LayoutParams focusedLayoutParams = focused.getLayoutParams(); |
| 2392 | if (focusedLayoutParams instanceof LayoutParams) { |
| 2393 | // if focused child has item decors, use them. Otherwise, ignore. |
| 2394 | final LayoutParams lp = (LayoutParams) focusedLayoutParams; |
| 2395 | if (!lp.mInsetsDirty) { |
| 2396 | final Rect insets = lp.mDecorInsets; |
| 2397 | mTempRect.left -= insets.left; |
| 2398 | mTempRect.right += insets.right; |
| 2399 | mTempRect.top -= insets.top; |
| 2400 | mTempRect.bottom += insets.bottom; |
| 2401 | } |
| 2402 | } |
| 2403 | |
| 2404 | offsetDescendantRectToMyCoords(focused, mTempRect); |
| 2405 | offsetRectIntoDescendantCoords(child, mTempRect); |
| 2406 | requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete); |
| 2407 | } |
| 2408 | super.requestChildFocus(child, focused); |
| 2409 | } |
| 2410 | |
| 2411 | @Override |
| 2412 | public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { |
| 2413 | return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate); |
| 2414 | } |
| 2415 | |
| 2416 | @Override |
| 2417 | public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { |
| 2418 | if (mLayout == null || !mLayout.onAddFocusables(this, views, direction, focusableMode)) { |
| 2419 | super.addFocusables(views, direction, focusableMode); |
| 2420 | } |
| 2421 | } |
| 2422 | |
| 2423 | @Override |
| 2424 | protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { |
| 2425 | if (isComputingLayout()) { |
| 2426 | // if we are in the middle of a layout calculation, don't let any child take focus. |
| 2427 | // RV will handle it after layout calculation is finished. |
| 2428 | return false; |
| 2429 | } |
| 2430 | return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); |
| 2431 | } |
| 2432 | |
| 2433 | @Override |
| 2434 | protected void onAttachedToWindow() { |
| 2435 | super.onAttachedToWindow(); |
| 2436 | mLayoutOrScrollCounter = 0; |
| 2437 | mIsAttached = true; |
| 2438 | mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested(); |
| 2439 | if (mLayout != null) { |
| 2440 | mLayout.dispatchAttachedToWindow(this); |
| 2441 | } |
| 2442 | mPostedAnimatorRunner = false; |
| 2443 | |
| 2444 | if (ALLOW_THREAD_GAP_WORK) { |
| 2445 | // Register with gap worker |
| 2446 | mGapWorker = GapWorker.sGapWorker.get(); |
| 2447 | if (mGapWorker == null) { |
| 2448 | mGapWorker = new GapWorker(); |
| 2449 | |
| 2450 | // break 60 fps assumption if data from display appears valid |
| 2451 | // NOTE: we only do this query once, statically, because it's very expensive (> 1ms) |
| 2452 | Display display = getDisplay(); |
| 2453 | float refreshRate = 60.0f; |
| 2454 | if (!isInEditMode() && display != null) { |
| 2455 | float displayRefreshRate = display.getRefreshRate(); |
| 2456 | if (displayRefreshRate >= 30.0f) { |
| 2457 | refreshRate = displayRefreshRate; |
| 2458 | } |
| 2459 | } |
| 2460 | mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate); |
| 2461 | GapWorker.sGapWorker.set(mGapWorker); |
| 2462 | } |
| 2463 | mGapWorker.add(this); |
| 2464 | } |
| 2465 | } |
| 2466 | |
| 2467 | @Override |
| 2468 | protected void onDetachedFromWindow() { |
| 2469 | super.onDetachedFromWindow(); |
| 2470 | if (mItemAnimator != null) { |
| 2471 | mItemAnimator.endAnimations(); |
| 2472 | } |
| 2473 | stopScroll(); |
| 2474 | mIsAttached = false; |
| 2475 | if (mLayout != null) { |
| 2476 | mLayout.dispatchDetachedFromWindow(this, mRecycler); |
| 2477 | } |
| 2478 | mPendingAccessibilityImportanceChange.clear(); |
| 2479 | removeCallbacks(mItemAnimatorRunner); |
| 2480 | mViewInfoStore.onDetach(); |
| 2481 | |
| 2482 | if (ALLOW_THREAD_GAP_WORK) { |
| 2483 | // Unregister with gap worker |
| 2484 | mGapWorker.remove(this); |
| 2485 | mGapWorker = null; |
| 2486 | } |
| 2487 | } |
| 2488 | |
| 2489 | /** |
| 2490 | * Returns true if RecyclerView is attached to window. |
| 2491 | */ |
| 2492 | // @override |
| 2493 | public boolean isAttachedToWindow() { |
| 2494 | return mIsAttached; |
| 2495 | } |
| 2496 | |
| 2497 | /** |
| 2498 | * Checks if RecyclerView is in the middle of a layout or scroll and throws an |
| 2499 | * {@link IllegalStateException} if it <b>is not</b>. |
| 2500 | * |
| 2501 | * @param message The message for the exception. Can be null. |
| 2502 | * @see #assertNotInLayoutOrScroll(String) |
| 2503 | */ |
| 2504 | void assertInLayoutOrScroll(String message) { |
| 2505 | if (!isComputingLayout()) { |
| 2506 | if (message == null) { |
| 2507 | throw new IllegalStateException("Cannot call this method unless RecyclerView is " |
| 2508 | + "computing a layout or scrolling"); |
| 2509 | } |
| 2510 | throw new IllegalStateException(message); |
| 2511 | |
| 2512 | } |
| 2513 | } |
| 2514 | |
| 2515 | /** |
| 2516 | * Checks if RecyclerView is in the middle of a layout or scroll and throws an |
| 2517 | * {@link IllegalStateException} if it <b>is</b>. |
| 2518 | * |
| 2519 | * @param message The message for the exception. Can be null. |
| 2520 | * @see #assertInLayoutOrScroll(String) |
| 2521 | */ |
| 2522 | void assertNotInLayoutOrScroll(String message) { |
| 2523 | if (isComputingLayout()) { |
| 2524 | if (message == null) { |
| 2525 | throw new IllegalStateException("Cannot call this method while RecyclerView is " |
| 2526 | + "computing a layout or scrolling"); |
| 2527 | } |
| 2528 | throw new IllegalStateException(message); |
| 2529 | } |
| 2530 | if (mDispatchScrollCounter > 0) { |
| 2531 | Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might be run" |
| 2532 | + " during a measure & layout pass where you cannot change the RecyclerView" |
| 2533 | + " data. Any method call that might change the structure of the RecyclerView" |
| 2534 | + " or the adapter contents should be postponed to the next frame.", |
| 2535 | new IllegalStateException("")); |
| 2536 | } |
| 2537 | } |
| 2538 | |
| 2539 | /** |
| 2540 | * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched |
| 2541 | * to child views or this view's standard scrolling behavior. |
| 2542 | * |
| 2543 | * <p>Client code may use listeners to implement item manipulation behavior. Once a listener |
| 2544 | * returns true from |
| 2545 | * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its |
| 2546 | * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called |
| 2547 | * for each incoming MotionEvent until the end of the gesture.</p> |
| 2548 | * |
| 2549 | * @param listener Listener to add |
| 2550 | * @see SimpleOnItemTouchListener |
| 2551 | */ |
| 2552 | public void addOnItemTouchListener(OnItemTouchListener listener) { |
| 2553 | mOnItemTouchListeners.add(listener); |
| 2554 | } |
| 2555 | |
| 2556 | /** |
| 2557 | * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events. |
| 2558 | * |
| 2559 | * @param listener Listener to remove |
| 2560 | */ |
| 2561 | public void removeOnItemTouchListener(OnItemTouchListener listener) { |
| 2562 | mOnItemTouchListeners.remove(listener); |
| 2563 | if (mActiveOnItemTouchListener == listener) { |
| 2564 | mActiveOnItemTouchListener = null; |
| 2565 | } |
| 2566 | } |
| 2567 | |
| 2568 | private boolean dispatchOnItemTouchIntercept(MotionEvent e) { |
| 2569 | final int action = e.getAction(); |
| 2570 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) { |
| 2571 | mActiveOnItemTouchListener = null; |
| 2572 | } |
| 2573 | |
| 2574 | final int listenerCount = mOnItemTouchListeners.size(); |
| 2575 | for (int i = 0; i < listenerCount; i++) { |
| 2576 | final OnItemTouchListener listener = mOnItemTouchListeners.get(i); |
| 2577 | if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) { |
| 2578 | mActiveOnItemTouchListener = listener; |
| 2579 | return true; |
| 2580 | } |
| 2581 | } |
| 2582 | return false; |
| 2583 | } |
| 2584 | |
| 2585 | private boolean dispatchOnItemTouch(MotionEvent e) { |
| 2586 | final int action = e.getAction(); |
| 2587 | if (mActiveOnItemTouchListener != null) { |
| 2588 | if (action == MotionEvent.ACTION_DOWN) { |
| 2589 | // Stale state from a previous gesture, we're starting a new one. Clear it. |
| 2590 | mActiveOnItemTouchListener = null; |
| 2591 | } else { |
| 2592 | mActiveOnItemTouchListener.onTouchEvent(this, e); |
| 2593 | if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { |
| 2594 | // Clean up for the next gesture. |
| 2595 | mActiveOnItemTouchListener = null; |
| 2596 | } |
| 2597 | return true; |
| 2598 | } |
| 2599 | } |
| 2600 | |
| 2601 | // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept |
| 2602 | // as called from onInterceptTouchEvent; skip it. |
| 2603 | if (action != MotionEvent.ACTION_DOWN) { |
| 2604 | final int listenerCount = mOnItemTouchListeners.size(); |
| 2605 | for (int i = 0; i < listenerCount; i++) { |
| 2606 | final OnItemTouchListener listener = mOnItemTouchListeners.get(i); |
| 2607 | if (listener.onInterceptTouchEvent(this, e)) { |
| 2608 | mActiveOnItemTouchListener = listener; |
| 2609 | return true; |
| 2610 | } |
| 2611 | } |
| 2612 | } |
| 2613 | return false; |
| 2614 | } |
| 2615 | |
| 2616 | @Override |
| 2617 | public boolean onInterceptTouchEvent(MotionEvent e) { |
| 2618 | if (mLayoutFrozen) { |
| 2619 | // When layout is frozen, RV does not intercept the motion event. |
| 2620 | // A child view e.g. a button may still get the click. |
| 2621 | return false; |
| 2622 | } |
| 2623 | if (dispatchOnItemTouchIntercept(e)) { |
| 2624 | cancelTouch(); |
| 2625 | return true; |
| 2626 | } |
| 2627 | |
| 2628 | if (mLayout == null) { |
| 2629 | return false; |
| 2630 | } |
| 2631 | |
| 2632 | final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); |
| 2633 | final boolean canScrollVertically = mLayout.canScrollVertically(); |
| 2634 | |
| 2635 | if (mVelocityTracker == null) { |
| 2636 | mVelocityTracker = VelocityTracker.obtain(); |
| 2637 | } |
| 2638 | mVelocityTracker.addMovement(e); |
| 2639 | |
| 2640 | final int action = e.getActionMasked(); |
| 2641 | final int actionIndex = e.getActionIndex(); |
| 2642 | |
| 2643 | switch (action) { |
| 2644 | case MotionEvent.ACTION_DOWN: |
| 2645 | if (mIgnoreMotionEventTillDown) { |
| 2646 | mIgnoreMotionEventTillDown = false; |
| 2647 | } |
| 2648 | mScrollPointerId = e.getPointerId(0); |
| 2649 | mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); |
| 2650 | mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); |
| 2651 | |
| 2652 | if (mScrollState == SCROLL_STATE_SETTLING) { |
| 2653 | getParent().requestDisallowInterceptTouchEvent(true); |
| 2654 | setScrollState(SCROLL_STATE_DRAGGING); |
| 2655 | } |
| 2656 | |
| 2657 | // Clear the nested offsets |
| 2658 | mNestedOffsets[0] = mNestedOffsets[1] = 0; |
| 2659 | |
| 2660 | int nestedScrollAxis = View.SCROLL_AXIS_NONE; |
| 2661 | if (canScrollHorizontally) { |
| 2662 | nestedScrollAxis |= View.SCROLL_AXIS_HORIZONTAL; |
| 2663 | } |
| 2664 | if (canScrollVertically) { |
| 2665 | nestedScrollAxis |= View.SCROLL_AXIS_VERTICAL; |
| 2666 | } |
| 2667 | startNestedScroll(nestedScrollAxis); |
| 2668 | break; |
| 2669 | |
| 2670 | case MotionEvent.ACTION_POINTER_DOWN: |
| 2671 | mScrollPointerId = e.getPointerId(actionIndex); |
| 2672 | mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); |
| 2673 | mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); |
| 2674 | break; |
| 2675 | |
| 2676 | case MotionEvent.ACTION_MOVE: { |
| 2677 | final int index = e.findPointerIndex(mScrollPointerId); |
| 2678 | if (index < 0) { |
| 2679 | Log.e(TAG, "Error processing scroll; pointer index for id " |
| 2680 | + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); |
| 2681 | return false; |
| 2682 | } |
| 2683 | |
| 2684 | final int x = (int) (e.getX(index) + 0.5f); |
| 2685 | final int y = (int) (e.getY(index) + 0.5f); |
| 2686 | if (mScrollState != SCROLL_STATE_DRAGGING) { |
| 2687 | final int dx = x - mInitialTouchX; |
| 2688 | final int dy = y - mInitialTouchY; |
| 2689 | boolean startScroll = false; |
| 2690 | if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { |
| 2691 | mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); |
| 2692 | startScroll = true; |
| 2693 | } |
| 2694 | if (canScrollVertically && Math.abs(dy) > mTouchSlop) { |
| 2695 | mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); |
| 2696 | startScroll = true; |
| 2697 | } |
| 2698 | if (startScroll) { |
| 2699 | setScrollState(SCROLL_STATE_DRAGGING); |
| 2700 | } |
| 2701 | } |
| 2702 | } break; |
| 2703 | |
| 2704 | case MotionEvent.ACTION_POINTER_UP: { |
| 2705 | onPointerUp(e); |
| 2706 | } break; |
| 2707 | |
| 2708 | case MotionEvent.ACTION_UP: { |
| 2709 | mVelocityTracker.clear(); |
| 2710 | stopNestedScroll(); |
| 2711 | } break; |
| 2712 | |
| 2713 | case MotionEvent.ACTION_CANCEL: { |
| 2714 | cancelTouch(); |
| 2715 | } |
| 2716 | } |
| 2717 | return mScrollState == SCROLL_STATE_DRAGGING; |
| 2718 | } |
| 2719 | |
| 2720 | @Override |
| 2721 | public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { |
| 2722 | final int listenerCount = mOnItemTouchListeners.size(); |
| 2723 | for (int i = 0; i < listenerCount; i++) { |
| 2724 | final OnItemTouchListener listener = mOnItemTouchListeners.get(i); |
| 2725 | listener.onRequestDisallowInterceptTouchEvent(disallowIntercept); |
| 2726 | } |
| 2727 | super.requestDisallowInterceptTouchEvent(disallowIntercept); |
| 2728 | } |
| 2729 | |
| 2730 | @Override |
| 2731 | public boolean onTouchEvent(MotionEvent e) { |
| 2732 | if (mLayoutFrozen || mIgnoreMotionEventTillDown) { |
| 2733 | return false; |
| 2734 | } |
| 2735 | if (dispatchOnItemTouch(e)) { |
| 2736 | cancelTouch(); |
| 2737 | return true; |
| 2738 | } |
| 2739 | |
| 2740 | if (mLayout == null) { |
| 2741 | return false; |
| 2742 | } |
| 2743 | |
| 2744 | final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); |
| 2745 | final boolean canScrollVertically = mLayout.canScrollVertically(); |
| 2746 | |
| 2747 | if (mVelocityTracker == null) { |
| 2748 | mVelocityTracker = VelocityTracker.obtain(); |
| 2749 | } |
| 2750 | boolean eventAddedToVelocityTracker = false; |
| 2751 | |
| 2752 | final MotionEvent vtev = MotionEvent.obtain(e); |
| 2753 | final int action = e.getActionMasked(); |
| 2754 | final int actionIndex = e.getActionIndex(); |
| 2755 | |
| 2756 | if (action == MotionEvent.ACTION_DOWN) { |
| 2757 | mNestedOffsets[0] = mNestedOffsets[1] = 0; |
| 2758 | } |
| 2759 | vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]); |
| 2760 | |
| 2761 | switch (action) { |
| 2762 | case MotionEvent.ACTION_DOWN: { |
| 2763 | mScrollPointerId = e.getPointerId(0); |
| 2764 | mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); |
| 2765 | mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); |
| 2766 | |
| 2767 | int nestedScrollAxis = View.SCROLL_AXIS_NONE; |
| 2768 | if (canScrollHorizontally) { |
| 2769 | nestedScrollAxis |= View.SCROLL_AXIS_HORIZONTAL; |
| 2770 | } |
| 2771 | if (canScrollVertically) { |
| 2772 | nestedScrollAxis |= View.SCROLL_AXIS_VERTICAL; |
| 2773 | } |
| 2774 | startNestedScroll(nestedScrollAxis); |
| 2775 | } break; |
| 2776 | |
| 2777 | case MotionEvent.ACTION_POINTER_DOWN: { |
| 2778 | mScrollPointerId = e.getPointerId(actionIndex); |
| 2779 | mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); |
| 2780 | mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); |
| 2781 | } break; |
| 2782 | |
| 2783 | case MotionEvent.ACTION_MOVE: { |
| 2784 | final int index = e.findPointerIndex(mScrollPointerId); |
| 2785 | if (index < 0) { |
| 2786 | Log.e(TAG, "Error processing scroll; pointer index for id " |
| 2787 | + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); |
| 2788 | return false; |
| 2789 | } |
| 2790 | |
| 2791 | final int x = (int) (e.getX(index) + 0.5f); |
| 2792 | final int y = (int) (e.getY(index) + 0.5f); |
| 2793 | int dx = mLastTouchX - x; |
| 2794 | int dy = mLastTouchY - y; |
| 2795 | |
| 2796 | if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) { |
| 2797 | dx -= mScrollConsumed[0]; |
| 2798 | dy -= mScrollConsumed[1]; |
| 2799 | vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); |
| 2800 | // Updated the nested offsets |
| 2801 | mNestedOffsets[0] += mScrollOffset[0]; |
| 2802 | mNestedOffsets[1] += mScrollOffset[1]; |
| 2803 | } |
| 2804 | |
| 2805 | if (mScrollState != SCROLL_STATE_DRAGGING) { |
| 2806 | boolean startScroll = false; |
| 2807 | if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { |
| 2808 | if (dx > 0) { |
| 2809 | dx -= mTouchSlop; |
| 2810 | } else { |
| 2811 | dx += mTouchSlop; |
| 2812 | } |
| 2813 | startScroll = true; |
| 2814 | } |
| 2815 | if (canScrollVertically && Math.abs(dy) > mTouchSlop) { |
| 2816 | if (dy > 0) { |
| 2817 | dy -= mTouchSlop; |
| 2818 | } else { |
| 2819 | dy += mTouchSlop; |
| 2820 | } |
| 2821 | startScroll = true; |
| 2822 | } |
| 2823 | if (startScroll) { |
| 2824 | setScrollState(SCROLL_STATE_DRAGGING); |
| 2825 | } |
| 2826 | } |
| 2827 | |
| 2828 | if (mScrollState == SCROLL_STATE_DRAGGING) { |
| 2829 | mLastTouchX = x - mScrollOffset[0]; |
| 2830 | mLastTouchY = y - mScrollOffset[1]; |
| 2831 | |
| 2832 | if (scrollByInternal( |
| 2833 | canScrollHorizontally ? dx : 0, |
| 2834 | canScrollVertically ? dy : 0, |
| 2835 | vtev)) { |
| 2836 | getParent().requestDisallowInterceptTouchEvent(true); |
| 2837 | } |
| 2838 | if (mGapWorker != null && (dx != 0 || dy != 0)) { |
| 2839 | mGapWorker.postFromTraversal(this, dx, dy); |
| 2840 | } |
| 2841 | } |
| 2842 | } break; |
| 2843 | |
| 2844 | case MotionEvent.ACTION_POINTER_UP: { |
| 2845 | onPointerUp(e); |
| 2846 | } break; |
| 2847 | |
| 2848 | case MotionEvent.ACTION_UP: { |
| 2849 | mVelocityTracker.addMovement(vtev); |
| 2850 | eventAddedToVelocityTracker = true; |
| 2851 | mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); |
| 2852 | final float xvel = canScrollHorizontally |
| 2853 | ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0; |
| 2854 | final float yvel = canScrollVertically |
| 2855 | ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0; |
| 2856 | if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { |
| 2857 | setScrollState(SCROLL_STATE_IDLE); |
| 2858 | } |
| 2859 | resetTouch(); |
| 2860 | } break; |
| 2861 | |
| 2862 | case MotionEvent.ACTION_CANCEL: { |
| 2863 | cancelTouch(); |
| 2864 | } break; |
| 2865 | } |
| 2866 | |
| 2867 | if (!eventAddedToVelocityTracker) { |
| 2868 | mVelocityTracker.addMovement(vtev); |
| 2869 | } |
| 2870 | vtev.recycle(); |
| 2871 | |
| 2872 | return true; |
| 2873 | } |
| 2874 | |
| 2875 | private void resetTouch() { |
| 2876 | if (mVelocityTracker != null) { |
| 2877 | mVelocityTracker.clear(); |
| 2878 | } |
| 2879 | stopNestedScroll(); |
| 2880 | releaseGlows(); |
| 2881 | } |
| 2882 | |
| 2883 | private void cancelTouch() { |
| 2884 | resetTouch(); |
| 2885 | setScrollState(SCROLL_STATE_IDLE); |
| 2886 | } |
| 2887 | |
| 2888 | private void onPointerUp(MotionEvent e) { |
| 2889 | final int actionIndex = e.getActionIndex(); |
| 2890 | if (e.getPointerId(actionIndex) == mScrollPointerId) { |
| 2891 | // Pick a new pointer to pick up the slack. |
| 2892 | final int newIndex = actionIndex == 0 ? 1 : 0; |
| 2893 | mScrollPointerId = e.getPointerId(newIndex); |
| 2894 | mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f); |
| 2895 | mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f); |
| 2896 | } |
| 2897 | } |
| 2898 | |
| 2899 | // @Override |
| 2900 | public boolean onGenericMotionEvent(MotionEvent event) { |
| 2901 | if (mLayout == null) { |
| 2902 | return false; |
| 2903 | } |
| 2904 | if (mLayoutFrozen) { |
| 2905 | return false; |
| 2906 | } |
| 2907 | if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { |
| 2908 | if (event.getAction() == MotionEvent.ACTION_SCROLL) { |
| 2909 | final float vScroll, hScroll; |
| 2910 | if (mLayout.canScrollVertically()) { |
| 2911 | // Inverse the sign of the vertical scroll to align the scroll orientation |
| 2912 | // with AbsListView. |
| 2913 | vScroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); |
| 2914 | } else { |
| 2915 | vScroll = 0f; |
| 2916 | } |
| 2917 | if (mLayout.canScrollHorizontally()) { |
| 2918 | hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); |
| 2919 | } else { |
| 2920 | hScroll = 0f; |
| 2921 | } |
| 2922 | |
| 2923 | if (vScroll != 0 || hScroll != 0) { |
| 2924 | final float scrollFactor = getScrollFactor(); |
| 2925 | scrollByInternal((int) (hScroll * scrollFactor), |
| 2926 | (int) (vScroll * scrollFactor), event); |
| 2927 | } |
| 2928 | } |
| 2929 | } |
| 2930 | return false; |
| 2931 | } |
| 2932 | |
| 2933 | /** |
| 2934 | * Ported from View.getVerticalScrollFactor. |
| 2935 | */ |
| 2936 | private float getScrollFactor() { |
| 2937 | if (mScrollFactor == Float.MIN_VALUE) { |
| 2938 | TypedValue outValue = new TypedValue(); |
| 2939 | if (getContext().getTheme().resolveAttribute( |
| 2940 | android.R.attr.listPreferredItemHeight, outValue, true)) { |
| 2941 | mScrollFactor = outValue.getDimension( |
| 2942 | getContext().getResources().getDisplayMetrics()); |
| 2943 | } else { |
| 2944 | return 0; //listPreferredItemHeight is not defined, no generic scrolling |
| 2945 | } |
| 2946 | } |
| 2947 | return mScrollFactor; |
| 2948 | } |
| 2949 | |
| 2950 | @Override |
| 2951 | protected void onMeasure(int widthSpec, int heightSpec) { |
| 2952 | if (mLayout == null) { |
| 2953 | defaultOnMeasure(widthSpec, heightSpec); |
| 2954 | return; |
| 2955 | } |
| 2956 | if (mLayout.mAutoMeasure) { |
| 2957 | final int widthMode = MeasureSpec.getMode(widthSpec); |
| 2958 | final int heightMode = MeasureSpec.getMode(heightSpec); |
| 2959 | final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY |
| 2960 | && heightMode == MeasureSpec.EXACTLY; |
| 2961 | mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); |
| 2962 | if (skipMeasure || mAdapter == null) { |
| 2963 | return; |
| 2964 | } |
| 2965 | if (mState.mLayoutStep == State.STEP_START) { |
| 2966 | dispatchLayoutStep1(); |
| 2967 | } |
| 2968 | // set dimensions in 2nd step. Pre-layout should happen with old dimensions for |
| 2969 | // consistency |
| 2970 | mLayout.setMeasureSpecs(widthSpec, heightSpec); |
| 2971 | mState.mIsMeasuring = true; |
| 2972 | dispatchLayoutStep2(); |
| 2973 | |
| 2974 | // now we can get the width and height from the children. |
| 2975 | mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); |
| 2976 | |
| 2977 | // if RecyclerView has non-exact width and height and if there is at least one child |
| 2978 | // which also has non-exact width & height, we have to re-measure. |
| 2979 | if (mLayout.shouldMeasureTwice()) { |
| 2980 | mLayout.setMeasureSpecs( |
| 2981 | MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), |
| 2982 | MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); |
| 2983 | mState.mIsMeasuring = true; |
| 2984 | dispatchLayoutStep2(); |
| 2985 | // now we can get the width and height from the children. |
| 2986 | mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); |
| 2987 | } |
| 2988 | } else { |
| 2989 | if (mHasFixedSize) { |
| 2990 | mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); |
| 2991 | return; |
| 2992 | } |
| 2993 | // custom onMeasure |
| 2994 | if (mAdapterUpdateDuringMeasure) { |
| 2995 | eatRequestLayout(); |
| 2996 | onEnterLayoutOrScroll(); |
| 2997 | processAdapterUpdatesAndSetAnimationFlags(); |
| 2998 | onExitLayoutOrScroll(); |
| 2999 | |
| 3000 | if (mState.mRunPredictiveAnimations) { |
| 3001 | mState.mInPreLayout = true; |
| 3002 | } else { |
| 3003 | // consume remaining updates to provide a consistent state with the layout pass. |
| 3004 | mAdapterHelper.consumeUpdatesInOnePass(); |
| 3005 | mState.mInPreLayout = false; |
| 3006 | } |
| 3007 | mAdapterUpdateDuringMeasure = false; |
| 3008 | resumeRequestLayout(false); |
| 3009 | } |
| 3010 | |
| 3011 | if (mAdapter != null) { |
| 3012 | mState.mItemCount = mAdapter.getItemCount(); |
| 3013 | } else { |
| 3014 | mState.mItemCount = 0; |
| 3015 | } |
| 3016 | eatRequestLayout(); |
| 3017 | mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); |
| 3018 | resumeRequestLayout(false); |
| 3019 | mState.mInPreLayout = false; // clear |
| 3020 | } |
| 3021 | } |
| 3022 | |
| 3023 | /** |
| 3024 | * Used when onMeasure is called before layout manager is set |
| 3025 | */ |
| 3026 | void defaultOnMeasure(int widthSpec, int heightSpec) { |
| 3027 | // calling LayoutManager here is not pretty but that API is already public and it is better |
| 3028 | // than creating another method since this is internal. |
| 3029 | final int width = LayoutManager.chooseSize(widthSpec, |
| 3030 | getPaddingLeft() + getPaddingRight(), |
| 3031 | getMinimumWidth()); |
| 3032 | final int height = LayoutManager.chooseSize(heightSpec, |
| 3033 | getPaddingTop() + getPaddingBottom(), |
| 3034 | getMinimumHeight()); |
| 3035 | |
| 3036 | setMeasuredDimension(width, height); |
| 3037 | } |
| 3038 | |
| 3039 | @Override |
| 3040 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| 3041 | super.onSizeChanged(w, h, oldw, oldh); |
| 3042 | if (w != oldw || h != oldh) { |
| 3043 | invalidateGlows(); |
| 3044 | // layout's w/h are updated during measure/layout steps. |
| 3045 | } |
| 3046 | } |
| 3047 | |
| 3048 | /** |
| 3049 | * Sets the {@link ItemAnimator} that will handle animations involving changes |
| 3050 | * to the items in this RecyclerView. By default, RecyclerView instantiates and |
| 3051 | * uses an instance of {@link DefaultItemAnimator}. Whether item animations are |
| 3052 | * enabled for the RecyclerView depends on the ItemAnimator and whether |
| 3053 | * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations() |
| 3054 | * supports item animations}. |
| 3055 | * |
| 3056 | * @param animator The ItemAnimator being set. If null, no animations will occur |
| 3057 | * when changes occur to the items in this RecyclerView. |
| 3058 | */ |
| 3059 | public void setItemAnimator(ItemAnimator animator) { |
| 3060 | if (mItemAnimator != null) { |
| 3061 | mItemAnimator.endAnimations(); |
| 3062 | mItemAnimator.setListener(null); |
| 3063 | } |
| 3064 | mItemAnimator = animator; |
| 3065 | if (mItemAnimator != null) { |
| 3066 | mItemAnimator.setListener(mItemAnimatorListener); |
| 3067 | } |
| 3068 | } |
| 3069 | |
| 3070 | void onEnterLayoutOrScroll() { |
| 3071 | mLayoutOrScrollCounter++; |
| 3072 | } |
| 3073 | |
| 3074 | void onExitLayoutOrScroll() { |
| 3075 | mLayoutOrScrollCounter--; |
| 3076 | if (mLayoutOrScrollCounter < 1) { |
| 3077 | if (DEBUG && mLayoutOrScrollCounter < 0) { |
| 3078 | throw new IllegalStateException("layout or scroll counter cannot go below zero." |
| 3079 | + "Some calls are not matching"); |
| 3080 | } |
| 3081 | mLayoutOrScrollCounter = 0; |
| 3082 | dispatchContentChangedIfNecessary(); |
| 3083 | dispatchPendingImportantForAccessibilityChanges(); |
| 3084 | } |
| 3085 | } |
| 3086 | |
| 3087 | boolean isAccessibilityEnabled() { |
| 3088 | return mAccessibilityManager != null && mAccessibilityManager.isEnabled(); |
| 3089 | } |
| 3090 | |
| 3091 | private void dispatchContentChangedIfNecessary() { |
| 3092 | final int flags = mEatenAccessibilityChangeFlags; |
| 3093 | mEatenAccessibilityChangeFlags = 0; |
| 3094 | if (flags != 0 && isAccessibilityEnabled()) { |
| 3095 | final AccessibilityEvent event = AccessibilityEvent.obtain(); |
| 3096 | event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); |
| 3097 | event.setContentChangeTypes(flags); |
| 3098 | sendAccessibilityEventUnchecked(event); |
| 3099 | } |
| 3100 | } |
| 3101 | |
| 3102 | /** |
| 3103 | * Returns whether RecyclerView is currently computing a layout. |
| 3104 | * <p> |
| 3105 | * If this method returns true, it means that RecyclerView is in a lockdown state and any |
| 3106 | * attempt to update adapter contents will result in an exception because adapter contents |
| 3107 | * cannot be changed while RecyclerView is trying to compute the layout. |
| 3108 | * <p> |
| 3109 | * It is very unlikely that your code will be running during this state as it is |
| 3110 | * called by the framework when a layout traversal happens or RecyclerView starts to scroll |
| 3111 | * in response to system events (touch, accessibility etc). |
| 3112 | * <p> |
| 3113 | * This case may happen if you have some custom logic to change adapter contents in |
| 3114 | * response to a View callback (e.g. focus change callback) which might be triggered during a |
| 3115 | * layout calculation. In these cases, you should just postpone the change using a Handler or a |
| 3116 | * similar mechanism. |
| 3117 | * |
| 3118 | * @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code> |
| 3119 | * otherwise |
| 3120 | */ |
| 3121 | public boolean isComputingLayout() { |
| 3122 | return mLayoutOrScrollCounter > 0; |
| 3123 | } |
| 3124 | |
| 3125 | /** |
| 3126 | * Returns true if an accessibility event should not be dispatched now. This happens when an |
| 3127 | * accessibility request arrives while RecyclerView does not have a stable state which is very |
| 3128 | * hard to handle for a LayoutManager. Instead, this method records necessary information about |
| 3129 | * the event and dispatches a window change event after the critical section is finished. |
| 3130 | * |
| 3131 | * @return True if the accessibility event should be postponed. |
| 3132 | */ |
| 3133 | boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) { |
| 3134 | if (isComputingLayout()) { |
| 3135 | int type = 0; |
| 3136 | if (event != null) { |
| 3137 | type = event.getContentChangeTypes(); |
| 3138 | } |
| 3139 | if (type == 0) { |
| 3140 | type = AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; |
| 3141 | } |
| 3142 | mEatenAccessibilityChangeFlags |= type; |
| 3143 | return true; |
| 3144 | } |
| 3145 | return false; |
| 3146 | } |
| 3147 | |
| 3148 | @Override |
| 3149 | public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { |
| 3150 | if (shouldDeferAccessibilityEvent(event)) { |
| 3151 | return; |
| 3152 | } |
| 3153 | super.sendAccessibilityEventUnchecked(event); |
| 3154 | } |
| 3155 | |
| 3156 | /** |
| 3157 | * Gets the current ItemAnimator for this RecyclerView. A null return value |
| 3158 | * indicates that there is no animator and that item changes will happen without |
| 3159 | * any animations. By default, RecyclerView instantiates and |
| 3160 | * uses an instance of {@link DefaultItemAnimator}. |
| 3161 | * |
| 3162 | * @return ItemAnimator The current ItemAnimator. If null, no animations will occur |
| 3163 | * when changes occur to the items in this RecyclerView. |
| 3164 | */ |
| 3165 | public ItemAnimator getItemAnimator() { |
| 3166 | return mItemAnimator; |
| 3167 | } |
| 3168 | |
| 3169 | /** |
| 3170 | * Post a runnable to the next frame to run pending item animations. Only the first such |
| 3171 | * request will be posted, governed by the mPostedAnimatorRunner flag. |
| 3172 | */ |
| 3173 | void postAnimationRunner() { |
| 3174 | if (!mPostedAnimatorRunner && mIsAttached) { |
| 3175 | postOnAnimation(mItemAnimatorRunner); |
| 3176 | mPostedAnimatorRunner = true; |
| 3177 | } |
| 3178 | } |
| 3179 | |
| 3180 | private boolean predictiveItemAnimationsEnabled() { |
| 3181 | return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()); |
| 3182 | } |
| 3183 | |
| 3184 | /** |
| 3185 | * Consumes adapter updates and calculates which type of animations we want to run. |
| 3186 | * Called in onMeasure and dispatchLayout. |
| 3187 | * <p> |
| 3188 | * This method may process only the pre-layout state of updates or all of them. |
| 3189 | */ |
| 3190 | private void processAdapterUpdatesAndSetAnimationFlags() { |
| 3191 | if (mDataSetHasChangedAfterLayout) { |
| 3192 | // Processing these items have no value since data set changed unexpectedly. |
| 3193 | // Instead, we just reset it. |
| 3194 | mAdapterHelper.reset(); |
| 3195 | mLayout.onItemsChanged(this); |
| 3196 | } |
| 3197 | // simple animations are a subset of advanced animations (which will cause a |
| 3198 | // pre-layout step) |
| 3199 | // If layout supports predictive animations, pre-process to decide if we want to run them |
| 3200 | if (predictiveItemAnimationsEnabled()) { |
| 3201 | mAdapterHelper.preProcess(); |
| 3202 | } else { |
| 3203 | mAdapterHelper.consumeUpdatesInOnePass(); |
| 3204 | } |
| 3205 | boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; |
| 3206 | mState.mRunSimpleAnimations = mFirstLayoutComplete |
| 3207 | && mItemAnimator != null |
| 3208 | && (mDataSetHasChangedAfterLayout |
| 3209 | || animationTypeSupported |
| 3210 | || mLayout.mRequestedSimpleAnimations) |
| 3211 | && (!mDataSetHasChangedAfterLayout |
| 3212 | || mAdapter.hasStableIds()); |
| 3213 | mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations |
| 3214 | && animationTypeSupported |
| 3215 | && !mDataSetHasChangedAfterLayout |
| 3216 | && predictiveItemAnimationsEnabled(); |
| 3217 | } |
| 3218 | |
| 3219 | /** |
| 3220 | * Wrapper around layoutChildren() that handles animating changes caused by layout. |
| 3221 | * Animations work on the assumption that there are five different kinds of items |
| 3222 | * in play: |
| 3223 | * PERSISTENT: items are visible before and after layout |
| 3224 | * REMOVED: items were visible before layout and were removed by the app |
| 3225 | * ADDED: items did not exist before layout and were added by the app |
| 3226 | * DISAPPEARING: items exist in the data set before/after, but changed from |
| 3227 | * visible to non-visible in the process of layout (they were moved off |
| 3228 | * screen as a side-effect of other changes) |
| 3229 | * APPEARING: items exist in the data set before/after, but changed from |
| 3230 | * non-visible to visible in the process of layout (they were moved on |
| 3231 | * screen as a side-effect of other changes) |
| 3232 | * The overall approach figures out what items exist before/after layout and |
| 3233 | * infers one of the five above states for each of the items. Then the animations |
| 3234 | * are set up accordingly: |
| 3235 | * PERSISTENT views are animated via |
| 3236 | * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)} |
| 3237 | * DISAPPEARING views are animated via |
| 3238 | * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} |
| 3239 | * APPEARING views are animated via |
| 3240 | * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} |
| 3241 | * and changed views are animated via |
| 3242 | * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}. |
| 3243 | */ |
| 3244 | void dispatchLayout() { |
| 3245 | if (mAdapter == null) { |
| 3246 | Log.e(TAG, "No adapter attached; skipping layout"); |
| 3247 | // leave the state in START |
| 3248 | return; |
| 3249 | } |
| 3250 | if (mLayout == null) { |
| 3251 | Log.e(TAG, "No layout manager attached; skipping layout"); |
| 3252 | // leave the state in START |
| 3253 | return; |
| 3254 | } |
| 3255 | mState.mIsMeasuring = false; |
| 3256 | if (mState.mLayoutStep == State.STEP_START) { |
| 3257 | dispatchLayoutStep1(); |
| 3258 | mLayout.setExactMeasureSpecsFrom(this); |
| 3259 | dispatchLayoutStep2(); |
| 3260 | } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() |
| 3261 | || mLayout.getHeight() != getHeight()) { |
| 3262 | // First 2 steps are done in onMeasure but looks like we have to run again due to |
| 3263 | // changed size. |
| 3264 | mLayout.setExactMeasureSpecsFrom(this); |
| 3265 | dispatchLayoutStep2(); |
| 3266 | } else { |
| 3267 | // always make sure we sync them (to ensure mode is exact) |
| 3268 | mLayout.setExactMeasureSpecsFrom(this); |
| 3269 | } |
| 3270 | dispatchLayoutStep3(); |
| 3271 | } |
| 3272 | |
| 3273 | private void saveFocusInfo() { |
| 3274 | View child = null; |
| 3275 | if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) { |
| 3276 | child = getFocusedChild(); |
| 3277 | } |
| 3278 | |
| 3279 | final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child); |
| 3280 | if (focusedVh == null) { |
| 3281 | resetFocusInfo(); |
| 3282 | } else { |
| 3283 | mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID; |
| 3284 | // mFocusedItemPosition should hold the current adapter position of the previously |
| 3285 | // focused item. If the item is removed, we store the previous adapter position of the |
| 3286 | // removed item. |
| 3287 | mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION |
| 3288 | : (focusedVh.isRemoved() ? focusedVh.mOldPosition |
| 3289 | : focusedVh.getAdapterPosition()); |
| 3290 | mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView); |
| 3291 | } |
| 3292 | } |
| 3293 | |
| 3294 | private void resetFocusInfo() { |
| 3295 | mState.mFocusedItemId = NO_ID; |
| 3296 | mState.mFocusedItemPosition = NO_POSITION; |
| 3297 | mState.mFocusedSubChildId = View.NO_ID; |
| 3298 | } |
| 3299 | |
| 3300 | /** |
| 3301 | * Finds the best view candidate to request focus on using mFocusedItemPosition index of the |
| 3302 | * previously focused item. It first traverses the adapter forward to find a focusable candidate |
| 3303 | * and if no such candidate is found, it reverses the focus search direction for the items |
| 3304 | * before the mFocusedItemPosition'th index; |
| 3305 | * @return The best candidate to request focus on, or null if no such candidate exists. Null |
| 3306 | * indicates all the existing adapter items are unfocusable. |
| 3307 | */ |
| 3308 | @Nullable |
| 3309 | private View findNextViewToFocus() { |
| 3310 | int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition |
| 3311 | : 0; |
| 3312 | ViewHolder nextFocus; |
| 3313 | final int itemCount = mState.getItemCount(); |
| 3314 | for (int i = startFocusSearchIndex; i < itemCount; i++) { |
| 3315 | nextFocus = findViewHolderForAdapterPosition(i); |
| 3316 | if (nextFocus == null) { |
| 3317 | break; |
| 3318 | } |
| 3319 | if (nextFocus.itemView.hasFocusable()) { |
| 3320 | return nextFocus.itemView; |
| 3321 | } |
| 3322 | } |
| 3323 | final int limit = Math.min(itemCount, startFocusSearchIndex); |
| 3324 | for (int i = limit - 1; i >= 0; i--) { |
| 3325 | nextFocus = findViewHolderForAdapterPosition(i); |
| 3326 | if (nextFocus == null) { |
| 3327 | return null; |
| 3328 | } |
| 3329 | if (nextFocus.itemView.hasFocusable()) { |
| 3330 | return nextFocus.itemView; |
| 3331 | } |
| 3332 | } |
| 3333 | return null; |
| 3334 | } |
| 3335 | |
| 3336 | private void recoverFocusFromState() { |
| 3337 | if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus() |
| 3338 | || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS |
| 3339 | || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) { |
| 3340 | // No-op if either of these cases happens: |
| 3341 | // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus |
| 3342 | // before its children and is focused (i.e. it already stole the focus away from its |
| 3343 | // descendants). |
| 3344 | return; |
| 3345 | } |
| 3346 | // only recover focus if RV itself has the focus or the focused view is hidden |
| 3347 | if (!isFocused()) { |
| 3348 | final View focusedChild = getFocusedChild(); |
| 3349 | if (IGNORE_DETACHED_FOCUSED_CHILD |
| 3350 | && (focusedChild.getParent() == null || !focusedChild.hasFocus())) { |
| 3351 | // Special handling of API 15-. A focused child can be invalid because mFocus is not |
| 3352 | // cleared when the child is detached (mParent = null), |
| 3353 | // This happens because clearFocus on API 15- does not invalidate mFocus of its |
| 3354 | // parent when this child is detached. |
| 3355 | // For API 16+, this is not an issue because requestFocus takes care of clearing the |
| 3356 | // prior detached focused child. For API 15- the problem happens in 2 cases because |
| 3357 | // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called |
| 3358 | // for the current focused item which calls clearChild or 2. when the prior focused |
| 3359 | // child is removed, removeDetachedView called in layout step 3 which calls |
| 3360 | // clearChild. We should ignore this invalid focused child in all our calculations |
| 3361 | // for the next view to receive focus, and apply the focus recovery logic instead. |
| 3362 | if (mChildHelper.getChildCount() == 0) { |
| 3363 | // No children left. Request focus on the RV itself since one of its children |
| 3364 | // was holding focus previously. |
| 3365 | requestFocus(); |
| 3366 | return; |
| 3367 | } |
| 3368 | } else if (!mChildHelper.isHidden(focusedChild)) { |
| 3369 | // If the currently focused child is hidden, apply the focus recovery logic. |
| 3370 | // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/. |
| 3371 | return; |
| 3372 | } |
| 3373 | } |
| 3374 | ViewHolder focusTarget = null; |
| 3375 | // RV first attempts to locate the previously focused item to request focus on using |
| 3376 | // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to |
| 3377 | // find the next best candidate to request focus on based on mFocusedItemPosition. |
| 3378 | if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) { |
| 3379 | focusTarget = findViewHolderForItemId(mState.mFocusedItemId); |
| 3380 | } |
| 3381 | View viewToFocus = null; |
| 3382 | if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView) |
| 3383 | || !focusTarget.itemView.hasFocusable()) { |
| 3384 | if (mChildHelper.getChildCount() > 0) { |
| 3385 | // At this point, RV has focus and either of these conditions are true: |
| 3386 | // 1. There's no previously focused item either because RV received focused before |
| 3387 | // layout, or the previously focused item was removed, or RV doesn't have stable IDs |
| 3388 | // 2. Previous focus child is hidden, or 3. Previous focused child is no longer |
| 3389 | // focusable. In either of these cases, we make sure that RV still passes down the |
| 3390 | // focus to one of its focusable children using a best-effort algorithm. |
| 3391 | viewToFocus = findNextViewToFocus(); |
| 3392 | } |
| 3393 | } else { |
| 3394 | // looks like the focused item has been replaced with another view that represents the |
| 3395 | // same item in the adapter. Request focus on that. |
| 3396 | viewToFocus = focusTarget.itemView; |
| 3397 | } |
| 3398 | |
| 3399 | if (viewToFocus != null) { |
| 3400 | if (mState.mFocusedSubChildId != NO_ID) { |
| 3401 | View child = viewToFocus.findViewById(mState.mFocusedSubChildId); |
| 3402 | if (child != null && child.isFocusable()) { |
| 3403 | viewToFocus = child; |
| 3404 | } |
| 3405 | } |
| 3406 | viewToFocus.requestFocus(); |
| 3407 | } |
| 3408 | } |
| 3409 | |
| 3410 | private int getDeepestFocusedViewWithId(View view) { |
| 3411 | int lastKnownId = view.getId(); |
| 3412 | while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) { |
| 3413 | view = ((ViewGroup) view).getFocusedChild(); |
| 3414 | final int id = view.getId(); |
| 3415 | if (id != View.NO_ID) { |
| 3416 | lastKnownId = view.getId(); |
| 3417 | } |
| 3418 | } |
| 3419 | return lastKnownId; |
| 3420 | } |
| 3421 | |
| 3422 | /** |
| 3423 | * The first step of a layout where we; |
| 3424 | * - process adapter updates |
| 3425 | * - decide which animation should run |
| 3426 | * - save information about current views |
| 3427 | * - If necessary, run predictive layout and save its information |
| 3428 | */ |
| 3429 | private void dispatchLayoutStep1() { |
| 3430 | mState.assertLayoutStep(State.STEP_START); |
| 3431 | mState.mIsMeasuring = false; |
| 3432 | eatRequestLayout(); |
| 3433 | mViewInfoStore.clear(); |
| 3434 | onEnterLayoutOrScroll(); |
| 3435 | processAdapterUpdatesAndSetAnimationFlags(); |
| 3436 | saveFocusInfo(); |
| 3437 | mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; |
| 3438 | mItemsAddedOrRemoved = mItemsChanged = false; |
| 3439 | mState.mInPreLayout = mState.mRunPredictiveAnimations; |
| 3440 | mState.mItemCount = mAdapter.getItemCount(); |
| 3441 | findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); |
| 3442 | |
| 3443 | if (mState.mRunSimpleAnimations) { |
| 3444 | // Step 0: Find out where all non-removed items are, pre-layout |
| 3445 | int count = mChildHelper.getChildCount(); |
| 3446 | for (int i = 0; i < count; ++i) { |
| 3447 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); |
| 3448 | if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) { |
| 3449 | continue; |
| 3450 | } |
| 3451 | final ItemHolderInfo animationInfo = mItemAnimator |
| 3452 | .recordPreLayoutInformation(mState, holder, |
| 3453 | ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), |
| 3454 | holder.getUnmodifiedPayloads()); |
| 3455 | mViewInfoStore.addToPreLayout(holder, animationInfo); |
| 3456 | if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved() |
| 3457 | && !holder.shouldIgnore() && !holder.isInvalid()) { |
| 3458 | long key = getChangedHolderKey(holder); |
| 3459 | // This is NOT the only place where a ViewHolder is added to old change holders |
| 3460 | // list. There is another case where: |
| 3461 | // * A VH is currently hidden but not deleted |
| 3462 | // * The hidden item is changed in the adapter |
| 3463 | // * Layout manager decides to layout the item in the pre-Layout pass (step1) |
| 3464 | // When this case is detected, RV will un-hide that view and add to the old |
| 3465 | // change holders list. |
| 3466 | mViewInfoStore.addToOldChangeHolders(key, holder); |
| 3467 | } |
| 3468 | } |
| 3469 | } |
| 3470 | if (mState.mRunPredictiveAnimations) { |
| 3471 | // Step 1: run prelayout: This will use the old positions of items. The layout manager |
| 3472 | // is expected to layout everything, even removed items (though not to add removed |
| 3473 | // items back to the container). This gives the pre-layout position of APPEARING views |
| 3474 | // which come into existence as part of the real layout. |
| 3475 | |
| 3476 | // Save old positions so that LayoutManager can run its mapping logic. |
| 3477 | saveOldPositions(); |
| 3478 | final boolean didStructureChange = mState.mStructureChanged; |
| 3479 | mState.mStructureChanged = false; |
| 3480 | // temporarily disable flag because we are asking for previous layout |
| 3481 | mLayout.onLayoutChildren(mRecycler, mState); |
| 3482 | mState.mStructureChanged = didStructureChange; |
| 3483 | |
| 3484 | for (int i = 0; i < mChildHelper.getChildCount(); ++i) { |
| 3485 | final View child = mChildHelper.getChildAt(i); |
| 3486 | final ViewHolder viewHolder = getChildViewHolderInt(child); |
| 3487 | if (viewHolder.shouldIgnore()) { |
| 3488 | continue; |
| 3489 | } |
| 3490 | if (!mViewInfoStore.isInPreLayout(viewHolder)) { |
| 3491 | int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder); |
| 3492 | boolean wasHidden = viewHolder |
| 3493 | .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); |
| 3494 | if (!wasHidden) { |
| 3495 | flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; |
| 3496 | } |
| 3497 | final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( |
| 3498 | mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); |
| 3499 | if (wasHidden) { |
| 3500 | recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo); |
| 3501 | } else { |
| 3502 | mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo); |
| 3503 | } |
| 3504 | } |
| 3505 | } |
| 3506 | // we don't process disappearing list because they may re-appear in post layout pass. |
| 3507 | clearOldPositions(); |
| 3508 | } else { |
| 3509 | clearOldPositions(); |
| 3510 | } |
| 3511 | onExitLayoutOrScroll(); |
| 3512 | resumeRequestLayout(false); |
| 3513 | mState.mLayoutStep = State.STEP_LAYOUT; |
| 3514 | } |
| 3515 | |
| 3516 | /** |
| 3517 | * The second layout step where we do the actual layout of the views for the final state. |
| 3518 | * This step might be run multiple times if necessary (e.g. measure). |
| 3519 | */ |
| 3520 | private void dispatchLayoutStep2() { |
| 3521 | eatRequestLayout(); |
| 3522 | onEnterLayoutOrScroll(); |
| 3523 | mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); |
| 3524 | mAdapterHelper.consumeUpdatesInOnePass(); |
| 3525 | mState.mItemCount = mAdapter.getItemCount(); |
| 3526 | mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; |
| 3527 | |
| 3528 | // Step 2: Run layout |
| 3529 | mState.mInPreLayout = false; |
| 3530 | mLayout.onLayoutChildren(mRecycler, mState); |
| 3531 | |
| 3532 | mState.mStructureChanged = false; |
| 3533 | mPendingSavedState = null; |
| 3534 | |
| 3535 | // onLayoutChildren may have caused client code to disable item animations; re-check |
| 3536 | mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null; |
| 3537 | mState.mLayoutStep = State.STEP_ANIMATIONS; |
| 3538 | onExitLayoutOrScroll(); |
| 3539 | resumeRequestLayout(false); |
| 3540 | } |
| 3541 | |
| 3542 | /** |
| 3543 | * The final step of the layout where we save the information about views for animations, |
| 3544 | * trigger animations and do any necessary cleanup. |
| 3545 | */ |
| 3546 | private void dispatchLayoutStep3() { |
| 3547 | mState.assertLayoutStep(State.STEP_ANIMATIONS); |
| 3548 | eatRequestLayout(); |
| 3549 | onEnterLayoutOrScroll(); |
| 3550 | mState.mLayoutStep = State.STEP_START; |
| 3551 | if (mState.mRunSimpleAnimations) { |
| 3552 | // Step 3: Find out where things are now, and process change animations. |
| 3553 | // traverse list in reverse because we may call animateChange in the loop which may |
| 3554 | // remove the target view holder. |
| 3555 | for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { |
| 3556 | ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); |
| 3557 | if (holder.shouldIgnore()) { |
| 3558 | continue; |
| 3559 | } |
| 3560 | long key = getChangedHolderKey(holder); |
| 3561 | final ItemHolderInfo animationInfo = mItemAnimator |
| 3562 | .recordPostLayoutInformation(mState, holder); |
| 3563 | ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key); |
| 3564 | if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) { |
| 3565 | // run a change animation |
| 3566 | |
| 3567 | // If an Item is CHANGED but the updated version is disappearing, it creates |
| 3568 | // a conflicting case. |
| 3569 | // Since a view that is marked as disappearing is likely to be going out of |
| 3570 | // bounds, we run a change animation. Both views will be cleaned automatically |
| 3571 | // once their animations finish. |
| 3572 | // On the other hand, if it is the same view holder instance, we run a |
| 3573 | // disappearing animation instead because we are not going to rebind the updated |
| 3574 | // VH unless it is enforced by the layout manager. |
| 3575 | final boolean oldDisappearing = mViewInfoStore.isDisappearing( |
| 3576 | oldChangeViewHolder); |
| 3577 | final boolean newDisappearing = mViewInfoStore.isDisappearing(holder); |
| 3578 | if (oldDisappearing && oldChangeViewHolder == holder) { |
| 3579 | // run disappear animation instead of change |
| 3580 | mViewInfoStore.addToPostLayout(holder, animationInfo); |
| 3581 | } else { |
| 3582 | final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout( |
| 3583 | oldChangeViewHolder); |
| 3584 | // we add and remove so that any post info is merged. |
| 3585 | mViewInfoStore.addToPostLayout(holder, animationInfo); |
| 3586 | ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder); |
| 3587 | if (preInfo == null) { |
| 3588 | handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); |
| 3589 | } else { |
| 3590 | animateChange(oldChangeViewHolder, holder, preInfo, postInfo, |
| 3591 | oldDisappearing, newDisappearing); |
| 3592 | } |
| 3593 | } |
| 3594 | } else { |
| 3595 | mViewInfoStore.addToPostLayout(holder, animationInfo); |
| 3596 | } |
| 3597 | } |
| 3598 | |
| 3599 | // Step 4: Process view info lists and trigger animations |
| 3600 | mViewInfoStore.process(mViewInfoProcessCallback); |
| 3601 | } |
| 3602 | |
| 3603 | mLayout.removeAndRecycleScrapInt(mRecycler); |
| 3604 | mState.mPreviousLayoutItemCount = mState.mItemCount; |
| 3605 | mDataSetHasChangedAfterLayout = false; |
| 3606 | mState.mRunSimpleAnimations = false; |
| 3607 | |
| 3608 | mState.mRunPredictiveAnimations = false; |
| 3609 | mLayout.mRequestedSimpleAnimations = false; |
| 3610 | if (mRecycler.mChangedScrap != null) { |
| 3611 | mRecycler.mChangedScrap.clear(); |
| 3612 | } |
| 3613 | if (mLayout.mPrefetchMaxObservedInInitialPrefetch) { |
| 3614 | // Initial prefetch has expanded cache, so reset until next prefetch. |
| 3615 | // This prevents initial prefetches from expanding the cache permanently. |
| 3616 | mLayout.mPrefetchMaxCountObserved = 0; |
| 3617 | mLayout.mPrefetchMaxObservedInInitialPrefetch = false; |
| 3618 | mRecycler.updateViewCacheSize(); |
| 3619 | } |
| 3620 | |
| 3621 | mLayout.onLayoutCompleted(mState); |
| 3622 | onExitLayoutOrScroll(); |
| 3623 | resumeRequestLayout(false); |
| 3624 | mViewInfoStore.clear(); |
| 3625 | if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { |
| 3626 | dispatchOnScrolled(0, 0); |
| 3627 | } |
| 3628 | recoverFocusFromState(); |
| 3629 | resetFocusInfo(); |
| 3630 | } |
| 3631 | |
| 3632 | /** |
| 3633 | * This handles the case where there is an unexpected VH missing in the pre-layout map. |
| 3634 | * <p> |
| 3635 | * We might be able to detect the error in the application which will help the developer to |
| 3636 | * resolve the issue. |
| 3637 | * <p> |
| 3638 | * If it is not an expected error, we at least print an error to notify the developer and ignore |
| 3639 | * the animation. |
| 3640 | * |
| 3641 | * https://code.google.com/p/android/issues/detail?id=193958 |
| 3642 | * |
| 3643 | * @param key The change key |
| 3644 | * @param holder Current ViewHolder |
| 3645 | * @param oldChangeViewHolder Changed ViewHolder |
| 3646 | */ |
| 3647 | private void handleMissingPreInfoForChangeError(long key, |
| 3648 | ViewHolder holder, ViewHolder oldChangeViewHolder) { |
| 3649 | // check if two VH have the same key, if so, print that as an error |
| 3650 | final int childCount = mChildHelper.getChildCount(); |
| 3651 | for (int i = 0; i < childCount; i++) { |
| 3652 | View view = mChildHelper.getChildAt(i); |
| 3653 | ViewHolder other = getChildViewHolderInt(view); |
| 3654 | if (other == holder) { |
| 3655 | continue; |
| 3656 | } |
| 3657 | final long otherKey = getChangedHolderKey(other); |
| 3658 | if (otherKey == key) { |
| 3659 | if (mAdapter != null && mAdapter.hasStableIds()) { |
| 3660 | throw new IllegalStateException("Two different ViewHolders have the same stable" |
| 3661 | + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT" |
| 3662 | + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder); |
| 3663 | } else { |
| 3664 | throw new IllegalStateException("Two different ViewHolders have the same change" |
| 3665 | + " ID. This might happen due to inconsistent Adapter update events or" |
| 3666 | + " if the LayoutManager lays out the same View multiple times." |
| 3667 | + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder); |
| 3668 | } |
| 3669 | } |
| 3670 | } |
| 3671 | // Very unlikely to happen but if it does, notify the developer. |
| 3672 | Log.e(TAG, "Problem while matching changed view holders with the new" |
| 3673 | + "ones. The pre-layout information for the change holder " + oldChangeViewHolder |
| 3674 | + " cannot be found but it is necessary for " + holder); |
| 3675 | } |
| 3676 | |
| 3677 | /** |
| 3678 | * Records the animation information for a view holder that was bounced from hidden list. It |
| 3679 | * also clears the bounce back flag. |
| 3680 | */ |
| 3681 | void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, |
| 3682 | ItemHolderInfo animationInfo) { |
| 3683 | // looks like this view bounced back from hidden list! |
| 3684 | viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); |
| 3685 | if (mState.mTrackOldChangeHolders && viewHolder.isUpdated() |
| 3686 | && !viewHolder.isRemoved() && !viewHolder.shouldIgnore()) { |
| 3687 | long key = getChangedHolderKey(viewHolder); |
| 3688 | mViewInfoStore.addToOldChangeHolders(key, viewHolder); |
| 3689 | } |
| 3690 | mViewInfoStore.addToPreLayout(viewHolder, animationInfo); |
| 3691 | } |
| 3692 | |
| 3693 | private void findMinMaxChildLayoutPositions(int[] into) { |
| 3694 | final int count = mChildHelper.getChildCount(); |
| 3695 | if (count == 0) { |
| 3696 | into[0] = NO_POSITION; |
| 3697 | into[1] = NO_POSITION; |
| 3698 | return; |
| 3699 | } |
| 3700 | int minPositionPreLayout = Integer.MAX_VALUE; |
| 3701 | int maxPositionPreLayout = Integer.MIN_VALUE; |
| 3702 | for (int i = 0; i < count; ++i) { |
| 3703 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); |
| 3704 | if (holder.shouldIgnore()) { |
| 3705 | continue; |
| 3706 | } |
| 3707 | final int pos = holder.getLayoutPosition(); |
| 3708 | if (pos < minPositionPreLayout) { |
| 3709 | minPositionPreLayout = pos; |
| 3710 | } |
| 3711 | if (pos > maxPositionPreLayout) { |
| 3712 | maxPositionPreLayout = pos; |
| 3713 | } |
| 3714 | } |
| 3715 | into[0] = minPositionPreLayout; |
| 3716 | into[1] = maxPositionPreLayout; |
| 3717 | } |
| 3718 | |
| 3719 | private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) { |
| 3720 | findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); |
| 3721 | return mMinMaxLayoutPositions[0] != minPositionPreLayout |
| 3722 | || mMinMaxLayoutPositions[1] != maxPositionPreLayout; |
| 3723 | } |
| 3724 | |
| 3725 | @Override |
| 3726 | protected void removeDetachedView(View child, boolean animate) { |
| 3727 | ViewHolder vh = getChildViewHolderInt(child); |
| 3728 | if (vh != null) { |
| 3729 | if (vh.isTmpDetached()) { |
| 3730 | vh.clearTmpDetachFlag(); |
| 3731 | } else if (!vh.shouldIgnore()) { |
| 3732 | throw new IllegalArgumentException("Called removeDetachedView with a view which" |
| 3733 | + " is not flagged as tmp detached." + vh); |
| 3734 | } |
| 3735 | } |
| 3736 | dispatchChildDetached(child); |
| 3737 | super.removeDetachedView(child, animate); |
| 3738 | } |
| 3739 | |
| 3740 | /** |
| 3741 | * Returns a unique key to be used while handling change animations. |
| 3742 | * It might be child's position or stable id depending on the adapter type. |
| 3743 | */ |
| 3744 | long getChangedHolderKey(ViewHolder holder) { |
| 3745 | return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; |
| 3746 | } |
| 3747 | |
| 3748 | void animateAppearance(@NonNull ViewHolder itemHolder, |
| 3749 | @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { |
| 3750 | itemHolder.setIsRecyclable(false); |
| 3751 | if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { |
| 3752 | postAnimationRunner(); |
| 3753 | } |
| 3754 | } |
| 3755 | |
| 3756 | void animateDisappearance(@NonNull ViewHolder holder, |
| 3757 | @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { |
| 3758 | addAnimatingView(holder); |
| 3759 | holder.setIsRecyclable(false); |
| 3760 | if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) { |
| 3761 | postAnimationRunner(); |
| 3762 | } |
| 3763 | } |
| 3764 | |
| 3765 | private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, |
| 3766 | @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo, |
| 3767 | boolean oldHolderDisappearing, boolean newHolderDisappearing) { |
| 3768 | oldHolder.setIsRecyclable(false); |
| 3769 | if (oldHolderDisappearing) { |
| 3770 | addAnimatingView(oldHolder); |
| 3771 | } |
| 3772 | if (oldHolder != newHolder) { |
| 3773 | if (newHolderDisappearing) { |
| 3774 | addAnimatingView(newHolder); |
| 3775 | } |
| 3776 | oldHolder.mShadowedHolder = newHolder; |
| 3777 | // old holder should disappear after animation ends |
| 3778 | addAnimatingView(oldHolder); |
| 3779 | mRecycler.unscrapView(oldHolder); |
| 3780 | newHolder.setIsRecyclable(false); |
| 3781 | newHolder.mShadowingHolder = oldHolder; |
| 3782 | } |
| 3783 | if (mItemAnimator.animateChange(oldHolder, newHolder, preInfo, postInfo)) { |
| 3784 | postAnimationRunner(); |
| 3785 | } |
| 3786 | } |
| 3787 | |
| 3788 | @Override |
| 3789 | protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| 3790 | Trace.beginSection(TRACE_ON_LAYOUT_TAG); |
| 3791 | dispatchLayout(); |
| 3792 | Trace.endSection(); |
| 3793 | mFirstLayoutComplete = true; |
| 3794 | } |
| 3795 | |
| 3796 | @Override |
| 3797 | public void requestLayout() { |
| 3798 | if (mEatRequestLayout == 0 && !mLayoutFrozen) { |
| 3799 | super.requestLayout(); |
| 3800 | } else { |
| 3801 | mLayoutRequestEaten = true; |
| 3802 | } |
| 3803 | } |
| 3804 | |
| 3805 | void markItemDecorInsetsDirty() { |
| 3806 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 3807 | for (int i = 0; i < childCount; i++) { |
| 3808 | final View child = mChildHelper.getUnfilteredChildAt(i); |
| 3809 | ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; |
| 3810 | } |
| 3811 | mRecycler.markItemDecorInsetsDirty(); |
| 3812 | } |
| 3813 | |
| 3814 | @Override |
| 3815 | public void draw(Canvas c) { |
| 3816 | super.draw(c); |
| 3817 | |
| 3818 | final int count = mItemDecorations.size(); |
| 3819 | for (int i = 0; i < count; i++) { |
| 3820 | mItemDecorations.get(i).onDrawOver(c, this, mState); |
| 3821 | } |
| 3822 | // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we |
| 3823 | // need find children closest to edges. Not sure if it is worth the effort. |
| 3824 | boolean needsInvalidate = false; |
| 3825 | if (mLeftGlow != null && !mLeftGlow.isFinished()) { |
| 3826 | final int restore = c.save(); |
| 3827 | final int padding = mClipToPadding ? getPaddingBottom() : 0; |
| 3828 | c.rotate(270); |
| 3829 | c.translate(-getHeight() + padding, 0); |
| 3830 | needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); |
| 3831 | c.restoreToCount(restore); |
| 3832 | } |
| 3833 | if (mTopGlow != null && !mTopGlow.isFinished()) { |
| 3834 | final int restore = c.save(); |
| 3835 | if (mClipToPadding) { |
| 3836 | c.translate(getPaddingLeft(), getPaddingTop()); |
| 3837 | } |
| 3838 | needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); |
| 3839 | c.restoreToCount(restore); |
| 3840 | } |
| 3841 | if (mRightGlow != null && !mRightGlow.isFinished()) { |
| 3842 | final int restore = c.save(); |
| 3843 | final int width = getWidth(); |
| 3844 | final int padding = mClipToPadding ? getPaddingTop() : 0; |
| 3845 | c.rotate(90); |
| 3846 | c.translate(-padding, -width); |
| 3847 | needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); |
| 3848 | c.restoreToCount(restore); |
| 3849 | } |
| 3850 | if (mBottomGlow != null && !mBottomGlow.isFinished()) { |
| 3851 | final int restore = c.save(); |
| 3852 | c.rotate(180); |
| 3853 | if (mClipToPadding) { |
| 3854 | c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom()); |
| 3855 | } else { |
| 3856 | c.translate(-getWidth(), -getHeight()); |
| 3857 | } |
| 3858 | needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); |
| 3859 | c.restoreToCount(restore); |
| 3860 | } |
| 3861 | |
| 3862 | // If some views are animating, ItemDecorators are likely to move/change with them. |
| 3863 | // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's |
| 3864 | // display lists are not invalidated. |
| 3865 | if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 |
| 3866 | && mItemAnimator.isRunning()) { |
| 3867 | needsInvalidate = true; |
| 3868 | } |
| 3869 | |
| 3870 | if (needsInvalidate) { |
| 3871 | postInvalidateOnAnimation(); |
| 3872 | } |
| 3873 | } |
| 3874 | |
| 3875 | @Override |
| 3876 | public void onDraw(Canvas c) { |
| 3877 | super.onDraw(c); |
| 3878 | |
| 3879 | final int count = mItemDecorations.size(); |
| 3880 | for (int i = 0; i < count; i++) { |
| 3881 | mItemDecorations.get(i).onDraw(c, this, mState); |
| 3882 | } |
| 3883 | } |
| 3884 | |
| 3885 | @Override |
| 3886 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { |
| 3887 | return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p); |
| 3888 | } |
| 3889 | |
| 3890 | @Override |
| 3891 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() { |
| 3892 | if (mLayout == null) { |
| 3893 | throw new IllegalStateException("RecyclerView has no LayoutManager"); |
| 3894 | } |
| 3895 | return mLayout.generateDefaultLayoutParams(); |
| 3896 | } |
| 3897 | |
| 3898 | @Override |
| 3899 | public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { |
| 3900 | if (mLayout == null) { |
| 3901 | throw new IllegalStateException("RecyclerView has no LayoutManager"); |
| 3902 | } |
| 3903 | return mLayout.generateLayoutParams(getContext(), attrs); |
| 3904 | } |
| 3905 | |
| 3906 | @Override |
| 3907 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { |
| 3908 | if (mLayout == null) { |
| 3909 | throw new IllegalStateException("RecyclerView has no LayoutManager"); |
| 3910 | } |
| 3911 | return mLayout.generateLayoutParams(p); |
| 3912 | } |
| 3913 | |
| 3914 | /** |
| 3915 | * Returns true if RecyclerView is currently running some animations. |
| 3916 | * <p> |
| 3917 | * If you want to be notified when animations are finished, use |
| 3918 | * {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}. |
| 3919 | * |
| 3920 | * @return True if there are some item animations currently running or waiting to be started. |
| 3921 | */ |
| 3922 | public boolean isAnimating() { |
| 3923 | return mItemAnimator != null && mItemAnimator.isRunning(); |
| 3924 | } |
| 3925 | |
| 3926 | void saveOldPositions() { |
| 3927 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 3928 | for (int i = 0; i < childCount; i++) { |
| 3929 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 3930 | if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) { |
| 3931 | throw new IllegalStateException("view holder cannot have position -1 unless it" |
| 3932 | + " is removed"); |
| 3933 | } |
| 3934 | if (!holder.shouldIgnore()) { |
| 3935 | holder.saveOldPosition(); |
| 3936 | } |
| 3937 | } |
| 3938 | } |
| 3939 | |
| 3940 | void clearOldPositions() { |
| 3941 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 3942 | for (int i = 0; i < childCount; i++) { |
| 3943 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 3944 | if (!holder.shouldIgnore()) { |
| 3945 | holder.clearOldPosition(); |
| 3946 | } |
| 3947 | } |
| 3948 | mRecycler.clearOldPositions(); |
| 3949 | } |
| 3950 | |
| 3951 | void offsetPositionRecordsForMove(int from, int to) { |
| 3952 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 3953 | final int start, end, inBetweenOffset; |
| 3954 | if (from < to) { |
| 3955 | start = from; |
| 3956 | end = to; |
| 3957 | inBetweenOffset = -1; |
| 3958 | } else { |
| 3959 | start = to; |
| 3960 | end = from; |
| 3961 | inBetweenOffset = 1; |
| 3962 | } |
| 3963 | |
| 3964 | for (int i = 0; i < childCount; i++) { |
| 3965 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 3966 | if (holder == null || holder.mPosition < start || holder.mPosition > end) { |
| 3967 | continue; |
| 3968 | } |
| 3969 | if (DEBUG) { |
| 3970 | Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " |
| 3971 | + holder); |
| 3972 | } |
| 3973 | if (holder.mPosition == from) { |
| 3974 | holder.offsetPosition(to - from, false); |
| 3975 | } else { |
| 3976 | holder.offsetPosition(inBetweenOffset, false); |
| 3977 | } |
| 3978 | |
| 3979 | mState.mStructureChanged = true; |
| 3980 | } |
| 3981 | mRecycler.offsetPositionRecordsForMove(from, to); |
| 3982 | requestLayout(); |
| 3983 | } |
| 3984 | |
| 3985 | void offsetPositionRecordsForInsert(int positionStart, int itemCount) { |
| 3986 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 3987 | for (int i = 0; i < childCount; i++) { |
| 3988 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 3989 | if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) { |
| 3990 | if (DEBUG) { |
| 3991 | Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " |
| 3992 | + holder + " now at position " + (holder.mPosition + itemCount)); |
| 3993 | } |
| 3994 | holder.offsetPosition(itemCount, false); |
| 3995 | mState.mStructureChanged = true; |
| 3996 | } |
| 3997 | } |
| 3998 | mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount); |
| 3999 | requestLayout(); |
| 4000 | } |
| 4001 | |
| 4002 | void offsetPositionRecordsForRemove(int positionStart, int itemCount, |
| 4003 | boolean applyToPreLayout) { |
| 4004 | final int positionEnd = positionStart + itemCount; |
| 4005 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 4006 | for (int i = 0; i < childCount; i++) { |
| 4007 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 4008 | if (holder != null && !holder.shouldIgnore()) { |
| 4009 | if (holder.mPosition >= positionEnd) { |
| 4010 | if (DEBUG) { |
| 4011 | Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i |
| 4012 | + " holder " + holder + " now at position " |
| 4013 | + (holder.mPosition - itemCount)); |
| 4014 | } |
| 4015 | holder.offsetPosition(-itemCount, applyToPreLayout); |
| 4016 | mState.mStructureChanged = true; |
| 4017 | } else if (holder.mPosition >= positionStart) { |
| 4018 | if (DEBUG) { |
| 4019 | Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i |
| 4020 | + " holder " + holder + " now REMOVED"); |
| 4021 | } |
| 4022 | holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount, |
| 4023 | applyToPreLayout); |
| 4024 | mState.mStructureChanged = true; |
| 4025 | } |
| 4026 | } |
| 4027 | } |
| 4028 | mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout); |
| 4029 | requestLayout(); |
| 4030 | } |
| 4031 | |
| 4032 | /** |
| 4033 | * Rebind existing views for the given range, or create as needed. |
| 4034 | * |
| 4035 | * @param positionStart Adapter position to start at |
| 4036 | * @param itemCount Number of views that must explicitly be rebound |
| 4037 | */ |
| 4038 | void viewRangeUpdate(int positionStart, int itemCount, Object payload) { |
| 4039 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 4040 | final int positionEnd = positionStart + itemCount; |
| 4041 | |
| 4042 | for (int i = 0; i < childCount; i++) { |
| 4043 | final View child = mChildHelper.getUnfilteredChildAt(i); |
| 4044 | final ViewHolder holder = getChildViewHolderInt(child); |
| 4045 | if (holder == null || holder.shouldIgnore()) { |
| 4046 | continue; |
| 4047 | } |
| 4048 | if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { |
| 4049 | // We re-bind these view holders after pre-processing is complete so that |
| 4050 | // ViewHolders have their final positions assigned. |
| 4051 | holder.addFlags(ViewHolder.FLAG_UPDATE); |
| 4052 | holder.addChangePayload(payload); |
| 4053 | // lp cannot be null since we get ViewHolder from it. |
| 4054 | ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true; |
| 4055 | } |
| 4056 | } |
| 4057 | mRecycler.viewRangeUpdate(positionStart, itemCount); |
| 4058 | } |
| 4059 | |
| 4060 | boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { |
| 4061 | return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder, |
| 4062 | viewHolder.getUnmodifiedPayloads()); |
| 4063 | } |
| 4064 | |
| 4065 | |
| 4066 | /** |
| 4067 | * Call this method to signal that *all* adapter content has changed (generally, because of |
| 4068 | * swapAdapter, or notifyDataSetChanged), and that once layout occurs, all attached items should |
| 4069 | * be discarded or animated. Note that this work is deferred because RecyclerView requires a |
| 4070 | * layout to resolve non-incremental changes to the data set. |
| 4071 | * |
| 4072 | * Attached items are labeled as position unknown, and may no longer be cached. |
| 4073 | * |
| 4074 | * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true, |
| 4075 | * so calling this method *must* be associated with marking the cache invalid, so that the |
| 4076 | * only valid items that remain in the cache, once layout occurs, are prefetched items. |
| 4077 | */ |
| 4078 | void setDataSetChangedAfterLayout() { |
| 4079 | if (mDataSetHasChangedAfterLayout) { |
| 4080 | return; |
| 4081 | } |
| 4082 | mDataSetHasChangedAfterLayout = true; |
| 4083 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 4084 | for (int i = 0; i < childCount; i++) { |
| 4085 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 4086 | if (holder != null && !holder.shouldIgnore()) { |
| 4087 | holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); |
| 4088 | } |
| 4089 | } |
| 4090 | mRecycler.setAdapterPositionsAsUnknown(); |
| 4091 | |
| 4092 | // immediately mark all views as invalid, so prefetched views can be |
| 4093 | // differentiated from views bound to previous data set - both in children, and cache |
| 4094 | markKnownViewsInvalid(); |
| 4095 | } |
| 4096 | |
| 4097 | /** |
| 4098 | * Mark all known views as invalid. Used in response to a, "the whole world might have changed" |
| 4099 | * data change event. |
| 4100 | */ |
| 4101 | void markKnownViewsInvalid() { |
| 4102 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 4103 | for (int i = 0; i < childCount; i++) { |
| 4104 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 4105 | if (holder != null && !holder.shouldIgnore()) { |
| 4106 | holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); |
| 4107 | } |
| 4108 | } |
| 4109 | markItemDecorInsetsDirty(); |
| 4110 | mRecycler.markKnownViewsInvalid(); |
| 4111 | } |
| 4112 | |
| 4113 | /** |
| 4114 | * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method |
| 4115 | * will trigger a {@link #requestLayout()} call. |
| 4116 | */ |
| 4117 | public void invalidateItemDecorations() { |
| 4118 | if (mItemDecorations.size() == 0) { |
| 4119 | return; |
| 4120 | } |
| 4121 | if (mLayout != null) { |
| 4122 | mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll" |
| 4123 | + " or layout"); |
| 4124 | } |
| 4125 | markItemDecorInsetsDirty(); |
| 4126 | requestLayout(); |
| 4127 | } |
| 4128 | |
| 4129 | /** |
| 4130 | * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's |
| 4131 | * focus even if the View representing the Item is replaced during a layout calculation. |
| 4132 | * <p> |
| 4133 | * By default, this value is {@code true}. |
| 4134 | * |
| 4135 | * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses |
| 4136 | * focus. |
| 4137 | * |
| 4138 | * @see #setPreserveFocusAfterLayout(boolean) |
| 4139 | */ |
| 4140 | public boolean getPreserveFocusAfterLayout() { |
| 4141 | return mPreserveFocusAfterLayout; |
| 4142 | } |
| 4143 | |
| 4144 | /** |
| 4145 | * Set whether the RecyclerView should try to keep the same Item focused after a layout |
| 4146 | * calculation or not. |
| 4147 | * <p> |
| 4148 | * Usually, LayoutManagers keep focused views visible before and after layout but sometimes, |
| 4149 | * views may lose focus during a layout calculation as their state changes or they are replaced |
| 4150 | * with another view due to type change or animation. In these cases, RecyclerView can request |
| 4151 | * focus on the new view automatically. |
| 4152 | * |
| 4153 | * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a |
| 4154 | * layout calculations. Defaults to true. |
| 4155 | * |
| 4156 | * @see #getPreserveFocusAfterLayout() |
| 4157 | */ |
| 4158 | public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) { |
| 4159 | mPreserveFocusAfterLayout = preserveFocusAfterLayout; |
| 4160 | } |
| 4161 | |
| 4162 | /** |
| 4163 | * Retrieve the {@link ViewHolder} for the given child view. |
| 4164 | * |
| 4165 | * @param child Child of this RecyclerView to query for its ViewHolder |
| 4166 | * @return The child view's ViewHolder |
| 4167 | */ |
| 4168 | public ViewHolder getChildViewHolder(View child) { |
| 4169 | final ViewParent parent = child.getParent(); |
| 4170 | if (parent != null && parent != this) { |
| 4171 | throw new IllegalArgumentException("View " + child + " is not a direct child of " |
| 4172 | + this); |
| 4173 | } |
| 4174 | return getChildViewHolderInt(child); |
| 4175 | } |
| 4176 | |
| 4177 | /** |
| 4178 | * Traverses the ancestors of the given view and returns the item view that contains it and |
| 4179 | * also a direct child of the RecyclerView. This returned view can be used to get the |
| 4180 | * ViewHolder by calling {@link #getChildViewHolder(View)}. |
| 4181 | * |
| 4182 | * @param view The view that is a descendant of the RecyclerView. |
| 4183 | * |
| 4184 | * @return The direct child of the RecyclerView which contains the given view or null if the |
| 4185 | * provided view is not a descendant of this RecyclerView. |
| 4186 | * |
| 4187 | * @see #getChildViewHolder(View) |
| 4188 | * @see #findContainingViewHolder(View) |
| 4189 | */ |
| 4190 | @Nullable |
| 4191 | public View findContainingItemView(View view) { |
| 4192 | ViewParent parent = view.getParent(); |
| 4193 | while (parent != null && parent != this && parent instanceof View) { |
| 4194 | view = (View) parent; |
| 4195 | parent = view.getParent(); |
| 4196 | } |
| 4197 | return parent == this ? view : null; |
| 4198 | } |
| 4199 | |
| 4200 | /** |
| 4201 | * Returns the ViewHolder that contains the given view. |
| 4202 | * |
| 4203 | * @param view The view that is a descendant of the RecyclerView. |
| 4204 | * |
| 4205 | * @return The ViewHolder that contains the given view or null if the provided view is not a |
| 4206 | * descendant of this RecyclerView. |
| 4207 | */ |
| 4208 | @Nullable |
| 4209 | public ViewHolder findContainingViewHolder(View view) { |
| 4210 | View itemView = findContainingItemView(view); |
| 4211 | return itemView == null ? null : getChildViewHolder(itemView); |
| 4212 | } |
| 4213 | |
| 4214 | |
| 4215 | static ViewHolder getChildViewHolderInt(View child) { |
| 4216 | if (child == null) { |
| 4217 | return null; |
| 4218 | } |
| 4219 | return ((LayoutParams) child.getLayoutParams()).mViewHolder; |
| 4220 | } |
| 4221 | |
| 4222 | /** |
| 4223 | * @deprecated use {@link #getChildAdapterPosition(View)} or |
| 4224 | * {@link #getChildLayoutPosition(View)}. |
| 4225 | */ |
| 4226 | @Deprecated |
| 4227 | public int getChildPosition(View child) { |
| 4228 | return getChildAdapterPosition(child); |
| 4229 | } |
| 4230 | |
| 4231 | /** |
| 4232 | * Return the adapter position that the given child view corresponds to. |
| 4233 | * |
| 4234 | * @param child Child View to query |
| 4235 | * @return Adapter position corresponding to the given view or {@link #NO_POSITION} |
| 4236 | */ |
| 4237 | public int getChildAdapterPosition(View child) { |
| 4238 | final ViewHolder holder = getChildViewHolderInt(child); |
| 4239 | return holder != null ? holder.getAdapterPosition() : NO_POSITION; |
| 4240 | } |
| 4241 | |
| 4242 | /** |
| 4243 | * Return the adapter position of the given child view as of the latest completed layout pass. |
| 4244 | * <p> |
| 4245 | * This position may not be equal to Item's adapter position if there are pending changes |
| 4246 | * in the adapter which have not been reflected to the layout yet. |
| 4247 | * |
| 4248 | * @param child Child View to query |
| 4249 | * @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if |
| 4250 | * the View is representing a removed item. |
| 4251 | */ |
| 4252 | public int getChildLayoutPosition(View child) { |
| 4253 | final ViewHolder holder = getChildViewHolderInt(child); |
| 4254 | return holder != null ? holder.getLayoutPosition() : NO_POSITION; |
| 4255 | } |
| 4256 | |
| 4257 | /** |
| 4258 | * Return the stable item id that the given child view corresponds to. |
| 4259 | * |
| 4260 | * @param child Child View to query |
| 4261 | * @return Item id corresponding to the given view or {@link #NO_ID} |
| 4262 | */ |
| 4263 | public long getChildItemId(View child) { |
| 4264 | if (mAdapter == null || !mAdapter.hasStableIds()) { |
| 4265 | return NO_ID; |
| 4266 | } |
| 4267 | final ViewHolder holder = getChildViewHolderInt(child); |
| 4268 | return holder != null ? holder.getItemId() : NO_ID; |
| 4269 | } |
| 4270 | |
| 4271 | /** |
| 4272 | * @deprecated use {@link #findViewHolderForLayoutPosition(int)} or |
| 4273 | * {@link #findViewHolderForAdapterPosition(int)} |
| 4274 | */ |
| 4275 | @Deprecated |
| 4276 | public ViewHolder findViewHolderForPosition(int position) { |
| 4277 | return findViewHolderForPosition(position, false); |
| 4278 | } |
| 4279 | |
| 4280 | /** |
| 4281 | * Return the ViewHolder for the item in the given position of the data set as of the latest |
| 4282 | * layout pass. |
| 4283 | * <p> |
| 4284 | * This method checks only the children of RecyclerView. If the item at the given |
| 4285 | * <code>position</code> is not laid out, it <em>will not</em> create a new one. |
| 4286 | * <p> |
| 4287 | * Note that when Adapter contents change, ViewHolder positions are not updated until the |
| 4288 | * next layout calculation. If there are pending adapter updates, the return value of this |
| 4289 | * method may not match your adapter contents. You can use |
| 4290 | * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder. |
| 4291 | * <p> |
| 4292 | * When the ItemAnimator is running a change animation, there might be 2 ViewHolders |
| 4293 | * with the same layout position representing the same Item. In this case, the updated |
| 4294 | * ViewHolder will be returned. |
| 4295 | * |
| 4296 | * @param position The position of the item in the data set of the adapter |
| 4297 | * @return The ViewHolder at <code>position</code> or null if there is no such item |
| 4298 | */ |
| 4299 | public ViewHolder findViewHolderForLayoutPosition(int position) { |
| 4300 | return findViewHolderForPosition(position, false); |
| 4301 | } |
| 4302 | |
| 4303 | /** |
| 4304 | * Return the ViewHolder for the item in the given position of the data set. Unlike |
| 4305 | * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending |
| 4306 | * adapter changes that may not be reflected to the layout yet. On the other hand, if |
| 4307 | * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been |
| 4308 | * calculated yet, this method will return <code>null</code> since the new positions of views |
| 4309 | * are unknown until the layout is calculated. |
| 4310 | * <p> |
| 4311 | * This method checks only the children of RecyclerView. If the item at the given |
| 4312 | * <code>position</code> is not laid out, it <em>will not</em> create a new one. |
| 4313 | * <p> |
| 4314 | * When the ItemAnimator is running a change animation, there might be 2 ViewHolders |
| 4315 | * representing the same Item. In this case, the updated ViewHolder will be returned. |
| 4316 | * |
| 4317 | * @param position The position of the item in the data set of the adapter |
| 4318 | * @return The ViewHolder at <code>position</code> or null if there is no such item |
| 4319 | */ |
| 4320 | public ViewHolder findViewHolderForAdapterPosition(int position) { |
| 4321 | if (mDataSetHasChangedAfterLayout) { |
| 4322 | return null; |
| 4323 | } |
| 4324 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 4325 | // hidden VHs are not preferred but if that is the only one we find, we rather return it |
| 4326 | ViewHolder hidden = null; |
| 4327 | for (int i = 0; i < childCount; i++) { |
| 4328 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 4329 | if (holder != null && !holder.isRemoved() |
| 4330 | && getAdapterPositionFor(holder) == position) { |
| 4331 | if (mChildHelper.isHidden(holder.itemView)) { |
| 4332 | hidden = holder; |
| 4333 | } else { |
| 4334 | return holder; |
| 4335 | } |
| 4336 | } |
| 4337 | } |
| 4338 | return hidden; |
| 4339 | } |
| 4340 | |
| 4341 | ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { |
| 4342 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 4343 | ViewHolder hidden = null; |
| 4344 | for (int i = 0; i < childCount; i++) { |
| 4345 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 4346 | if (holder != null && !holder.isRemoved()) { |
| 4347 | if (checkNewPosition) { |
| 4348 | if (holder.mPosition != position) { |
| 4349 | continue; |
| 4350 | } |
| 4351 | } else if (holder.getLayoutPosition() != position) { |
| 4352 | continue; |
| 4353 | } |
| 4354 | if (mChildHelper.isHidden(holder.itemView)) { |
| 4355 | hidden = holder; |
| 4356 | } else { |
| 4357 | return holder; |
| 4358 | } |
| 4359 | } |
| 4360 | } |
| 4361 | // This method should not query cached views. It creates a problem during adapter updates |
| 4362 | // when we are dealing with already laid out views. Also, for the public method, it is more |
| 4363 | // reasonable to return null if position is not laid out. |
| 4364 | return hidden; |
| 4365 | } |
| 4366 | |
| 4367 | /** |
| 4368 | * Return the ViewHolder for the item with the given id. The RecyclerView must |
| 4369 | * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to |
| 4370 | * return a non-null value. |
| 4371 | * <p> |
| 4372 | * This method checks only the children of RecyclerView. If the item with the given |
| 4373 | * <code>id</code> is not laid out, it <em>will not</em> create a new one. |
| 4374 | * |
| 4375 | * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the |
| 4376 | * same id. In this case, the updated ViewHolder will be returned. |
| 4377 | * |
| 4378 | * @param id The id for the requested item |
| 4379 | * @return The ViewHolder with the given <code>id</code> or null if there is no such item |
| 4380 | */ |
| 4381 | public ViewHolder findViewHolderForItemId(long id) { |
| 4382 | if (mAdapter == null || !mAdapter.hasStableIds()) { |
| 4383 | return null; |
| 4384 | } |
| 4385 | final int childCount = mChildHelper.getUnfilteredChildCount(); |
| 4386 | ViewHolder hidden = null; |
| 4387 | for (int i = 0; i < childCount; i++) { |
| 4388 | final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); |
| 4389 | if (holder != null && !holder.isRemoved() && holder.getItemId() == id) { |
| 4390 | if (mChildHelper.isHidden(holder.itemView)) { |
| 4391 | hidden = holder; |
| 4392 | } else { |
| 4393 | return holder; |
| 4394 | } |
| 4395 | } |
| 4396 | } |
| 4397 | return hidden; |
| 4398 | } |
| 4399 | |
| 4400 | /** |
| 4401 | * Find the topmost view under the given point. |
| 4402 | * |
| 4403 | * @param x Horizontal position in pixels to search |
| 4404 | * @param y Vertical position in pixels to search |
| 4405 | * @return The child view under (x, y) or null if no matching child is found |
| 4406 | */ |
| 4407 | public View findChildViewUnder(float x, float y) { |
| 4408 | final int count = mChildHelper.getChildCount(); |
| 4409 | for (int i = count - 1; i >= 0; i--) { |
| 4410 | final View child = mChildHelper.getChildAt(i); |
| 4411 | final float translationX = child.getTranslationX(); |
| 4412 | final float translationY = child.getTranslationY(); |
| 4413 | if (x >= child.getLeft() + translationX |
| 4414 | && x <= child.getRight() + translationX |
| 4415 | && y >= child.getTop() + translationY |
| 4416 | && y <= child.getBottom() + translationY) { |
| 4417 | return child; |
| 4418 | } |
| 4419 | } |
| 4420 | return null; |
| 4421 | } |
| 4422 | |
| 4423 | @Override |
| 4424 | public boolean drawChild(Canvas canvas, View child, long drawingTime) { |
| 4425 | return super.drawChild(canvas, child, drawingTime); |
| 4426 | } |
| 4427 | |
| 4428 | /** |
| 4429 | * Offset the bounds of all child views by <code>dy</code> pixels. |
| 4430 | * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. |
| 4431 | * |
| 4432 | * @param dy Vertical pixel offset to apply to the bounds of all child views |
| 4433 | */ |
| 4434 | public void offsetChildrenVertical(int dy) { |
| 4435 | final int childCount = mChildHelper.getChildCount(); |
| 4436 | for (int i = 0; i < childCount; i++) { |
| 4437 | mChildHelper.getChildAt(i).offsetTopAndBottom(dy); |
| 4438 | } |
| 4439 | } |
| 4440 | |
| 4441 | /** |
| 4442 | * Called when an item view is attached to this RecyclerView. |
| 4443 | * |
| 4444 | * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications |
| 4445 | * of child views as they become attached. This will be called before a |
| 4446 | * {@link LayoutManager} measures or lays out the view and is a good time to perform these |
| 4447 | * changes.</p> |
| 4448 | * |
| 4449 | * @param child Child view that is now attached to this RecyclerView and its associated window |
| 4450 | */ |
| 4451 | public void onChildAttachedToWindow(View child) { |
| 4452 | } |
| 4453 | |
| 4454 | /** |
| 4455 | * Called when an item view is detached from this RecyclerView. |
| 4456 | * |
| 4457 | * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications |
| 4458 | * of child views as they become detached. This will be called as a |
| 4459 | * {@link LayoutManager} fully detaches the child view from the parent and its window.</p> |
| 4460 | * |
| 4461 | * @param child Child view that is now detached from this RecyclerView and its associated window |
| 4462 | */ |
| 4463 | public void onChildDetachedFromWindow(View child) { |
| 4464 | } |
| 4465 | |
| 4466 | /** |
| 4467 | * Offset the bounds of all child views by <code>dx</code> pixels. |
| 4468 | * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}. |
| 4469 | * |
| 4470 | * @param dx Horizontal pixel offset to apply to the bounds of all child views |
| 4471 | */ |
| 4472 | public void offsetChildrenHorizontal(int dx) { |
| 4473 | final int childCount = mChildHelper.getChildCount(); |
| 4474 | for (int i = 0; i < childCount; i++) { |
| 4475 | mChildHelper.getChildAt(i).offsetLeftAndRight(dx); |
| 4476 | } |
| 4477 | } |
| 4478 | |
| 4479 | /** |
| 4480 | * Returns the bounds of the view including its decoration and margins. |
| 4481 | * |
| 4482 | * @param view The view element to check |
| 4483 | * @param outBounds A rect that will receive the bounds of the element including its |
| 4484 | * decoration and margins. |
| 4485 | */ |
| 4486 | public void getDecoratedBoundsWithMargins(View view, Rect outBounds) { |
| 4487 | getDecoratedBoundsWithMarginsInt(view, outBounds); |
| 4488 | } |
| 4489 | |
| 4490 | static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) { |
| 4491 | final LayoutParams lp = (LayoutParams) view.getLayoutParams(); |
| 4492 | final Rect insets = lp.mDecorInsets; |
| 4493 | outBounds.set(view.getLeft() - insets.left - lp.leftMargin, |
| 4494 | view.getTop() - insets.top - lp.topMargin, |
| 4495 | view.getRight() + insets.right + lp.rightMargin, |
| 4496 | view.getBottom() + insets.bottom + lp.bottomMargin); |
| 4497 | } |
| 4498 | |
| 4499 | Rect getItemDecorInsetsForChild(View child) { |
| 4500 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| 4501 | if (!lp.mInsetsDirty) { |
| 4502 | return lp.mDecorInsets; |
| 4503 | } |
| 4504 | |
| 4505 | if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { |
| 4506 | // changed/invalid items should not be updated until they are rebound. |
| 4507 | return lp.mDecorInsets; |
| 4508 | } |
| 4509 | final Rect insets = lp.mDecorInsets; |
| 4510 | insets.set(0, 0, 0, 0); |
| 4511 | final int decorCount = mItemDecorations.size(); |
| 4512 | for (int i = 0; i < decorCount; i++) { |
| 4513 | mTempRect.set(0, 0, 0, 0); |
| 4514 | mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); |
| 4515 | insets.left += mTempRect.left; |
| 4516 | insets.top += mTempRect.top; |
| 4517 | insets.right += mTempRect.right; |
| 4518 | insets.bottom += mTempRect.bottom; |
| 4519 | } |
| 4520 | lp.mInsetsDirty = false; |
| 4521 | return insets; |
| 4522 | } |
| 4523 | |
| 4524 | /** |
| 4525 | * Called when the scroll position of this RecyclerView changes. Subclasses should use |
| 4526 | * this method to respond to scrolling within the adapter's data set instead of an explicit |
| 4527 | * listener. |
| 4528 | * |
| 4529 | * <p>This method will always be invoked before listeners. If a subclass needs to perform |
| 4530 | * any additional upkeep or bookkeeping after scrolling but before listeners run, |
| 4531 | * this is a good place to do so.</p> |
| 4532 | * |
| 4533 | * <p>This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives |
| 4534 | * the distance scrolled in either direction within the adapter's data set instead of absolute |
| 4535 | * scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from |
| 4536 | * any arbitrary point in the data set, <code>onScrollChanged</code> will always receive |
| 4537 | * the current {@link View#getScrollX()} and {@link View#getScrollY()} values which |
| 4538 | * do not correspond to the data set scroll position. However, some subclasses may choose |
| 4539 | * to use these fields as special offsets.</p> |
| 4540 | * |
| 4541 | * @param dx horizontal distance scrolled in pixels |
| 4542 | * @param dy vertical distance scrolled in pixels |
| 4543 | */ |
| 4544 | public void onScrolled(int dx, int dy) { |
| 4545 | // Do nothing |
| 4546 | } |
| 4547 | |
| 4548 | void dispatchOnScrolled(int hresult, int vresult) { |
| 4549 | mDispatchScrollCounter++; |
| 4550 | // Pass the current scrollX/scrollY values; no actual change in these properties occurred |
| 4551 | // but some general-purpose code may choose to respond to changes this way. |
| 4552 | final int scrollX = getScrollX(); |
| 4553 | final int scrollY = getScrollY(); |
| 4554 | onScrollChanged(scrollX, scrollY, scrollX, scrollY); |
| 4555 | |
| 4556 | // Pass the real deltas to onScrolled, the RecyclerView-specific method. |
| 4557 | onScrolled(hresult, vresult); |
| 4558 | |
| 4559 | // Invoke listeners last. Subclassed view methods always handle the event first. |
| 4560 | // All internal state is consistent by the time listeners are invoked. |
| 4561 | if (mScrollListener != null) { |
| 4562 | mScrollListener.onScrolled(this, hresult, vresult); |
| 4563 | } |
| 4564 | if (mScrollListeners != null) { |
| 4565 | for (int i = mScrollListeners.size() - 1; i >= 0; i--) { |
| 4566 | mScrollListeners.get(i).onScrolled(this, hresult, vresult); |
| 4567 | } |
| 4568 | } |
| 4569 | mDispatchScrollCounter--; |
| 4570 | } |
| 4571 | |
| 4572 | /** |
| 4573 | * Called when the scroll state of this RecyclerView changes. Subclasses should use this |
| 4574 | * method to respond to state changes instead of an explicit listener. |
| 4575 | * |
| 4576 | * <p>This method will always be invoked before listeners, but after the LayoutManager |
| 4577 | * responds to the scroll state change.</p> |
| 4578 | * |
| 4579 | * @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE}, |
| 4580 | * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING} |
| 4581 | */ |
| 4582 | public void onScrollStateChanged(int state) { |
| 4583 | // Do nothing |
| 4584 | } |
| 4585 | |
| 4586 | void dispatchOnScrollStateChanged(int state) { |
| 4587 | // Let the LayoutManager go first; this allows it to bring any properties into |
| 4588 | // a consistent state before the RecyclerView subclass responds. |
| 4589 | if (mLayout != null) { |
| 4590 | mLayout.onScrollStateChanged(state); |
| 4591 | } |
| 4592 | |
| 4593 | // Let the RecyclerView subclass handle this event next; any LayoutManager property |
| 4594 | // changes will be reflected by this time. |
| 4595 | onScrollStateChanged(state); |
| 4596 | |
| 4597 | // Listeners go last. All other internal state is consistent by this point. |
| 4598 | if (mScrollListener != null) { |
| 4599 | mScrollListener.onScrollStateChanged(this, state); |
| 4600 | } |
| 4601 | if (mScrollListeners != null) { |
| 4602 | for (int i = mScrollListeners.size() - 1; i >= 0; i--) { |
| 4603 | mScrollListeners.get(i).onScrollStateChanged(this, state); |
| 4604 | } |
| 4605 | } |
| 4606 | } |
| 4607 | |
| 4608 | /** |
| 4609 | * Returns whether there are pending adapter updates which are not yet applied to the layout. |
| 4610 | * <p> |
| 4611 | * If this method returns <code>true</code>, it means that what user is currently seeing may not |
| 4612 | * reflect them adapter contents (depending on what has changed). |
| 4613 | * You may use this information to defer or cancel some operations. |
| 4614 | * <p> |
| 4615 | * This method returns true if RecyclerView has not yet calculated the first layout after it is |
| 4616 | * attached to the Window or the Adapter has been replaced. |
| 4617 | * |
| 4618 | * @return True if there are some adapter updates which are not yet reflected to layout or false |
| 4619 | * if layout is up to date. |
| 4620 | */ |
| 4621 | public boolean hasPendingAdapterUpdates() { |
| 4622 | return !mFirstLayoutComplete || mDataSetHasChangedAfterLayout |
| 4623 | || mAdapterHelper.hasPendingUpdates(); |
| 4624 | } |
| 4625 | |
| 4626 | class ViewFlinger implements Runnable { |
| 4627 | private int mLastFlingX; |
| 4628 | private int mLastFlingY; |
| 4629 | private OverScroller mScroller; |
| 4630 | Interpolator mInterpolator = sQuinticInterpolator; |
| 4631 | |
| 4632 | |
| 4633 | // When set to true, postOnAnimation callbacks are delayed until the run method completes |
| 4634 | private boolean mEatRunOnAnimationRequest = false; |
| 4635 | |
| 4636 | // Tracks if postAnimationCallback should be re-attached when it is done |
| 4637 | private boolean mReSchedulePostAnimationCallback = false; |
| 4638 | |
| 4639 | ViewFlinger() { |
| 4640 | mScroller = new OverScroller(getContext(), sQuinticInterpolator); |
| 4641 | } |
| 4642 | |
| 4643 | @Override |
| 4644 | public void run() { |
| 4645 | if (mLayout == null) { |
| 4646 | stop(); |
| 4647 | return; // no layout, cannot scroll. |
| 4648 | } |
| 4649 | disableRunOnAnimationRequests(); |
| 4650 | consumePendingUpdateOperations(); |
| 4651 | // keep a local reference so that if it is changed during onAnimation method, it won't |
| 4652 | // cause unexpected behaviors |
| 4653 | final OverScroller scroller = mScroller; |
| 4654 | final SmoothScroller smoothScroller = mLayout.mSmoothScroller; |
| 4655 | if (scroller.computeScrollOffset()) { |
| 4656 | final int x = scroller.getCurrX(); |
| 4657 | final int y = scroller.getCurrY(); |
| 4658 | final int dx = x - mLastFlingX; |
| 4659 | final int dy = y - mLastFlingY; |
| 4660 | int hresult = 0; |
| 4661 | int vresult = 0; |
| 4662 | mLastFlingX = x; |
| 4663 | mLastFlingY = y; |
| 4664 | int overscrollX = 0, overscrollY = 0; |
| 4665 | if (mAdapter != null) { |
| 4666 | eatRequestLayout(); |
| 4667 | onEnterLayoutOrScroll(); |
| 4668 | Trace.beginSection(TRACE_SCROLL_TAG); |
| 4669 | if (dx != 0) { |
| 4670 | hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); |
| 4671 | overscrollX = dx - hresult; |
| 4672 | } |
| 4673 | if (dy != 0) { |
| 4674 | vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState); |
| 4675 | overscrollY = dy - vresult; |
| 4676 | } |
| 4677 | Trace.endSection(); |
| 4678 | repositionShadowingViews(); |
| 4679 | |
| 4680 | onExitLayoutOrScroll(); |
| 4681 | resumeRequestLayout(false); |
| 4682 | |
| 4683 | if (smoothScroller != null && !smoothScroller.isPendingInitialRun() |
| 4684 | && smoothScroller.isRunning()) { |
| 4685 | final int adapterSize = mState.getItemCount(); |
| 4686 | if (adapterSize == 0) { |
| 4687 | smoothScroller.stop(); |
| 4688 | } else if (smoothScroller.getTargetPosition() >= adapterSize) { |
| 4689 | smoothScroller.setTargetPosition(adapterSize - 1); |
| 4690 | smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); |
| 4691 | } else { |
| 4692 | smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY); |
| 4693 | } |
| 4694 | } |
| 4695 | } |
| 4696 | if (!mItemDecorations.isEmpty()) { |
| 4697 | invalidate(); |
| 4698 | } |
| 4699 | if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { |
| 4700 | considerReleasingGlowsOnScroll(dx, dy); |
| 4701 | } |
| 4702 | if (overscrollX != 0 || overscrollY != 0) { |
| 4703 | final int vel = (int) scroller.getCurrVelocity(); |
| 4704 | |
| 4705 | int velX = 0; |
| 4706 | if (overscrollX != x) { |
| 4707 | velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0; |
| 4708 | } |
| 4709 | |
| 4710 | int velY = 0; |
| 4711 | if (overscrollY != y) { |
| 4712 | velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0; |
| 4713 | } |
| 4714 | |
| 4715 | if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { |
| 4716 | absorbGlows(velX, velY); |
| 4717 | } |
| 4718 | if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) |
| 4719 | && (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) { |
| 4720 | scroller.abortAnimation(); |
| 4721 | } |
| 4722 | } |
| 4723 | if (hresult != 0 || vresult != 0) { |
| 4724 | dispatchOnScrolled(hresult, vresult); |
| 4725 | } |
| 4726 | |
| 4727 | if (!awakenScrollBars()) { |
| 4728 | invalidate(); |
| 4729 | } |
| 4730 | |
| 4731 | final boolean fullyConsumedVertical = dy != 0 && mLayout.canScrollVertically() |
| 4732 | && vresult == dy; |
| 4733 | final boolean fullyConsumedHorizontal = dx != 0 && mLayout.canScrollHorizontally() |
| 4734 | && hresult == dx; |
| 4735 | final boolean fullyConsumedAny = (dx == 0 && dy == 0) || fullyConsumedHorizontal |
| 4736 | || fullyConsumedVertical; |
| 4737 | |
| 4738 | if (scroller.isFinished() || !fullyConsumedAny) { |
| 4739 | setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this. |
| 4740 | if (ALLOW_THREAD_GAP_WORK) { |
| 4741 | mPrefetchRegistry.clearPrefetchPositions(); |
| 4742 | } |
| 4743 | } else { |
| 4744 | postOnAnimation(); |
| 4745 | if (mGapWorker != null) { |
| 4746 | mGapWorker.postFromTraversal(RecyclerView.this, dx, dy); |
| 4747 | } |
| 4748 | } |
| 4749 | } |
| 4750 | // call this after the onAnimation is complete not to have inconsistent callbacks etc. |
| 4751 | if (smoothScroller != null) { |
| 4752 | if (smoothScroller.isPendingInitialRun()) { |
| 4753 | smoothScroller.onAnimation(0, 0); |
| 4754 | } |
| 4755 | if (!mReSchedulePostAnimationCallback) { |
| 4756 | smoothScroller.stop(); //stop if it does not trigger any scroll |
| 4757 | } |
| 4758 | } |
| 4759 | enableRunOnAnimationRequests(); |
| 4760 | } |
| 4761 | |
| 4762 | private void disableRunOnAnimationRequests() { |
| 4763 | mReSchedulePostAnimationCallback = false; |
| 4764 | mEatRunOnAnimationRequest = true; |
| 4765 | } |
| 4766 | |
| 4767 | private void enableRunOnAnimationRequests() { |
| 4768 | mEatRunOnAnimationRequest = false; |
| 4769 | if (mReSchedulePostAnimationCallback) { |
| 4770 | postOnAnimation(); |
| 4771 | } |
| 4772 | } |
| 4773 | |
| 4774 | void postOnAnimation() { |
| 4775 | if (mEatRunOnAnimationRequest) { |
| 4776 | mReSchedulePostAnimationCallback = true; |
| 4777 | } else { |
| 4778 | removeCallbacks(this); |
| 4779 | RecyclerView.this.postOnAnimation(this); |
| 4780 | } |
| 4781 | } |
| 4782 | |
| 4783 | public void fling(int velocityX, int velocityY) { |
| 4784 | setScrollState(SCROLL_STATE_SETTLING); |
| 4785 | mLastFlingX = mLastFlingY = 0; |
| 4786 | mScroller.fling(0, 0, velocityX, velocityY, |
| 4787 | Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); |
| 4788 | postOnAnimation(); |
| 4789 | } |
| 4790 | |
| 4791 | public void smoothScrollBy(int dx, int dy) { |
| 4792 | smoothScrollBy(dx, dy, 0, 0); |
| 4793 | } |
| 4794 | |
| 4795 | public void smoothScrollBy(int dx, int dy, int vx, int vy) { |
| 4796 | smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy)); |
| 4797 | } |
| 4798 | |
| 4799 | private float distanceInfluenceForSnapDuration(float f) { |
| 4800 | f -= 0.5f; // center the values about 0. |
| 4801 | f *= 0.3f * Math.PI / 2.0f; |
| 4802 | return (float) Math.sin(f); |
| 4803 | } |
| 4804 | |
| 4805 | private int computeScrollDuration(int dx, int dy, int vx, int vy) { |
| 4806 | final int absDx = Math.abs(dx); |
| 4807 | final int absDy = Math.abs(dy); |
| 4808 | final boolean horizontal = absDx > absDy; |
| 4809 | final int velocity = (int) Math.sqrt(vx * vx + vy * vy); |
| 4810 | final int delta = (int) Math.sqrt(dx * dx + dy * dy); |
| 4811 | final int containerSize = horizontal ? getWidth() : getHeight(); |
| 4812 | final int halfContainerSize = containerSize / 2; |
| 4813 | final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize); |
| 4814 | final float distance = halfContainerSize + halfContainerSize |
| 4815 | * distanceInfluenceForSnapDuration(distanceRatio); |
| 4816 | |
| 4817 | final int duration; |
| 4818 | if (velocity > 0) { |
| 4819 | duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); |
| 4820 | } else { |
| 4821 | float absDelta = (float) (horizontal ? absDx : absDy); |
| 4822 | duration = (int) (((absDelta / containerSize) + 1) * 300); |
| 4823 | } |
| 4824 | return Math.min(duration, MAX_SCROLL_DURATION); |
| 4825 | } |
| 4826 | |
| 4827 | public void smoothScrollBy(int dx, int dy, int duration) { |
| 4828 | smoothScrollBy(dx, dy, duration, sQuinticInterpolator); |
| 4829 | } |
| 4830 | |
| 4831 | public void smoothScrollBy(int dx, int dy, Interpolator interpolator) { |
| 4832 | smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, 0, 0), |
| 4833 | interpolator == null ? sQuinticInterpolator : interpolator); |
| 4834 | } |
| 4835 | |
| 4836 | public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) { |
| 4837 | if (mInterpolator != interpolator) { |
| 4838 | mInterpolator = interpolator; |
| 4839 | mScroller = new OverScroller(getContext(), interpolator); |
| 4840 | } |
| 4841 | setScrollState(SCROLL_STATE_SETTLING); |
| 4842 | mLastFlingX = mLastFlingY = 0; |
| 4843 | mScroller.startScroll(0, 0, dx, dy, duration); |
| 4844 | postOnAnimation(); |
| 4845 | } |
| 4846 | |
| 4847 | public void stop() { |
| 4848 | removeCallbacks(this); |
| 4849 | mScroller.abortAnimation(); |
| 4850 | } |
| 4851 | |
| 4852 | } |
| 4853 | |
| 4854 | void repositionShadowingViews() { |
| 4855 | // Fix up shadow views used by change animations |
| 4856 | int count = mChildHelper.getChildCount(); |
| 4857 | for (int i = 0; i < count; i++) { |
| 4858 | View view = mChildHelper.getChildAt(i); |
| 4859 | ViewHolder holder = getChildViewHolder(view); |
| 4860 | if (holder != null && holder.mShadowingHolder != null) { |
| 4861 | View shadowingView = holder.mShadowingHolder.itemView; |
| 4862 | int left = view.getLeft(); |
| 4863 | int top = view.getTop(); |
| 4864 | if (left != shadowingView.getLeft() || top != shadowingView.getTop()) { |
| 4865 | shadowingView.layout(left, top, |
| 4866 | left + shadowingView.getWidth(), |
| 4867 | top + shadowingView.getHeight()); |
| 4868 | } |
| 4869 | } |
| 4870 | } |
| 4871 | } |
| 4872 | |
| 4873 | private class RecyclerViewDataObserver extends AdapterDataObserver { |
| 4874 | RecyclerViewDataObserver() { |
| 4875 | } |
| 4876 | |
| 4877 | @Override |
| 4878 | public void onChanged() { |
| 4879 | assertNotInLayoutOrScroll(null); |
| 4880 | mState.mStructureChanged = true; |
| 4881 | |
| 4882 | setDataSetChangedAfterLayout(); |
| 4883 | if (!mAdapterHelper.hasPendingUpdates()) { |
| 4884 | requestLayout(); |
| 4885 | } |
| 4886 | } |
| 4887 | |
| 4888 | @Override |
| 4889 | public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { |
| 4890 | assertNotInLayoutOrScroll(null); |
| 4891 | if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { |
| 4892 | triggerUpdateProcessor(); |
| 4893 | } |
| 4894 | } |
| 4895 | |
| 4896 | @Override |
| 4897 | public void onItemRangeInserted(int positionStart, int itemCount) { |
| 4898 | assertNotInLayoutOrScroll(null); |
| 4899 | if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) { |
| 4900 | triggerUpdateProcessor(); |
| 4901 | } |
| 4902 | } |
| 4903 | |
| 4904 | @Override |
| 4905 | public void onItemRangeRemoved(int positionStart, int itemCount) { |
| 4906 | assertNotInLayoutOrScroll(null); |
| 4907 | if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { |
| 4908 | triggerUpdateProcessor(); |
| 4909 | } |
| 4910 | } |
| 4911 | |
| 4912 | @Override |
| 4913 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { |
| 4914 | assertNotInLayoutOrScroll(null); |
| 4915 | if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) { |
| 4916 | triggerUpdateProcessor(); |
| 4917 | } |
| 4918 | } |
| 4919 | |
| 4920 | void triggerUpdateProcessor() { |
| 4921 | if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { |
| 4922 | RecyclerView.this.postOnAnimation(mUpdateChildViewsRunnable); |
| 4923 | } else { |
| 4924 | mAdapterUpdateDuringMeasure = true; |
| 4925 | requestLayout(); |
| 4926 | } |
| 4927 | } |
| 4928 | } |
| 4929 | |
| 4930 | /** |
| 4931 | * RecycledViewPool lets you share Views between multiple RecyclerViews. |
| 4932 | * <p> |
| 4933 | * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool |
| 4934 | * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. |
| 4935 | * <p> |
| 4936 | * RecyclerView automatically creates a pool for itself if you don't provide one. |
| 4937 | * |
| 4938 | */ |
| 4939 | public static class RecycledViewPool { |
| 4940 | private static final int DEFAULT_MAX_SCRAP = 5; |
| 4941 | |
| 4942 | /** |
| 4943 | * Tracks both pooled holders, as well as create/bind timing metadata for the given type. |
| 4944 | * |
| 4945 | * Note that this tracks running averages of create/bind time across all RecyclerViews |
| 4946 | * (and, indirectly, Adapters) that use this pool. |
| 4947 | * |
| 4948 | * 1) This enables us to track average create and bind times across multiple adapters. Even |
| 4949 | * though create (and especially bind) may behave differently for different Adapter |
| 4950 | * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type. |
| 4951 | * |
| 4952 | * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return |
| 4953 | * false for all other views of its type for the same deadline. This prevents items |
| 4954 | * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch. |
| 4955 | */ |
| 4956 | static class ScrapData { |
Andrei Onea | 1588439 | 2019-03-22 17:28:11 +0000 | [diff] [blame^] | 4957 | @UnsupportedAppUsage |
Aurimas Liutikas | 7149a63 | 2017-01-18 17:36:10 -0800 | [diff] [blame] | 4958 | ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); |
| 4959 | int mMaxScrap = DEFAULT_MAX_SCRAP; |
| 4960 | long mCreateRunningAverageNs = 0; |
| 4961 | long mBindRunningAverageNs = 0; |
| 4962 | } |
| 4963 | SparseArray<ScrapData> mScrap = new SparseArray<>(); |
| 4964 | |
| 4965 | private int mAttachCount = 0; |
| 4966 | |
| 4967 | public void clear() { |
| 4968 | for (int i = 0; i < mScrap.size(); i++) { |
| 4969 | ScrapData data = mScrap.valueAt(i); |
| 4970 | data.mScrapHeap.clear(); |
| 4971 | } |
| 4972 | } |
| 4973 | |
| 4974 | public void setMaxRecycledViews(int viewType, int max) { |
| 4975 | ScrapData scrapData = getScrapDataForType(viewType); |
| 4976 | scrapData.mMaxScrap = max; |
| 4977 | final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; |
| 4978 | if (scrapHeap != null) { |
| 4979 | while (scrapHeap.size() > max) { |
| 4980 | scrapHeap.remove(scrapHeap.size() - 1); |
| 4981 | } |
| 4982 | } |
| 4983 | } |
| 4984 | |
| 4985 | /** |
| 4986 | * Returns the current number of Views held by the RecycledViewPool of the given view type. |
| 4987 | */ |
| 4988 | public int getRecycledViewCount(int viewType) { |
| 4989 | return getScrapDataForType(viewType).mScrapHeap.size(); |
| 4990 | } |
| 4991 | |
| 4992 | public ViewHolder getRecycledView(int viewType) { |
| 4993 | final ScrapData scrapData = mScrap.get(viewType); |
| 4994 | if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { |
| 4995 | final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; |
| 4996 | return scrapHeap.remove(scrapHeap.size() - 1); |
| 4997 | } |
| 4998 | return null; |
| 4999 | } |
| 5000 | |
| 5001 | int size() { |
| 5002 | int count = 0; |
| 5003 | for (int i = 0; i < mScrap.size(); i++) { |
| 5004 | ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i).mScrapHeap; |
| 5005 | if (viewHolders != null) { |
| 5006 | count += viewHolders.size(); |
| 5007 | } |
| 5008 | } |
| 5009 | return count; |
| 5010 | } |
| 5011 | |
| 5012 | public void putRecycledView(ViewHolder scrap) { |
| 5013 | final int viewType = scrap.getItemViewType(); |
| 5014 | final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap; |
| 5015 | if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { |
| 5016 | return; |
| 5017 | } |
| 5018 | if (DEBUG && scrapHeap.contains(scrap)) { |
| 5019 | throw new IllegalArgumentException("this scrap item already exists"); |
| 5020 | } |
| 5021 | scrap.resetInternal(); |
| 5022 | scrapHeap.add(scrap); |
| 5023 | } |
| 5024 | |
| 5025 | long runningAverage(long oldAverage, long newValue) { |
| 5026 | if (oldAverage == 0) { |
| 5027 | return newValue; |
| 5028 | } |
| 5029 | return (oldAverage / 4 * 3) + (newValue / 4); |
| 5030 | } |
| 5031 | |
| 5032 | void factorInCreateTime(int viewType, long createTimeNs) { |
| 5033 | ScrapData scrapData = getScrapDataForType(viewType); |
| 5034 | scrapData.mCreateRunningAverageNs = runningAverage( |
| 5035 | scrapData.mCreateRunningAverageNs, createTimeNs); |
| 5036 | } |
| 5037 | |
| 5038 | void factorInBindTime(int viewType, long bindTimeNs) { |
| 5039 | ScrapData scrapData = getScrapDataForType(viewType); |
| 5040 | scrapData.mBindRunningAverageNs = runningAverage( |
| 5041 | scrapData.mBindRunningAverageNs, bindTimeNs); |
| 5042 | } |
| 5043 | |
| 5044 | boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) { |
| 5045 | long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs; |
| 5046 | return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); |
| 5047 | } |
| 5048 | |
| 5049 | boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) { |
| 5050 | long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs; |
| 5051 | return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); |
| 5052 | } |
| 5053 | |
| 5054 | void attach(Adapter adapter) { |
| 5055 | mAttachCount++; |
| 5056 | } |
| 5057 | |
| 5058 | void detach() { |
| 5059 | mAttachCount--; |
| 5060 | } |
| 5061 | |
| 5062 | |
| 5063 | /** |
| 5064 | * Detaches the old adapter and attaches the new one. |
| 5065 | * <p> |
| 5066 | * RecycledViewPool will clear its cache if it has only one adapter attached and the new |
| 5067 | * adapter uses a different ViewHolder than the oldAdapter. |
| 5068 | * |
| 5069 | * @param oldAdapter The previous adapter instance. Will be detached. |
| 5070 | * @param newAdapter The new adapter instance. Will be attached. |
| 5071 | * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same |
| 5072 | * ViewHolder and view types. |
| 5073 | */ |
| 5074 | void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, |
| 5075 | boolean compatibleWithPrevious) { |
| 5076 | if (oldAdapter != null) { |
| 5077 | detach(); |
| 5078 | } |
| 5079 | if (!compatibleWithPrevious && mAttachCount == 0) { |
| 5080 | clear(); |
| 5081 | } |
| 5082 | if (newAdapter != null) { |
| 5083 | attach(newAdapter); |
| 5084 | } |
| 5085 | } |
| 5086 | |
| 5087 | private ScrapData getScrapDataForType(int viewType) { |
| 5088 | ScrapData scrapData = mScrap.get(viewType); |
| 5089 | if (scrapData == null) { |
| 5090 | scrapData = new ScrapData(); |
| 5091 | mScrap.put(viewType, scrapData); |
| 5092 | } |
| 5093 | return scrapData; |
| 5094 | } |
| 5095 | } |
| 5096 | |
| 5097 | /** |
| 5098 | * Utility method for finding an internal RecyclerView, if present |
| 5099 | */ |
| 5100 | @Nullable |
| 5101 | static RecyclerView findNestedRecyclerView(@NonNull View view) { |
| 5102 | if (!(view instanceof ViewGroup)) { |
| 5103 | return null; |
| 5104 | } |
| 5105 | if (view instanceof RecyclerView) { |
| 5106 | return (RecyclerView) view; |
| 5107 | } |
| 5108 | final ViewGroup parent = (ViewGroup) view; |
| 5109 | final int count = parent.getChildCount(); |
| 5110 | for (int i = 0; i < count; i++) { |
| 5111 | final View child = parent.getChildAt(i); |
| 5112 | final RecyclerView descendant = findNestedRecyclerView(child); |
| 5113 | if (descendant != null) { |
| 5114 | return descendant; |
| 5115 | } |
| 5116 | } |
| 5117 | return null; |
| 5118 | } |
| 5119 | |
| 5120 | /** |
| 5121 | * Utility method for clearing holder's internal RecyclerView, if present |
| 5122 | */ |
| 5123 | static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) { |
| 5124 | if (holder.mNestedRecyclerView != null) { |
| 5125 | View item = holder.mNestedRecyclerView.get(); |
| 5126 | while (item != null) { |
| 5127 | if (item == holder.itemView) { |
| 5128 | return; // match found, don't need to clear |
| 5129 | } |
| 5130 | |
| 5131 | ViewParent parent = item.getParent(); |
| 5132 | if (parent instanceof View) { |
| 5133 | item = (View) parent; |
| 5134 | } else { |
| 5135 | item = null; |
| 5136 | } |
| 5137 | } |
| 5138 | holder.mNestedRecyclerView = null; // not nested |
| 5139 | } |
| 5140 | } |
| 5141 | |
| 5142 | /** |
| 5143 | * Time base for deadline-aware work scheduling. Overridable for testing. |
| 5144 | * |
| 5145 | * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling |
| 5146 | * isn't relevant. |
| 5147 | */ |
| 5148 | long getNanoTime() { |
| 5149 | if (ALLOW_THREAD_GAP_WORK) { |
| 5150 | return System.nanoTime(); |
| 5151 | } else { |
| 5152 | return 0; |
| 5153 | } |
| 5154 | } |
| 5155 | |
| 5156 | /** |
| 5157 | * A Recycler is responsible for managing scrapped or detached item views for reuse. |
| 5158 | * |
| 5159 | * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but |
| 5160 | * that has been marked for removal or reuse.</p> |
| 5161 | * |
| 5162 | * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for |
| 5163 | * an adapter's data set representing the data at a given position or item ID. |
| 5164 | * If the view to be reused is considered "dirty" the adapter will be asked to rebind it. |
| 5165 | * If not, the view can be quickly reused by the LayoutManager with no further work. |
| 5166 | * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout} |
| 5167 | * may be repositioned by a LayoutManager without remeasurement.</p> |
| 5168 | */ |
| 5169 | public final class Recycler { |
| 5170 | final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); |
| 5171 | ArrayList<ViewHolder> mChangedScrap = null; |
| 5172 | |
| 5173 | final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); |
| 5174 | |
| 5175 | private final List<ViewHolder> |
| 5176 | mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); |
| 5177 | |
| 5178 | private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; |
| 5179 | int mViewCacheMax = DEFAULT_CACHE_SIZE; |
| 5180 | |
| 5181 | RecycledViewPool mRecyclerPool; |
| 5182 | |
| 5183 | private ViewCacheExtension mViewCacheExtension; |
| 5184 | |
| 5185 | static final int DEFAULT_CACHE_SIZE = 2; |
| 5186 | |
| 5187 | /** |
| 5188 | * Clear scrap views out of this recycler. Detached views contained within a |
| 5189 | * recycled view pool will remain. |
| 5190 | */ |
| 5191 | public void clear() { |
| 5192 | mAttachedScrap.clear(); |
| 5193 | recycleAndClearCachedViews(); |
| 5194 | } |
| 5195 | |
| 5196 | /** |
| 5197 | * Set the maximum number of detached, valid views we should retain for later use. |
| 5198 | * |
| 5199 | * @param viewCount Number of views to keep before sending views to the shared pool |
| 5200 | */ |
| 5201 | public void setViewCacheSize(int viewCount) { |
| 5202 | mRequestedCacheMax = viewCount; |
| 5203 | updateViewCacheSize(); |
| 5204 | } |
| 5205 | |
| 5206 | void updateViewCacheSize() { |
| 5207 | int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0; |
| 5208 | mViewCacheMax = mRequestedCacheMax + extraCache; |
| 5209 | |
| 5210 | // first, try the views that can be recycled |
| 5211 | for (int i = mCachedViews.size() - 1; |
| 5212 | i >= 0 && mCachedViews.size() > mViewCacheMax; i--) { |
| 5213 | recycleCachedViewAt(i); |
| 5214 | } |
| 5215 | } |
| 5216 | |
| 5217 | /** |
| 5218 | * Returns an unmodifiable list of ViewHolders that are currently in the scrap list. |
| 5219 | * |
| 5220 | * @return List of ViewHolders in the scrap list. |
| 5221 | */ |
| 5222 | public List<ViewHolder> getScrapList() { |
| 5223 | return mUnmodifiableAttachedScrap; |
| 5224 | } |
| 5225 | |
| 5226 | /** |
| 5227 | * Helper method for getViewForPosition. |
| 5228 | * <p> |
| 5229 | * Checks whether a given view holder can be used for the provided position. |
| 5230 | * |
| 5231 | * @param holder ViewHolder |
| 5232 | * @return true if ViewHolder matches the provided position, false otherwise |
| 5233 | */ |
| 5234 | boolean validateViewHolderForOffsetPosition(ViewHolder holder) { |
| 5235 | // if it is a removed holder, nothing to verify since we cannot ask adapter anymore |
| 5236 | // if it is not removed, verify the type and id. |
| 5237 | if (holder.isRemoved()) { |
| 5238 | if (DEBUG && !mState.isPreLayout()) { |
| 5239 | throw new IllegalStateException("should not receive a removed view unless it" |
| 5240 | + " is pre layout"); |
| 5241 | } |
| 5242 | return mState.isPreLayout(); |
| 5243 | } |
| 5244 | if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) { |
| 5245 | throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder " |
| 5246 | + "adapter position" + holder); |
| 5247 | } |
| 5248 | if (!mState.isPreLayout()) { |
| 5249 | // don't check type if it is pre-layout. |
| 5250 | final int type = mAdapter.getItemViewType(holder.mPosition); |
| 5251 | if (type != holder.getItemViewType()) { |
| 5252 | return false; |
| 5253 | } |
| 5254 | } |
| 5255 | if (mAdapter.hasStableIds()) { |
| 5256 | return holder.getItemId() == mAdapter.getItemId(holder.mPosition); |
| 5257 | } |
| 5258 | return true; |
| 5259 | } |
| 5260 | |
| 5261 | /** |
| 5262 | * Attempts to bind view, and account for relevant timing information. If |
| 5263 | * deadlineNs != FOREVER_NS, this method may fail to bind, and return false. |
| 5264 | * |
| 5265 | * @param holder Holder to be bound. |
| 5266 | * @param offsetPosition Position of item to be bound. |
| 5267 | * @param position Pre-layout position of item to be bound. |
| 5268 | * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should |
| 5269 | * complete. If FOREVER_NS is passed, this method will not fail to |
| 5270 | * bind the holder. |
| 5271 | * @return |
| 5272 | */ |
| 5273 | private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition, |
| 5274 | int position, long deadlineNs) { |
| 5275 | holder.mOwnerRecyclerView = RecyclerView.this; |
| 5276 | final int viewType = holder.getItemViewType(); |
| 5277 | long startBindNs = getNanoTime(); |
| 5278 | if (deadlineNs != FOREVER_NS |
| 5279 | && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) { |
| 5280 | // abort - we have a deadline we can't meet |
| 5281 | return false; |
| 5282 | } |
| 5283 | mAdapter.bindViewHolder(holder, offsetPosition); |
| 5284 | long endBindNs = getNanoTime(); |
| 5285 | mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs); |
| 5286 | attachAccessibilityDelegate(holder.itemView); |
| 5287 | if (mState.isPreLayout()) { |
| 5288 | holder.mPreLayoutPosition = position; |
| 5289 | } |
| 5290 | return true; |
| 5291 | } |
| 5292 | |
| 5293 | /** |
| 5294 | * Binds the given View to the position. The View can be a View previously retrieved via |
| 5295 | * {@link #getViewForPosition(int)} or created by |
| 5296 | * {@link Adapter#onCreateViewHolder(ViewGroup, int)}. |
| 5297 | * <p> |
| 5298 | * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)} |
| 5299 | * and let the RecyclerView handle caching. This is a helper method for LayoutManager who |
| 5300 | * wants to handle its own recycling logic. |
| 5301 | * <p> |
| 5302 | * Note that, {@link #getViewForPosition(int)} already binds the View to the position so |
| 5303 | * you don't need to call this method unless you want to bind this View to another position. |
| 5304 | * |
| 5305 | * @param view The view to update. |
| 5306 | * @param position The position of the item to bind to this View. |
| 5307 | */ |
| 5308 | public void bindViewToPosition(View view, int position) { |
| 5309 | ViewHolder holder = getChildViewHolderInt(view); |
| 5310 | if (holder == null) { |
| 5311 | throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot" |
| 5312 | + " pass arbitrary views to this method, they should be created by the " |
| 5313 | + "Adapter"); |
| 5314 | } |
| 5315 | final int offsetPosition = mAdapterHelper.findPositionOffset(position); |
| 5316 | if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { |
| 5317 | throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " |
| 5318 | + "position " + position + "(offset:" + offsetPosition + ")." |
| 5319 | + "state:" + mState.getItemCount()); |
| 5320 | } |
| 5321 | tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS); |
| 5322 | |
| 5323 | final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); |
| 5324 | final LayoutParams rvLayoutParams; |
| 5325 | if (lp == null) { |
| 5326 | rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); |
| 5327 | holder.itemView.setLayoutParams(rvLayoutParams); |
| 5328 | } else if (!checkLayoutParams(lp)) { |
| 5329 | rvLayoutParams = (LayoutParams) generateLayoutParams(lp); |
| 5330 | holder.itemView.setLayoutParams(rvLayoutParams); |
| 5331 | } else { |
| 5332 | rvLayoutParams = (LayoutParams) lp; |
| 5333 | } |
| 5334 | |
| 5335 | rvLayoutParams.mInsetsDirty = true; |
| 5336 | rvLayoutParams.mViewHolder = holder; |
| 5337 | rvLayoutParams.mPendingInvalidate = holder.itemView.getParent() == null; |
| 5338 | } |
| 5339 | |
| 5340 | /** |
| 5341 | * RecyclerView provides artificial position range (item count) in pre-layout state and |
| 5342 | * automatically maps these positions to {@link Adapter} positions when |
| 5343 | * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called. |
| 5344 | * <p> |
| 5345 | * Usually, LayoutManager does not need to worry about this. However, in some cases, your |
| 5346 | * LayoutManager may need to call some custom component with item positions in which |
| 5347 | * case you need the actual adapter position instead of the pre layout position. You |
| 5348 | * can use this method to convert a pre-layout position to adapter (post layout) position. |
| 5349 | * <p> |
| 5350 | * Note that if the provided position belongs to a deleted ViewHolder, this method will |
| 5351 | * return -1. |
| 5352 | * <p> |
| 5353 | * Calling this method in post-layout state returns the same value back. |
| 5354 | * |
| 5355 | * @param position The pre-layout position to convert. Must be greater or equal to 0 and |
| 5356 | * less than {@link State#getItemCount()}. |
| 5357 | */ |
| 5358 | public int convertPreLayoutPositionToPostLayout(int position) { |
| 5359 | if (position < 0 || position >= mState.getItemCount()) { |
| 5360 | throw new IndexOutOfBoundsException("invalid position " + position + ". State " |
| 5361 | + "item count is " + mState.getItemCount()); |
| 5362 | } |
| 5363 | if (!mState.isPreLayout()) { |
| 5364 | return position; |
| 5365 | } |
| 5366 | return mAdapterHelper.findPositionOffset(position); |
| 5367 | } |
| 5368 | |
| 5369 | /** |
| 5370 | * Obtain a view initialized for the given position. |
| 5371 | * |
| 5372 | * This method should be used by {@link LayoutManager} implementations to obtain |
| 5373 | * views to represent data from an {@link Adapter}. |
| 5374 | * <p> |
| 5375 | * The Recycler may reuse a scrap or detached view from a shared pool if one is |
| 5376 | * available for the correct view type. If the adapter has not indicated that the |
| 5377 | * data at the given position has changed, the Recycler will attempt to hand back |
| 5378 | * a scrap view that was previously initialized for that data without rebinding. |
| 5379 | * |
| 5380 | * @param position Position to obtain a view for |
| 5381 | * @return A view representing the data at <code>position</code> from <code>adapter</code> |
| 5382 | */ |
| 5383 | public View getViewForPosition(int position) { |
| 5384 | return getViewForPosition(position, false); |
| 5385 | } |
| 5386 | |
| 5387 | View getViewForPosition(int position, boolean dryRun) { |
| 5388 | return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; |
| 5389 | } |
| 5390 | |
| 5391 | /** |
| 5392 | * Attempts to get the ViewHolder for the given position, either from the Recycler scrap, |
| 5393 | * cache, the RecycledViewPool, or creating it directly. |
| 5394 | * <p> |
| 5395 | * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return |
| 5396 | * rather than constructing or binding a ViewHolder if it doesn't think it has time. |
| 5397 | * If a ViewHolder must be constructed and not enough time remains, null is returned. If a |
| 5398 | * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is |
| 5399 | * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this. |
| 5400 | * |
| 5401 | * @param position Position of ViewHolder to be returned. |
| 5402 | * @param dryRun True if the ViewHolder should not be removed from scrap/cache/ |
| 5403 | * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should |
| 5404 | * complete. If FOREVER_NS is passed, this method will not fail to |
| 5405 | * create/bind the holder if needed. |
| 5406 | * |
| 5407 | * @return ViewHolder for requested position |
| 5408 | */ |
| 5409 | @Nullable |
| 5410 | ViewHolder tryGetViewHolderForPositionByDeadline(int position, |
| 5411 | boolean dryRun, long deadlineNs) { |
| 5412 | if (position < 0 || position >= mState.getItemCount()) { |
| 5413 | throw new IndexOutOfBoundsException("Invalid item position " + position |
| 5414 | + "(" + position + "). Item count:" + mState.getItemCount()); |
| 5415 | } |
| 5416 | boolean fromScrapOrHiddenOrCache = false; |
| 5417 | ViewHolder holder = null; |
| 5418 | // 0) If there is a changed scrap, try to find from there |
| 5419 | if (mState.isPreLayout()) { |
| 5420 | holder = getChangedScrapViewForPosition(position); |
| 5421 | fromScrapOrHiddenOrCache = holder != null; |
| 5422 | } |
| 5423 | // 1) Find by position from scrap/hidden list/cache |
| 5424 | if (holder == null) { |
| 5425 | holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); |
| 5426 | if (holder != null) { |
| 5427 | if (!validateViewHolderForOffsetPosition(holder)) { |
| 5428 | // recycle holder (and unscrap if relevant) since it can't be used |
| 5429 | if (!dryRun) { |
| 5430 | // we would like to recycle this but need to make sure it is not used by |
| 5431 | // animation logic etc. |
| 5432 | holder.addFlags(ViewHolder.FLAG_INVALID); |
| 5433 | if (holder.isScrap()) { |
| 5434 | removeDetachedView(holder.itemView, false); |
| 5435 | holder.unScrap(); |
| 5436 | } else if (holder.wasReturnedFromScrap()) { |
| 5437 | holder.clearReturnedFromScrapFlag(); |
| 5438 | } |
| 5439 | recycleViewHolderInternal(holder); |
| 5440 | } |
| 5441 | holder = null; |
| 5442 | } else { |
| 5443 | fromScrapOrHiddenOrCache = true; |
| 5444 | } |
| 5445 | } |
| 5446 | } |
| 5447 | if (holder == null) { |
| 5448 | final int offsetPosition = mAdapterHelper.findPositionOffset(position); |
| 5449 | if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { |
| 5450 | throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " |
| 5451 | + "position " + position + "(offset:" + offsetPosition + ")." |
| 5452 | + "state:" + mState.getItemCount()); |
| 5453 | } |
| 5454 | |
| 5455 | final int type = mAdapter.getItemViewType(offsetPosition); |
| 5456 | // 2) Find from scrap/cache via stable ids, if exists |
| 5457 | if (mAdapter.hasStableIds()) { |
| 5458 | holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), |
| 5459 | type, dryRun); |
| 5460 | if (holder != null) { |
| 5461 | // update position |
| 5462 | holder.mPosition = offsetPosition; |
| 5463 | fromScrapOrHiddenOrCache = true; |
| 5464 | } |
| 5465 | } |
| 5466 | if (holder == null && mViewCacheExtension != null) { |
| 5467 | // We are NOT sending the offsetPosition because LayoutManager does not |
| 5468 | // know it. |
| 5469 | final View view = mViewCacheExtension |
| 5470 | .getViewForPositionAndType(this, position, type); |
| 5471 | if (view != null) { |
| 5472 | holder = getChildViewHolder(view); |
| 5473 | if (holder == null) { |
| 5474 | throw new IllegalArgumentException("getViewForPositionAndType returned" |
| 5475 | + " a view which does not have a ViewHolder"); |
| 5476 | } else if (holder.shouldIgnore()) { |
| 5477 | throw new IllegalArgumentException("getViewForPositionAndType returned" |
| 5478 | + " a view that is ignored. You must call stopIgnoring before" |
| 5479 | + " returning this view."); |
| 5480 | } |
| 5481 | } |
| 5482 | } |
| 5483 | if (holder == null) { // fallback to pool |
| 5484 | if (DEBUG) { |
| 5485 | Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" |
| 5486 | + position + ") fetching from shared pool"); |
| 5487 | } |
| 5488 | holder = getRecycledViewPool().getRecycledView(type); |
| 5489 | if (holder != null) { |
| 5490 | holder.resetInternal(); |
| 5491 | if (FORCE_INVALIDATE_DISPLAY_LIST) { |
| 5492 | invalidateDisplayListInt(holder); |
| 5493 | } |
| 5494 | } |
| 5495 | } |
| 5496 | if (holder == null) { |
| 5497 | long start = getNanoTime(); |
| 5498 | if (deadlineNs != FOREVER_NS |
| 5499 | && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { |
| 5500 | // abort - we have a deadline we can't meet |
| 5501 | return null; |
| 5502 | } |
| 5503 | holder = mAdapter.createViewHolder(RecyclerView.this, type); |
| 5504 | if (ALLOW_THREAD_GAP_WORK) { |
| 5505 | // only bother finding nested RV if prefetching |
| 5506 | RecyclerView innerView = findNestedRecyclerView(holder.itemView); |
| 5507 | if (innerView != null) { |
| 5508 | holder.mNestedRecyclerView = new WeakReference<>(innerView); |
| 5509 | } |
| 5510 | } |
| 5511 | |
| 5512 | long end = getNanoTime(); |
| 5513 | mRecyclerPool.factorInCreateTime(type, end - start); |
| 5514 | if (DEBUG) { |
| 5515 | Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); |
| 5516 | } |
| 5517 | } |
| 5518 | } |
| 5519 | |
| 5520 | // This is very ugly but the only place we can grab this information |
| 5521 | // before the View is rebound and returned to the LayoutManager for post layout ops. |
| 5522 | // We don't need this in pre-layout since the VH is not updated by the LM. |
| 5523 | if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder |
| 5524 | .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { |
| 5525 | holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); |
| 5526 | if (mState.mRunSimpleAnimations) { |
| 5527 | int changeFlags = ItemAnimator |
| 5528 | .buildAdapterChangeFlagsForAnimations(holder); |
| 5529 | changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; |
| 5530 | final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, |
| 5531 | holder, changeFlags, holder.getUnmodifiedPayloads()); |
| 5532 | recordAnimationInfoIfBouncedHiddenView(holder, info); |
| 5533 | } |
| 5534 | } |
| 5535 | |
| 5536 | boolean bound = false; |
| 5537 | if (mState.isPreLayout() && holder.isBound()) { |
| 5538 | // do not update unless we absolutely have to. |
| 5539 | holder.mPreLayoutPosition = position; |
| 5540 | } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { |
| 5541 | if (DEBUG && holder.isRemoved()) { |
| 5542 | throw new IllegalStateException("Removed holder should be bound and it should" |
| 5543 | + " come here only in pre-layout. Holder: " + holder); |
| 5544 | } |
| 5545 | final int offsetPosition = mAdapterHelper.findPositionOffset(position); |
| 5546 | bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); |
| 5547 | } |
| 5548 | |
| 5549 | final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); |
| 5550 | final LayoutParams rvLayoutParams; |
| 5551 | if (lp == null) { |
| 5552 | rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); |
| 5553 | holder.itemView.setLayoutParams(rvLayoutParams); |
| 5554 | } else if (!checkLayoutParams(lp)) { |
| 5555 | rvLayoutParams = (LayoutParams) generateLayoutParams(lp); |
| 5556 | holder.itemView.setLayoutParams(rvLayoutParams); |
| 5557 | } else { |
| 5558 | rvLayoutParams = (LayoutParams) lp; |
| 5559 | } |
| 5560 | rvLayoutParams.mViewHolder = holder; |
| 5561 | rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; |
| 5562 | return holder; |
| 5563 | } |
| 5564 | |
| 5565 | private void attachAccessibilityDelegate(View itemView) { |
| 5566 | if (isAccessibilityEnabled()) { |
| 5567 | if (itemView.getImportantForAccessibility() |
| 5568 | == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { |
| 5569 | itemView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); |
| 5570 | } |
| 5571 | |
| 5572 | if (itemView.getAccessibilityDelegate() == null) { |
| 5573 | itemView.setAccessibilityDelegate(mAccessibilityDelegate.getItemDelegate()); |
| 5574 | } |
| 5575 | } |
| 5576 | } |
| 5577 | |
| 5578 | private void invalidateDisplayListInt(ViewHolder holder) { |
| 5579 | if (holder.itemView instanceof ViewGroup) { |
| 5580 | invalidateDisplayListInt((ViewGroup) holder.itemView, false); |
| 5581 | } |
| 5582 | } |
| 5583 | |
| 5584 | private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) { |
| 5585 | for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) { |
| 5586 | final View view = viewGroup.getChildAt(i); |
| 5587 | if (view instanceof ViewGroup) { |
| 5588 | invalidateDisplayListInt((ViewGroup) view, true); |
| 5589 | } |
| 5590 | } |
| 5591 | if (!invalidateThis) { |
| 5592 | return; |
| 5593 | } |
| 5594 | // we need to force it to become invisible |
| 5595 | if (viewGroup.getVisibility() == View.INVISIBLE) { |
| 5596 | viewGroup.setVisibility(View.VISIBLE); |
| 5597 | viewGroup.setVisibility(View.INVISIBLE); |
| 5598 | } else { |
| 5599 | final int visibility = viewGroup.getVisibility(); |
| 5600 | viewGroup.setVisibility(View.INVISIBLE); |
| 5601 | viewGroup.setVisibility(visibility); |
| 5602 | } |
| 5603 | } |
| 5604 | |
| 5605 | /** |
| 5606 | * Recycle a detached view. The specified view will be added to a pool of views |
| 5607 | * for later rebinding and reuse. |
| 5608 | * |
| 5609 | * <p>A view must be fully detached (removed from parent) before it may be recycled. If the |
| 5610 | * View is scrapped, it will be removed from scrap list.</p> |
| 5611 | * |
| 5612 | * @param view Removed view for recycling |
| 5613 | * @see LayoutManager#removeAndRecycleView(View, Recycler) |
| 5614 | */ |
| 5615 | public void recycleView(View view) { |
| 5616 | // This public recycle method tries to make view recycle-able since layout manager |
| 5617 | // intended to recycle this view (e.g. even if it is in scrap or change cache) |
| 5618 | ViewHolder holder = getChildViewHolderInt(view); |
| 5619 | if (holder.isTmpDetached()) { |
| 5620 | removeDetachedView(view, false); |
| 5621 | } |
| 5622 | if (holder.isScrap()) { |
| 5623 | holder.unScrap(); |
| 5624 | } else if (holder.wasReturnedFromScrap()) { |
| 5625 | holder.clearReturnedFromScrapFlag(); |
| 5626 | } |
| 5627 | recycleViewHolderInternal(holder); |
| 5628 | } |
| 5629 | |
| 5630 | /** |
| 5631 | * Internally, use this method instead of {@link #recycleView(android.view.View)} to |
| 5632 | * catch potential bugs. |
| 5633 | * @param view |
| 5634 | */ |
| 5635 | void recycleViewInternal(View view) { |
| 5636 | recycleViewHolderInternal(getChildViewHolderInt(view)); |
| 5637 | } |
| 5638 | |
| 5639 | void recycleAndClearCachedViews() { |
| 5640 | final int count = mCachedViews.size(); |
| 5641 | for (int i = count - 1; i >= 0; i--) { |
| 5642 | recycleCachedViewAt(i); |
| 5643 | } |
| 5644 | mCachedViews.clear(); |
| 5645 | if (ALLOW_THREAD_GAP_WORK) { |
| 5646 | mPrefetchRegistry.clearPrefetchPositions(); |
| 5647 | } |
| 5648 | } |
| 5649 | |
| 5650 | /** |
| 5651 | * Recycles a cached view and removes the view from the list. Views are added to cache |
| 5652 | * if and only if they are recyclable, so this method does not check it again. |
| 5653 | * <p> |
| 5654 | * A small exception to this rule is when the view does not have an animator reference |
| 5655 | * but transient state is true (due to animations created outside ItemAnimator). In that |
| 5656 | * case, adapter may choose to recycle it. From RecyclerView's perspective, the view is |
| 5657 | * still recyclable since Adapter wants to do so. |
| 5658 | * |
| 5659 | * @param cachedViewIndex The index of the view in cached views list |
| 5660 | */ |
| 5661 | void recycleCachedViewAt(int cachedViewIndex) { |
| 5662 | if (DEBUG) { |
| 5663 | Log.d(TAG, "Recycling cached view at index " + cachedViewIndex); |
| 5664 | } |
| 5665 | ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); |
| 5666 | if (DEBUG) { |
| 5667 | Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); |
| 5668 | } |
| 5669 | addViewHolderToRecycledViewPool(viewHolder, true); |
| 5670 | mCachedViews.remove(cachedViewIndex); |
| 5671 | } |
| 5672 | |
| 5673 | /** |
| 5674 | * internal implementation checks if view is scrapped or attached and throws an exception |
| 5675 | * if so. |
| 5676 | * Public version un-scraps before calling recycle. |
| 5677 | */ |
| 5678 | void recycleViewHolderInternal(ViewHolder holder) { |
| 5679 | if (holder.isScrap() || holder.itemView.getParent() != null) { |
| 5680 | throw new IllegalArgumentException( |
| 5681 | "Scrapped or attached views may not be recycled. isScrap:" |
| 5682 | + holder.isScrap() + " isAttached:" |
| 5683 | + (holder.itemView.getParent() != null)); |
| 5684 | } |
| 5685 | |
| 5686 | if (holder.isTmpDetached()) { |
| 5687 | throw new IllegalArgumentException("Tmp detached view should be removed " |
| 5688 | + "from RecyclerView before it can be recycled: " + holder); |
| 5689 | } |
| 5690 | |
| 5691 | if (holder.shouldIgnore()) { |
| 5692 | throw new IllegalArgumentException("Trying to recycle an ignored view holder. You" |
| 5693 | + " should first call stopIgnoringView(view) before calling recycle."); |
| 5694 | } |
| 5695 | //noinspection unchecked |
| 5696 | final boolean transientStatePreventsRecycling = holder |
| 5697 | .doesTransientStatePreventRecycling(); |
| 5698 | final boolean forceRecycle = mAdapter != null |
| 5699 | && transientStatePreventsRecycling |
| 5700 | && mAdapter.onFailedToRecycleView(holder); |
| 5701 | boolean cached = false; |
| 5702 | boolean recycled = false; |
| 5703 | if (DEBUG && mCachedViews.contains(holder)) { |
| 5704 | throw new IllegalArgumentException("cached view received recycle internal? " |
| 5705 | + holder); |
| 5706 | } |
| 5707 | if (forceRecycle || holder.isRecyclable()) { |
| 5708 | if (mViewCacheMax > 0 |
| 5709 | && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID |
| 5710 | | ViewHolder.FLAG_REMOVED |
| 5711 | | ViewHolder.FLAG_UPDATE |
| 5712 | | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { |
| 5713 | // Retire oldest cached view |
| 5714 | int cachedViewSize = mCachedViews.size(); |
| 5715 | if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { |
| 5716 | recycleCachedViewAt(0); |
| 5717 | cachedViewSize--; |
| 5718 | } |
| 5719 | |
| 5720 | int targetCacheIndex = cachedViewSize; |
| 5721 | if (ALLOW_THREAD_GAP_WORK |
| 5722 | && cachedViewSize > 0 |
| 5723 | && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) { |
| 5724 | // when adding the view, skip past most recently prefetched views |
| 5725 | int cacheIndex = cachedViewSize - 1; |
| 5726 | while (cacheIndex >= 0) { |
| 5727 | int cachedPos = mCachedViews.get(cacheIndex).mPosition; |
| 5728 | if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) { |
| 5729 | break; |
| 5730 | } |
| 5731 | cacheIndex--; |
| 5732 | } |
| 5733 | targetCacheIndex = cacheIndex + 1; |
| 5734 | } |
| 5735 | mCachedViews.add(targetCacheIndex, holder); |
| 5736 | cached = true; |
| 5737 | } |
| 5738 | if (!cached) { |
| 5739 | addViewHolderToRecycledViewPool(holder, true); |
| 5740 | recycled = true; |
| 5741 | } |
| 5742 | } else { |
| 5743 | // NOTE: A view can fail to be recycled when it is scrolled off while an animation |
| 5744 | // runs. In this case, the item is eventually recycled by |
| 5745 | // ItemAnimatorRestoreListener#onAnimationFinished. |
| 5746 | |
| 5747 | // TODO: consider cancelling an animation when an item is removed scrollBy, |
| 5748 | // to return it to the pool faster |
| 5749 | if (DEBUG) { |
| 5750 | Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " |
| 5751 | + "re-visit here. We are still removing it from animation lists"); |
| 5752 | } |
| 5753 | } |
| 5754 | // even if the holder is not removed, we still call this method so that it is removed |
| 5755 | // from view holder lists. |
| 5756 | mViewInfoStore.removeViewHolder(holder); |
| 5757 | if (!cached && !recycled && transientStatePreventsRecycling) { |
| 5758 | holder.mOwnerRecyclerView = null; |
| 5759 | } |
| 5760 | } |
| 5761 | |
| 5762 | /** |
| 5763 | * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool. |
| 5764 | * |
| 5765 | * Pass false to dispatchRecycled for views that have not been bound. |
| 5766 | * |
| 5767 | * @param holder Holder to be added to the pool. |
| 5768 | * @param dispatchRecycled True to dispatch View recycled callbacks. |
| 5769 | */ |
| 5770 | void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) { |
| 5771 | clearNestedRecyclerViewIfNotNested(holder); |
| 5772 | holder.itemView.setAccessibilityDelegate(null); |
| 5773 | if (dispatchRecycled) { |
| 5774 | dispatchViewRecycled(holder); |
| 5775 | } |
| 5776 | holder.mOwnerRecyclerView = null; |
| 5777 | getRecycledViewPool().putRecycledView(holder); |
| 5778 | } |
| 5779 | |
| 5780 | /** |
| 5781 | * Used as a fast path for unscrapping and recycling a view during a bulk operation. |
| 5782 | * The caller must call {@link #clearScrap()} when it's done to update the recycler's |
| 5783 | * internal bookkeeping. |
| 5784 | */ |
| 5785 | void quickRecycleScrapView(View view) { |
| 5786 | final ViewHolder holder = getChildViewHolderInt(view); |
| 5787 | holder.mScrapContainer = null; |
| 5788 | holder.mInChangeScrap = false; |
| 5789 | holder.clearReturnedFromScrapFlag(); |
| 5790 | recycleViewHolderInternal(holder); |
| 5791 | } |
| 5792 | |
| 5793 | /** |
| 5794 | * Mark an attached view as scrap. |
| 5795 | * |
| 5796 | * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible |
| 5797 | * for rebinding and reuse. Requests for a view for a given position may return a |
| 5798 | * reused or rebound scrap view instance.</p> |
| 5799 | * |
| 5800 | * @param view View to scrap |
| 5801 | */ |
| 5802 | void scrapView(View view) { |
| 5803 | final ViewHolder holder = getChildViewHolderInt(view); |
| 5804 | if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) |
| 5805 | || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { |
| 5806 | if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { |
| 5807 | throw new IllegalArgumentException("Called scrap view with an invalid view." |
| 5808 | + " Invalid views cannot be reused from scrap, they should rebound from" |
| 5809 | + " recycler pool."); |
| 5810 | } |
| 5811 | holder.setScrapContainer(this, false); |
| 5812 | mAttachedScrap.add(holder); |
| 5813 | } else { |
| 5814 | if (mChangedScrap == null) { |
| 5815 | mChangedScrap = new ArrayList<ViewHolder>(); |
| 5816 | } |
| 5817 | holder.setScrapContainer(this, true); |
| 5818 | mChangedScrap.add(holder); |
| 5819 | } |
| 5820 | } |
| 5821 | |
| 5822 | /** |
| 5823 | * Remove a previously scrapped view from the pool of eligible scrap. |
| 5824 | * |
| 5825 | * <p>This view will no longer be eligible for reuse until re-scrapped or |
| 5826 | * until it is explicitly removed and recycled.</p> |
| 5827 | */ |
| 5828 | void unscrapView(ViewHolder holder) { |
| 5829 | if (holder.mInChangeScrap) { |
| 5830 | mChangedScrap.remove(holder); |
| 5831 | } else { |
| 5832 | mAttachedScrap.remove(holder); |
| 5833 | } |
| 5834 | holder.mScrapContainer = null; |
| 5835 | holder.mInChangeScrap = false; |
| 5836 | holder.clearReturnedFromScrapFlag(); |
| 5837 | } |
| 5838 | |
| 5839 | int getScrapCount() { |
| 5840 | return mAttachedScrap.size(); |
| 5841 | } |
| 5842 | |
| 5843 | View getScrapViewAt(int index) { |
| 5844 | return mAttachedScrap.get(index).itemView; |
| 5845 | } |
| 5846 | |
| 5847 | void clearScrap() { |
| 5848 | mAttachedScrap.clear(); |
| 5849 | if (mChangedScrap != null) { |
| 5850 | mChangedScrap.clear(); |
| 5851 | } |
| 5852 | } |
| 5853 | |
| 5854 | ViewHolder getChangedScrapViewForPosition(int position) { |
| 5855 | // If pre-layout, check the changed scrap for an exact match. |
| 5856 | final int changedScrapSize; |
| 5857 | if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) { |
| 5858 | return null; |
| 5859 | } |
| 5860 | // find by position |
| 5861 | for (int i = 0; i < changedScrapSize; i++) { |
| 5862 | final ViewHolder holder = mChangedScrap.get(i); |
| 5863 | if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) { |
| 5864 | holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); |
| 5865 | return holder; |
| 5866 | } |
| 5867 | } |
| 5868 | // find by id |
| 5869 | if (mAdapter.hasStableIds()) { |
| 5870 | final int offsetPosition = mAdapterHelper.findPositionOffset(position); |
| 5871 | if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) { |
| 5872 | final long id = mAdapter.getItemId(offsetPosition); |
| 5873 | for (int i = 0; i < changedScrapSize; i++) { |
| 5874 | final ViewHolder holder = mChangedScrap.get(i); |
| 5875 | if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) { |
| 5876 | holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); |
| 5877 | return holder; |
| 5878 | } |
| 5879 | } |
| 5880 | } |
| 5881 | } |
| 5882 | return null; |
| 5883 | } |
| 5884 | |
| 5885 | /** |
| 5886 | * Returns a view for the position either from attach scrap, hidden children, or cache. |
| 5887 | * |
| 5888 | * @param position Item position |
| 5889 | * @param dryRun Does a dry run, finds the ViewHolder but does not remove |
| 5890 | * @return a ViewHolder that can be re-used for this position. |
| 5891 | */ |
| 5892 | ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { |
| 5893 | final int scrapCount = mAttachedScrap.size(); |
| 5894 | |
| 5895 | // Try first for an exact, non-invalid match from scrap. |
| 5896 | for (int i = 0; i < scrapCount; i++) { |
| 5897 | final ViewHolder holder = mAttachedScrap.get(i); |
| 5898 | if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position |
| 5899 | && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { |
| 5900 | holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); |
| 5901 | return holder; |
| 5902 | } |
| 5903 | } |
| 5904 | |
| 5905 | if (!dryRun) { |
| 5906 | View view = mChildHelper.findHiddenNonRemovedView(position); |
| 5907 | if (view != null) { |
| 5908 | // This View is good to be used. We just need to unhide, detach and move to the |
| 5909 | // scrap list. |
| 5910 | final ViewHolder vh = getChildViewHolderInt(view); |
| 5911 | mChildHelper.unhide(view); |
| 5912 | int layoutIndex = mChildHelper.indexOfChild(view); |
| 5913 | if (layoutIndex == RecyclerView.NO_POSITION) { |
| 5914 | throw new IllegalStateException("layout index should not be -1 after " |
| 5915 | + "unhiding a view:" + vh); |
| 5916 | } |
| 5917 | mChildHelper.detachViewFromParent(layoutIndex); |
| 5918 | scrapView(view); |
| 5919 | vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP |
| 5920 | | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); |
| 5921 | return vh; |
| 5922 | } |
| 5923 | } |
| 5924 | |
| 5925 | // Search in our first-level recycled view cache. |
| 5926 | final int cacheSize = mCachedViews.size(); |
| 5927 | for (int i = 0; i < cacheSize; i++) { |
| 5928 | final ViewHolder holder = mCachedViews.get(i); |
| 5929 | // invalid view holders may be in cache if adapter has stable ids as they can be |
| 5930 | // retrieved via getScrapOrCachedViewForId |
| 5931 | if (!holder.isInvalid() && holder.getLayoutPosition() == position) { |
| 5932 | if (!dryRun) { |
| 5933 | mCachedViews.remove(i); |
| 5934 | } |
| 5935 | if (DEBUG) { |
| 5936 | Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position |
| 5937 | + ") found match in cache: " + holder); |
| 5938 | } |
| 5939 | return holder; |
| 5940 | } |
| 5941 | } |
| 5942 | return null; |
| 5943 | } |
| 5944 | |
| 5945 | ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) { |
| 5946 | // Look in our attached views first |
| 5947 | final int count = mAttachedScrap.size(); |
| 5948 | for (int i = count - 1; i >= 0; i--) { |
| 5949 | final ViewHolder holder = mAttachedScrap.get(i); |
| 5950 | if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) { |
| 5951 | if (type == holder.getItemViewType()) { |
| 5952 | holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); |
| 5953 | if (holder.isRemoved()) { |
| 5954 | // this might be valid in two cases: |
| 5955 | // > item is removed but we are in pre-layout pass |
| 5956 | // >> do nothing. return as is. make sure we don't rebind |
| 5957 | // > item is removed then added to another position and we are in |
| 5958 | // post layout. |
| 5959 | // >> remove removed and invalid flags, add update flag to rebind |
| 5960 | // because item was invisible to us and we don't know what happened in |
| 5961 | // between. |
| 5962 | if (!mState.isPreLayout()) { |
| 5963 | holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE |
| 5964 | | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED); |
| 5965 | } |
| 5966 | } |
| 5967 | return holder; |
| 5968 | } else if (!dryRun) { |
| 5969 | // if we are running animations, it is actually better to keep it in scrap |
| 5970 | // but this would force layout manager to lay it out which would be bad. |
| 5971 | // Recycle this scrap. Type mismatch. |
| 5972 | mAttachedScrap.remove(i); |
| 5973 | removeDetachedView(holder.itemView, false); |
| 5974 | quickRecycleScrapView(holder.itemView); |
| 5975 | } |
| 5976 | } |
| 5977 | } |
| 5978 | |
| 5979 | // Search the first-level cache |
| 5980 | final int cacheSize = mCachedViews.size(); |
| 5981 | for (int i = cacheSize - 1; i >= 0; i--) { |
| 5982 | final ViewHolder holder = mCachedViews.get(i); |
| 5983 | if (holder.getItemId() == id) { |
| 5984 | if (type == holder.getItemViewType()) { |
| 5985 | if (!dryRun) { |
| 5986 | mCachedViews.remove(i); |
| 5987 | } |
| 5988 | return holder; |
| 5989 | } else if (!dryRun) { |
| 5990 | recycleCachedViewAt(i); |
| 5991 | return null; |
| 5992 | } |
| 5993 | } |
| 5994 | } |
| 5995 | return null; |
| 5996 | } |
| 5997 | |
| 5998 | void dispatchViewRecycled(ViewHolder holder) { |
| 5999 | if (mRecyclerListener != null) { |
| 6000 | mRecyclerListener.onViewRecycled(holder); |
| 6001 | } |
| 6002 | if (mAdapter != null) { |
| 6003 | mAdapter.onViewRecycled(holder); |
| 6004 | } |
| 6005 | if (mState != null) { |
| 6006 | mViewInfoStore.removeViewHolder(holder); |
| 6007 | } |
| 6008 | if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder); |
| 6009 | } |
| 6010 | |
| 6011 | void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, |
| 6012 | boolean compatibleWithPrevious) { |
| 6013 | clear(); |
| 6014 | getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious); |
| 6015 | } |
| 6016 | |
| 6017 | void offsetPositionRecordsForMove(int from, int to) { |
| 6018 | final int start, end, inBetweenOffset; |
| 6019 | if (from < to) { |
| 6020 | start = from; |
| 6021 | end = to; |
| 6022 | inBetweenOffset = -1; |
| 6023 | } else { |
| 6024 | start = to; |
| 6025 | end = from; |
| 6026 | inBetweenOffset = 1; |
| 6027 | } |
| 6028 | final int cachedCount = mCachedViews.size(); |
| 6029 | for (int i = 0; i < cachedCount; i++) { |
| 6030 | final ViewHolder holder = mCachedViews.get(i); |
| 6031 | if (holder == null || holder.mPosition < start || holder.mPosition > end) { |
| 6032 | continue; |
| 6033 | } |
| 6034 | if (holder.mPosition == from) { |
| 6035 | holder.offsetPosition(to - from, false); |
| 6036 | } else { |
| 6037 | holder.offsetPosition(inBetweenOffset, false); |
| 6038 | } |
| 6039 | if (DEBUG) { |
| 6040 | Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " |
| 6041 | + holder); |
| 6042 | } |
| 6043 | } |
| 6044 | } |
| 6045 | |
| 6046 | void offsetPositionRecordsForInsert(int insertedAt, int count) { |
| 6047 | final int cachedCount = mCachedViews.size(); |
| 6048 | for (int i = 0; i < cachedCount; i++) { |
| 6049 | final ViewHolder holder = mCachedViews.get(i); |
| 6050 | if (holder != null && holder.mPosition >= insertedAt) { |
| 6051 | if (DEBUG) { |
| 6052 | Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " |
| 6053 | + holder + " now at position " + (holder.mPosition + count)); |
| 6054 | } |
| 6055 | holder.offsetPosition(count, true); |
| 6056 | } |
| 6057 | } |
| 6058 | } |
| 6059 | |
| 6060 | /** |
| 6061 | * @param removedFrom Remove start index |
| 6062 | * @param count Remove count |
| 6063 | * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if |
| 6064 | * false, they'll be applied before the second layout pass |
| 6065 | */ |
| 6066 | void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) { |
| 6067 | final int removedEnd = removedFrom + count; |
| 6068 | final int cachedCount = mCachedViews.size(); |
| 6069 | for (int i = cachedCount - 1; i >= 0; i--) { |
| 6070 | final ViewHolder holder = mCachedViews.get(i); |
| 6071 | if (holder != null) { |
| 6072 | if (holder.mPosition >= removedEnd) { |
| 6073 | if (DEBUG) { |
| 6074 | Log.d(TAG, "offsetPositionRecordsForRemove cached " + i |
| 6075 | + " holder " + holder + " now at position " |
| 6076 | + (holder.mPosition - count)); |
| 6077 | } |
| 6078 | holder.offsetPosition(-count, applyToPreLayout); |
| 6079 | } else if (holder.mPosition >= removedFrom) { |
| 6080 | // Item for this view was removed. Dump it from the cache. |
| 6081 | holder.addFlags(ViewHolder.FLAG_REMOVED); |
| 6082 | recycleCachedViewAt(i); |
| 6083 | } |
| 6084 | } |
| 6085 | } |
| 6086 | } |
| 6087 | |
| 6088 | void setViewCacheExtension(ViewCacheExtension extension) { |
| 6089 | mViewCacheExtension = extension; |
| 6090 | } |
| 6091 | |
| 6092 | void setRecycledViewPool(RecycledViewPool pool) { |
| 6093 | if (mRecyclerPool != null) { |
| 6094 | mRecyclerPool.detach(); |
| 6095 | } |
| 6096 | mRecyclerPool = pool; |
| 6097 | if (pool != null) { |
| 6098 | mRecyclerPool.attach(getAdapter()); |
| 6099 | } |
| 6100 | } |
| 6101 | |
| 6102 | RecycledViewPool getRecycledViewPool() { |
| 6103 | if (mRecyclerPool == null) { |
| 6104 | mRecyclerPool = new RecycledViewPool(); |
| 6105 | } |
| 6106 | return mRecyclerPool; |
| 6107 | } |
| 6108 | |
| 6109 | void viewRangeUpdate(int positionStart, int itemCount) { |
| 6110 | final int positionEnd = positionStart + itemCount; |
| 6111 | final int cachedCount = mCachedViews.size(); |
| 6112 | for (int i = cachedCount - 1; i >= 0; i--) { |
| 6113 | final ViewHolder holder = mCachedViews.get(i); |
| 6114 | if (holder == null) { |
| 6115 | continue; |
| 6116 | } |
| 6117 | |
| 6118 | final int pos = holder.getLayoutPosition(); |
| 6119 | if (pos >= positionStart && pos < positionEnd) { |
| 6120 | holder.addFlags(ViewHolder.FLAG_UPDATE); |
| 6121 | recycleCachedViewAt(i); |
| 6122 | // cached views should not be flagged as changed because this will cause them |
| 6123 | // to animate when they are returned from cache. |
| 6124 | } |
| 6125 | } |
| 6126 | } |
| 6127 | |
| 6128 | void setAdapterPositionsAsUnknown() { |
| 6129 | final int cachedCount = mCachedViews.size(); |
| 6130 | for (int i = 0; i < cachedCount; i++) { |
| 6131 | final ViewHolder holder = mCachedViews.get(i); |
| 6132 | if (holder != null) { |
| 6133 | holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); |
| 6134 | } |
| 6135 | } |
| 6136 | } |
| 6137 | |
| 6138 | void markKnownViewsInvalid() { |
| 6139 | if (mAdapter != null && mAdapter.hasStableIds()) { |
| 6140 | final int cachedCount = mCachedViews.size(); |
| 6141 | for (int i = 0; i < cachedCount; i++) { |
| 6142 | final ViewHolder holder = mCachedViews.get(i); |
| 6143 | if (holder != null) { |
| 6144 | holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID); |
| 6145 | holder.addChangePayload(null); |
| 6146 | } |
| 6147 | } |
| 6148 | } else { |
| 6149 | // we cannot re-use cached views in this case. Recycle them all |
| 6150 | recycleAndClearCachedViews(); |
| 6151 | } |
| 6152 | } |
| 6153 | |
| 6154 | void clearOldPositions() { |
| 6155 | final int cachedCount = mCachedViews.size(); |
| 6156 | for (int i = 0; i < cachedCount; i++) { |
| 6157 | final ViewHolder holder = mCachedViews.get(i); |
| 6158 | holder.clearOldPosition(); |
| 6159 | } |
| 6160 | final int scrapCount = mAttachedScrap.size(); |
| 6161 | for (int i = 0; i < scrapCount; i++) { |
| 6162 | mAttachedScrap.get(i).clearOldPosition(); |
| 6163 | } |
| 6164 | if (mChangedScrap != null) { |
| 6165 | final int changedScrapCount = mChangedScrap.size(); |
| 6166 | for (int i = 0; i < changedScrapCount; i++) { |
| 6167 | mChangedScrap.get(i).clearOldPosition(); |
| 6168 | } |
| 6169 | } |
| 6170 | } |
| 6171 | |
| 6172 | void markItemDecorInsetsDirty() { |
| 6173 | final int cachedCount = mCachedViews.size(); |
| 6174 | for (int i = 0; i < cachedCount; i++) { |
| 6175 | final ViewHolder holder = mCachedViews.get(i); |
| 6176 | LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); |
| 6177 | if (layoutParams != null) { |
| 6178 | layoutParams.mInsetsDirty = true; |
| 6179 | } |
| 6180 | } |
| 6181 | } |
| 6182 | } |
| 6183 | |
| 6184 | /** |
| 6185 | * ViewCacheExtension is a helper class to provide an additional layer of view caching that can |
| 6186 | * be controlled by the developer. |
| 6187 | * <p> |
| 6188 | * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and |
| 6189 | * first level cache to find a matching View. If it cannot find a suitable View, Recycler will |
| 6190 | * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking |
| 6191 | * {@link RecycledViewPool}. |
| 6192 | * <p> |
| 6193 | * Note that, Recycler never sends Views to this method to be cached. It is developers |
| 6194 | * responsibility to decide whether they want to keep their Views in this custom cache or let |
| 6195 | * the default recycling policy handle it. |
| 6196 | */ |
| 6197 | public abstract static class ViewCacheExtension { |
| 6198 | |
| 6199 | /** |
| 6200 | * Returns a View that can be binded to the given Adapter position. |
| 6201 | * <p> |
| 6202 | * This method should <b>not</b> create a new View. Instead, it is expected to return |
| 6203 | * an already created View that can be re-used for the given type and position. |
| 6204 | * If the View is marked as ignored, it should first call |
| 6205 | * {@link LayoutManager#stopIgnoringView(View)} before returning the View. |
| 6206 | * <p> |
| 6207 | * RecyclerView will re-bind the returned View to the position if necessary. |
| 6208 | * |
| 6209 | * @param recycler The Recycler that can be used to bind the View |
| 6210 | * @param position The adapter position |
| 6211 | * @param type The type of the View, defined by adapter |
| 6212 | * @return A View that is bound to the given position or NULL if there is no View to re-use |
| 6213 | * @see LayoutManager#ignoreView(View) |
| 6214 | */ |
| 6215 | public abstract View getViewForPositionAndType(Recycler recycler, int position, int type); |
| 6216 | } |
| 6217 | |
| 6218 | /** |
| 6219 | * Base class for an Adapter |
| 6220 | * |
| 6221 | * <p>Adapters provide a binding from an app-specific data set to views that are displayed |
| 6222 | * within a {@link RecyclerView}.</p> |
| 6223 | * |
| 6224 | * @param <VH> A class that extends ViewHolder that will be used by the adapter. |
| 6225 | */ |
| 6226 | public abstract static class Adapter<VH extends ViewHolder> { |
| 6227 | private final AdapterDataObservable mObservable = new AdapterDataObservable(); |
| 6228 | private boolean mHasStableIds = false; |
| 6229 | |
| 6230 | /** |
| 6231 | * Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent |
| 6232 | * an item. |
| 6233 | * <p> |
| 6234 | * This new ViewHolder should be constructed with a new View that can represent the items |
| 6235 | * of the given type. You can either create a new View manually or inflate it from an XML |
| 6236 | * layout file. |
| 6237 | * <p> |
| 6238 | * The new ViewHolder will be used to display items of the adapter using |
| 6239 | * {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display |
| 6240 | * different items in the data set, it is a good idea to cache references to sub views of |
| 6241 | * the View to avoid unnecessary {@link View#findViewById(int)} calls. |
| 6242 | * |
| 6243 | * @param parent The ViewGroup into which the new View will be added after it is bound to |
| 6244 | * an adapter position. |
| 6245 | * @param viewType The view type of the new View. |
| 6246 | * |
| 6247 | * @return A new ViewHolder that holds a View of the given view type. |
| 6248 | * @see #getItemViewType(int) |
| 6249 | * @see #onBindViewHolder(ViewHolder, int) |
| 6250 | */ |
| 6251 | public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); |
| 6252 | |
| 6253 | /** |
| 6254 | * Called by RecyclerView to display the data at the specified position. This method should |
| 6255 | * update the contents of the {@link ViewHolder#itemView} to reflect the item at the given |
| 6256 | * position. |
| 6257 | * <p> |
| 6258 | * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method |
| 6259 | * again if the position of the item changes in the data set unless the item itself is |
| 6260 | * invalidated or the new position cannot be determined. For this reason, you should only |
| 6261 | * use the <code>position</code> parameter while acquiring the related data item inside |
| 6262 | * this method and should not keep a copy of it. If you need the position of an item later |
| 6263 | * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will |
| 6264 | * have the updated adapter position. |
| 6265 | * |
| 6266 | * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can |
| 6267 | * handle efficient partial bind. |
| 6268 | * |
| 6269 | * @param holder The ViewHolder which should be updated to represent the contents of the |
| 6270 | * item at the given position in the data set. |
| 6271 | * @param position The position of the item within the adapter's data set. |
| 6272 | */ |
| 6273 | public abstract void onBindViewHolder(VH holder, int position); |
| 6274 | |
| 6275 | /** |
| 6276 | * Called by RecyclerView to display the data at the specified position. This method |
| 6277 | * should update the contents of the {@link ViewHolder#itemView} to reflect the item at |
| 6278 | * the given position. |
| 6279 | * <p> |
| 6280 | * Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method |
| 6281 | * again if the position of the item changes in the data set unless the item itself is |
| 6282 | * invalidated or the new position cannot be determined. For this reason, you should only |
| 6283 | * use the <code>position</code> parameter while acquiring the related data item inside |
| 6284 | * this method and should not keep a copy of it. If you need the position of an item later |
| 6285 | * on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will |
| 6286 | * have the updated adapter position. |
| 6287 | * <p> |
| 6288 | * Partial bind vs full bind: |
| 6289 | * <p> |
| 6290 | * The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or |
| 6291 | * {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty, |
| 6292 | * the ViewHolder is currently bound to old data and Adapter may run an efficient partial |
| 6293 | * update using the payload info. If the payload is empty, Adapter must run a full bind. |
| 6294 | * Adapter should not assume that the payload passed in notify methods will be received by |
| 6295 | * onBindViewHolder(). For example when the view is not attached to the screen, the |
| 6296 | * payload in notifyItemChange() will be simply dropped. |
| 6297 | * |
| 6298 | * @param holder The ViewHolder which should be updated to represent the contents of the |
| 6299 | * item at the given position in the data set. |
| 6300 | * @param position The position of the item within the adapter's data set. |
| 6301 | * @param payloads A non-null list of merged payloads. Can be empty list if requires full |
| 6302 | * update. |
| 6303 | */ |
| 6304 | public void onBindViewHolder(VH holder, int position, List<Object> payloads) { |
| 6305 | onBindViewHolder(holder, position); |
| 6306 | } |
| 6307 | |
| 6308 | /** |
| 6309 | * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new |
| 6310 | * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. |
| 6311 | * |
| 6312 | * @see #onCreateViewHolder(ViewGroup, int) |
| 6313 | */ |
| 6314 | public final VH createViewHolder(ViewGroup parent, int viewType) { |
| 6315 | Trace.beginSection(TRACE_CREATE_VIEW_TAG); |
| 6316 | final VH holder = onCreateViewHolder(parent, viewType); |
| 6317 | holder.mItemViewType = viewType; |
| 6318 | Trace.endSection(); |
| 6319 | return holder; |
| 6320 | } |
| 6321 | |
| 6322 | /** |
| 6323 | * This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the |
| 6324 | * {@link ViewHolder} contents with the item at the given position and also sets up some |
| 6325 | * private fields to be used by RecyclerView. |
| 6326 | * |
| 6327 | * @see #onBindViewHolder(ViewHolder, int) |
| 6328 | */ |
| 6329 | public final void bindViewHolder(VH holder, int position) { |
| 6330 | holder.mPosition = position; |
| 6331 | if (hasStableIds()) { |
| 6332 | holder.mItemId = getItemId(position); |
| 6333 | } |
| 6334 | holder.setFlags(ViewHolder.FLAG_BOUND, |
| 6335 | ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID |
| 6336 | | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); |
| 6337 | Trace.beginSection(TRACE_BIND_VIEW_TAG); |
| 6338 | onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); |
| 6339 | holder.clearPayload(); |
| 6340 | final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); |
| 6341 | if (layoutParams instanceof RecyclerView.LayoutParams) { |
| 6342 | ((LayoutParams) layoutParams).mInsetsDirty = true; |
| 6343 | } |
| 6344 | Trace.endSection(); |
| 6345 | } |
| 6346 | |
| 6347 | /** |
| 6348 | * Return the view type of the item at <code>position</code> for the purposes |
| 6349 | * of view recycling. |
| 6350 | * |
| 6351 | * <p>The default implementation of this method returns 0, making the assumption of |
| 6352 | * a single view type for the adapter. Unlike ListView adapters, types need not |
| 6353 | * be contiguous. Consider using id resources to uniquely identify item view types. |
| 6354 | * |
| 6355 | * @param position position to query |
| 6356 | * @return integer value identifying the type of the view needed to represent the item at |
| 6357 | * <code>position</code>. Type codes need not be contiguous. |
| 6358 | */ |
| 6359 | public int getItemViewType(int position) { |
| 6360 | return 0; |
| 6361 | } |
| 6362 | |
| 6363 | /** |
| 6364 | * Indicates whether each item in the data set can be represented with a unique identifier |
| 6365 | * of type {@link java.lang.Long}. |
| 6366 | * |
| 6367 | * @param hasStableIds Whether items in data set have unique identifiers or not. |
| 6368 | * @see #hasStableIds() |
| 6369 | * @see #getItemId(int) |
| 6370 | */ |
| 6371 | public void setHasStableIds(boolean hasStableIds) { |
| 6372 | if (hasObservers()) { |
| 6373 | throw new IllegalStateException("Cannot change whether this adapter has " |
| 6374 | + "stable IDs while the adapter has registered observers."); |
| 6375 | } |
| 6376 | mHasStableIds = hasStableIds; |
| 6377 | } |
| 6378 | |
| 6379 | /** |
| 6380 | * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()} |
| 6381 | * would return false this method should return {@link #NO_ID}. The default implementation |
| 6382 | * of this method returns {@link #NO_ID}. |
| 6383 | * |
| 6384 | * @param position Adapter position to query |
| 6385 | * @return the stable ID of the item at position |
| 6386 | */ |
| 6387 | public long getItemId(int position) { |
| 6388 | return NO_ID; |
| 6389 | } |
| 6390 | |
| 6391 | /** |
| 6392 | * Returns the total number of items in the data set held by the adapter. |
| 6393 | * |
| 6394 | * @return The total number of items in this adapter. |
| 6395 | */ |
| 6396 | public abstract int getItemCount(); |
| 6397 | |
| 6398 | /** |
| 6399 | * Returns true if this adapter publishes a unique <code>long</code> value that can |
| 6400 | * act as a key for the item at a given position in the data set. If that item is relocated |
| 6401 | * in the data set, the ID returned for that item should be the same. |
| 6402 | * |
| 6403 | * @return true if this adapter's items have stable IDs |
| 6404 | */ |
| 6405 | public final boolean hasStableIds() { |
| 6406 | return mHasStableIds; |
| 6407 | } |
| 6408 | |
| 6409 | /** |
| 6410 | * Called when a view created by this adapter has been recycled. |
| 6411 | * |
| 6412 | * <p>A view is recycled when a {@link LayoutManager} decides that it no longer |
| 6413 | * needs to be attached to its parent {@link RecyclerView}. This can be because it has |
| 6414 | * fallen out of visibility or a set of cached views represented by views still |
| 6415 | * attached to the parent RecyclerView. If an item view has large or expensive data |
| 6416 | * bound to it such as large bitmaps, this may be a good place to release those |
| 6417 | * resources.</p> |
| 6418 | * <p> |
| 6419 | * RecyclerView calls this method right before clearing ViewHolder's internal data and |
| 6420 | * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information |
| 6421 | * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get |
| 6422 | * its adapter position. |
| 6423 | * |
| 6424 | * @param holder The ViewHolder for the view being recycled |
| 6425 | */ |
| 6426 | public void onViewRecycled(VH holder) { |
| 6427 | } |
| 6428 | |
| 6429 | /** |
| 6430 | * Called by the RecyclerView if a ViewHolder created by this Adapter cannot be recycled |
| 6431 | * due to its transient state. Upon receiving this callback, Adapter can clear the |
| 6432 | * animation(s) that effect the View's transient state and return <code>true</code> so that |
| 6433 | * the View can be recycled. Keep in mind that the View in question is already removed from |
| 6434 | * the RecyclerView. |
| 6435 | * <p> |
| 6436 | * In some cases, it is acceptable to recycle a View although it has transient state. Most |
| 6437 | * of the time, this is a case where the transient state will be cleared in |
| 6438 | * {@link #onBindViewHolder(ViewHolder, int)} call when View is rebound to a new position. |
| 6439 | * For this reason, RecyclerView leaves the decision to the Adapter and uses the return |
| 6440 | * value of this method to decide whether the View should be recycled or not. |
| 6441 | * <p> |
| 6442 | * Note that when all animations are created by {@link RecyclerView.ItemAnimator}, you |
| 6443 | * should never receive this callback because RecyclerView keeps those Views as children |
| 6444 | * until their animations are complete. This callback is useful when children of the item |
| 6445 | * views create animations which may not be easy to implement using an {@link ItemAnimator}. |
| 6446 | * <p> |
| 6447 | * You should <em>never</em> fix this issue by calling |
| 6448 | * <code>holder.itemView.setHasTransientState(false);</code> unless you've previously called |
| 6449 | * <code>holder.itemView.setHasTransientState(true);</code>. Each |
| 6450 | * <code>View.setHasTransientState(true)</code> call must be matched by a |
| 6451 | * <code>View.setHasTransientState(false)</code> call, otherwise, the state of the View |
| 6452 | * may become inconsistent. You should always prefer to end or cancel animations that are |
| 6453 | * triggering the transient state instead of handling it manually. |
| 6454 | * |
| 6455 | * @param holder The ViewHolder containing the View that could not be recycled due to its |
| 6456 | * transient state. |
| 6457 | * @return True if the View should be recycled, false otherwise. Note that if this method |
| 6458 | * returns <code>true</code>, RecyclerView <em>will ignore</em> the transient state of |
| 6459 | * the View and recycle it regardless. If this method returns <code>false</code>, |
| 6460 | * RecyclerView will check the View's transient state again before giving a final decision. |
| 6461 | * Default implementation returns false. |
| 6462 | */ |
| 6463 | public boolean onFailedToRecycleView(VH holder) { |
| 6464 | return false; |
| 6465 | } |
| 6466 | |
| 6467 | /** |
| 6468 | * Called when a view created by this adapter has been attached to a window. |
| 6469 | * |
| 6470 | * <p>This can be used as a reasonable signal that the view is about to be seen |
| 6471 | * by the user. If the adapter previously freed any resources in |
| 6472 | * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow} |
| 6473 | * those resources should be restored here.</p> |
| 6474 | * |
| 6475 | * @param holder Holder of the view being attached |
| 6476 | */ |
| 6477 | public void onViewAttachedToWindow(VH holder) { |
| 6478 | } |
| 6479 | |
| 6480 | /** |
| 6481 | * Called when a view created by this adapter has been detached from its window. |
| 6482 | * |
| 6483 | * <p>Becoming detached from the window is not necessarily a permanent condition; |
| 6484 | * the consumer of an Adapter's views may choose to cache views offscreen while they |
| 6485 | * are not visible, attaching and detaching them as appropriate.</p> |
| 6486 | * |
| 6487 | * @param holder Holder of the view being detached |
| 6488 | */ |
| 6489 | public void onViewDetachedFromWindow(VH holder) { |
| 6490 | } |
| 6491 | |
| 6492 | /** |
| 6493 | * Returns true if one or more observers are attached to this adapter. |
| 6494 | * |
| 6495 | * @return true if this adapter has observers |
| 6496 | */ |
| 6497 | public final boolean hasObservers() { |
| 6498 | return mObservable.hasObservers(); |
| 6499 | } |
| 6500 | |
| 6501 | /** |
| 6502 | * Register a new observer to listen for data changes. |
| 6503 | * |
| 6504 | * <p>The adapter may publish a variety of events describing specific changes. |
| 6505 | * Not all adapters may support all change types and some may fall back to a generic |
| 6506 | * {@link com.android.internal.widget.RecyclerView.AdapterDataObserver#onChanged() |
| 6507 | * "something changed"} event if more specific data is not available.</p> |
| 6508 | * |
| 6509 | * <p>Components registering observers with an adapter are responsible for |
| 6510 | * {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) |
| 6511 | * unregistering} those observers when finished.</p> |
| 6512 | * |
| 6513 | * @param observer Observer to register |
| 6514 | * |
| 6515 | * @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) |
| 6516 | */ |
| 6517 | public void registerAdapterDataObserver(AdapterDataObserver observer) { |
| 6518 | mObservable.registerObserver(observer); |
| 6519 | } |
| 6520 | |
| 6521 | /** |
| 6522 | * Unregister an observer currently listening for data changes. |
| 6523 | * |
| 6524 | * <p>The unregistered observer will no longer receive events about changes |
| 6525 | * to the adapter.</p> |
| 6526 | * |
| 6527 | * @param observer Observer to unregister |
| 6528 | * |
| 6529 | * @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver) |
| 6530 | */ |
| 6531 | public void unregisterAdapterDataObserver(AdapterDataObserver observer) { |
| 6532 | mObservable.unregisterObserver(observer); |
| 6533 | } |
| 6534 | |
| 6535 | /** |
| 6536 | * Called by RecyclerView when it starts observing this Adapter. |
| 6537 | * <p> |
| 6538 | * Keep in mind that same adapter may be observed by multiple RecyclerViews. |
| 6539 | * |
| 6540 | * @param recyclerView The RecyclerView instance which started observing this adapter. |
| 6541 | * @see #onDetachedFromRecyclerView(RecyclerView) |
| 6542 | */ |
| 6543 | public void onAttachedToRecyclerView(RecyclerView recyclerView) { |
| 6544 | } |
| 6545 | |
| 6546 | /** |
| 6547 | * Called by RecyclerView when it stops observing this Adapter. |
| 6548 | * |
| 6549 | * @param recyclerView The RecyclerView instance which stopped observing this adapter. |
| 6550 | * @see #onAttachedToRecyclerView(RecyclerView) |
| 6551 | */ |
| 6552 | public void onDetachedFromRecyclerView(RecyclerView recyclerView) { |
| 6553 | } |
| 6554 | |
| 6555 | /** |
| 6556 | * Notify any registered observers that the data set has changed. |
| 6557 | * |
| 6558 | * <p>There are two different classes of data change events, item changes and structural |
| 6559 | * changes. Item changes are when a single item has its data updated but no positional |
| 6560 | * changes have occurred. Structural changes are when items are inserted, removed or moved |
| 6561 | * within the data set.</p> |
| 6562 | * |
| 6563 | * <p>This event does not specify what about the data set has changed, forcing |
| 6564 | * any observers to assume that all existing items and structure may no longer be valid. |
| 6565 | * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> |
| 6566 | * |
| 6567 | * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events |
| 6568 | * for adapters that report that they have {@link #hasStableIds() stable IDs} when |
| 6569 | * this method is used. This can help for the purposes of animation and visual |
| 6570 | * object persistence but individual item views will still need to be rebound |
| 6571 | * and relaid out.</p> |
| 6572 | * |
| 6573 | * <p>If you are writing an adapter it will always be more efficient to use the more |
| 6574 | * specific change events if you can. Rely on <code>notifyDataSetChanged()</code> |
| 6575 | * as a last resort.</p> |
| 6576 | * |
| 6577 | * @see #notifyItemChanged(int) |
| 6578 | * @see #notifyItemInserted(int) |
| 6579 | * @see #notifyItemRemoved(int) |
| 6580 | * @see #notifyItemRangeChanged(int, int) |
| 6581 | * @see #notifyItemRangeInserted(int, int) |
| 6582 | * @see #notifyItemRangeRemoved(int, int) |
| 6583 | */ |
| 6584 | public final void notifyDataSetChanged() { |
| 6585 | mObservable.notifyChanged(); |
| 6586 | } |
| 6587 | |
| 6588 | /** |
| 6589 | * Notify any registered observers that the item at <code>position</code> has changed. |
| 6590 | * Equivalent to calling <code>notifyItemChanged(position, null);</code>. |
| 6591 | * |
| 6592 | * <p>This is an item change event, not a structural change event. It indicates that any |
| 6593 | * reflection of the data at <code>position</code> is out of date and should be updated. |
| 6594 | * The item at <code>position</code> retains the same identity.</p> |
| 6595 | * |
| 6596 | * @param position Position of the item that has changed |
| 6597 | * |
| 6598 | * @see #notifyItemRangeChanged(int, int) |
| 6599 | */ |
| 6600 | public final void notifyItemChanged(int position) { |
| 6601 | mObservable.notifyItemRangeChanged(position, 1); |
| 6602 | } |
| 6603 | |
| 6604 | /** |
| 6605 | * Notify any registered observers that the item at <code>position</code> has changed with |
| 6606 | * an optional payload object. |
| 6607 | * |
| 6608 | * <p>This is an item change event, not a structural change event. It indicates that any |
| 6609 | * reflection of the data at <code>position</code> is out of date and should be updated. |
| 6610 | * The item at <code>position</code> retains the same identity. |
| 6611 | * </p> |
| 6612 | * |
| 6613 | * <p> |
| 6614 | * Client can optionally pass a payload for partial change. These payloads will be merged |
| 6615 | * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the |
| 6616 | * item is already represented by a ViewHolder and it will be rebound to the same |
| 6617 | * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing |
| 6618 | * payloads on that item and prevent future payload until |
| 6619 | * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume |
| 6620 | * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not |
| 6621 | * attached, the payload will be simply dropped. |
| 6622 | * |
| 6623 | * @param position Position of the item that has changed |
| 6624 | * @param payload Optional parameter, use null to identify a "full" update |
| 6625 | * |
| 6626 | * @see #notifyItemRangeChanged(int, int) |
| 6627 | */ |
| 6628 | public final void notifyItemChanged(int position, Object payload) { |
| 6629 | mObservable.notifyItemRangeChanged(position, 1, payload); |
| 6630 | } |
| 6631 | |
| 6632 | /** |
| 6633 | * Notify any registered observers that the <code>itemCount</code> items starting at |
| 6634 | * position <code>positionStart</code> have changed. |
| 6635 | * Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>. |
| 6636 | * |
| 6637 | * <p>This is an item change event, not a structural change event. It indicates that |
| 6638 | * any reflection of the data in the given position range is out of date and should |
| 6639 | * be updated. The items in the given range retain the same identity.</p> |
| 6640 | * |
| 6641 | * @param positionStart Position of the first item that has changed |
| 6642 | * @param itemCount Number of items that have changed |
| 6643 | * |
| 6644 | * @see #notifyItemChanged(int) |
| 6645 | */ |
| 6646 | public final void notifyItemRangeChanged(int positionStart, int itemCount) { |
| 6647 | mObservable.notifyItemRangeChanged(positionStart, itemCount); |
| 6648 | } |
| 6649 | |
| 6650 | /** |
| 6651 | * Notify any registered observers that the <code>itemCount</code> items starting at |
| 6652 | * position <code>positionStart</code> have changed. An optional payload can be |
| 6653 | * passed to each changed item. |
| 6654 | * |
| 6655 | * <p>This is an item change event, not a structural change event. It indicates that any |
| 6656 | * reflection of the data in the given position range is out of date and should be updated. |
| 6657 | * The items in the given range retain the same identity. |
| 6658 | * </p> |
| 6659 | * |
| 6660 | * <p> |
| 6661 | * Client can optionally pass a payload for partial change. These payloads will be merged |
| 6662 | * and may be passed to adapter's {@link #onBindViewHolder(ViewHolder, int, List)} if the |
| 6663 | * item is already represented by a ViewHolder and it will be rebound to the same |
| 6664 | * ViewHolder. A notifyItemRangeChanged() with null payload will clear all existing |
| 6665 | * payloads on that item and prevent future payload until |
| 6666 | * {@link #onBindViewHolder(ViewHolder, int, List)} is called. Adapter should not assume |
| 6667 | * that the payload will always be passed to onBindViewHolder(), e.g. when the view is not |
| 6668 | * attached, the payload will be simply dropped. |
| 6669 | * |
| 6670 | * @param positionStart Position of the first item that has changed |
| 6671 | * @param itemCount Number of items that have changed |
| 6672 | * @param payload Optional parameter, use null to identify a "full" update |
| 6673 | * |
| 6674 | * @see #notifyItemChanged(int) |
| 6675 | */ |
| 6676 | public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { |
| 6677 | mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); |
| 6678 | } |
| 6679 | |
| 6680 | /** |
| 6681 | * Notify any registered observers that the item reflected at <code>position</code> |
| 6682 | * has been newly inserted. The item previously at <code>position</code> is now at |
| 6683 | * position <code>position + 1</code>. |
| 6684 | * |
| 6685 | * <p>This is a structural change event. Representations of other existing items in the |
| 6686 | * data set are still considered up to date and will not be rebound, though their |
| 6687 | * positions may be altered.</p> |
| 6688 | * |
| 6689 | * @param position Position of the newly inserted item in the data set |
| 6690 | * |
| 6691 | * @see #notifyItemRangeInserted(int, int) |
| 6692 | */ |
| 6693 | public final void notifyItemInserted(int position) { |
| 6694 | mObservable.notifyItemRangeInserted(position, 1); |
| 6695 | } |
| 6696 | |
| 6697 | /** |
| 6698 | * Notify any registered observers that the item reflected at <code>fromPosition</code> |
| 6699 | * has been moved to <code>toPosition</code>. |
| 6700 | * |
| 6701 | * <p>This is a structural change event. Representations of other existing items in the |
| 6702 | * data set are still considered up to date and will not be rebound, though their |
| 6703 | * positions may be altered.</p> |
| 6704 | * |
| 6705 | * @param fromPosition Previous position of the item. |
| 6706 | * @param toPosition New position of the item. |
| 6707 | */ |
| 6708 | public final void notifyItemMoved(int fromPosition, int toPosition) { |
| 6709 | mObservable.notifyItemMoved(fromPosition, toPosition); |
| 6710 | } |
| 6711 | |
| 6712 | /** |
| 6713 | * Notify any registered observers that the currently reflected <code>itemCount</code> |
| 6714 | * items starting at <code>positionStart</code> have been newly inserted. The items |
| 6715 | * previously located at <code>positionStart</code> and beyond can now be found starting |
| 6716 | * at position <code>positionStart + itemCount</code>. |
| 6717 | * |
| 6718 | * <p>This is a structural change event. Representations of other existing items in the |
| 6719 | * data set are still considered up to date and will not be rebound, though their positions |
| 6720 | * may be altered.</p> |
| 6721 | * |
| 6722 | * @param positionStart Position of the first item that was inserted |
| 6723 | * @param itemCount Number of items inserted |
| 6724 | * |
| 6725 | * @see #notifyItemInserted(int) |
| 6726 | */ |
| 6727 | public final void notifyItemRangeInserted(int positionStart, int itemCount) { |
| 6728 | mObservable.notifyItemRangeInserted(positionStart, itemCount); |
| 6729 | } |
| 6730 | |
| 6731 | /** |
| 6732 | * Notify any registered observers that the item previously located at <code>position</code> |
| 6733 | * has been removed from the data set. The items previously located at and after |
| 6734 | * <code>position</code> may now be found at <code>oldPosition - 1</code>. |
| 6735 | * |
| 6736 | * <p>This is a structural change event. Representations of other existing items in the |
| 6737 | * data set are still considered up to date and will not be rebound, though their positions |
| 6738 | * may be altered.</p> |
| 6739 | * |
| 6740 | * @param position Position of the item that has now been removed |
| 6741 | * |
| 6742 | * @see #notifyItemRangeRemoved(int, int) |
| 6743 | */ |
| 6744 | public final void notifyItemRemoved(int position) { |
| 6745 | mObservable.notifyItemRangeRemoved(position, 1); |
| 6746 | } |
| 6747 | |
| 6748 | /** |
| 6749 | * Notify any registered observers that the <code>itemCount</code> items previously |
| 6750 | * located at <code>positionStart</code> have been removed from the data set. The items |
| 6751 | * previously located at and after <code>positionStart + itemCount</code> may now be found |
| 6752 | * at <code>oldPosition - itemCount</code>. |
| 6753 | * |
| 6754 | * <p>This is a structural change event. Representations of other existing items in the data |
| 6755 | * set are still considered up to date and will not be rebound, though their positions |
| 6756 | * may be altered.</p> |
| 6757 | * |
| 6758 | * @param positionStart Previous position of the first item that was removed |
| 6759 | * @param itemCount Number of items removed from the data set |
| 6760 | */ |
| 6761 | public final void notifyItemRangeRemoved(int positionStart, int itemCount) { |
| 6762 | mObservable.notifyItemRangeRemoved(positionStart, itemCount); |
| 6763 | } |
| 6764 | } |
| 6765 | |
| 6766 | void dispatchChildDetached(View child) { |
| 6767 | final ViewHolder viewHolder = getChildViewHolderInt(child); |
| 6768 | onChildDetachedFromWindow(child); |
| 6769 | if (mAdapter != null && viewHolder != null) { |
| 6770 | mAdapter.onViewDetachedFromWindow(viewHolder); |
| 6771 | } |
| 6772 | if (mOnChildAttachStateListeners != null) { |
| 6773 | final int cnt = mOnChildAttachStateListeners.size(); |
| 6774 | for (int i = cnt - 1; i >= 0; i--) { |
| 6775 | mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child); |
| 6776 | } |
| 6777 | } |
| 6778 | } |
| 6779 | |
| 6780 | void dispatchChildAttached(View child) { |
| 6781 | final ViewHolder viewHolder = getChildViewHolderInt(child); |
| 6782 | onChildAttachedToWindow(child); |
| 6783 | if (mAdapter != null && viewHolder != null) { |
| 6784 | mAdapter.onViewAttachedToWindow(viewHolder); |
| 6785 | } |
| 6786 | if (mOnChildAttachStateListeners != null) { |
| 6787 | final int cnt = mOnChildAttachStateListeners.size(); |
| 6788 | for (int i = cnt - 1; i >= 0; i--) { |
| 6789 | mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child); |
| 6790 | } |
| 6791 | } |
| 6792 | } |
| 6793 | |
| 6794 | /** |
| 6795 | * A <code>LayoutManager</code> is responsible for measuring and positioning item views |
| 6796 | * within a <code>RecyclerView</code> as well as determining the policy for when to recycle |
| 6797 | * item views that are no longer visible to the user. By changing the <code>LayoutManager</code> |
| 6798 | * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list, |
| 6799 | * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock |
| 6800 | * layout managers are provided for general use. |
| 6801 | * <p/> |
| 6802 | * If the LayoutManager specifies a default constructor or one with the signature |
| 6803 | * ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will |
| 6804 | * instantiate and set the LayoutManager when being inflated. Most used properties can |
| 6805 | * be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case |
| 6806 | * a LayoutManager specifies both constructors, the non-default constructor will take |
| 6807 | * precedence. |
| 6808 | * |
| 6809 | */ |
| 6810 | public abstract static class LayoutManager { |
| 6811 | ChildHelper mChildHelper; |
| 6812 | RecyclerView mRecyclerView; |
| 6813 | |
| 6814 | @Nullable |
| 6815 | SmoothScroller mSmoothScroller; |
| 6816 | |
| 6817 | boolean mRequestedSimpleAnimations = false; |
| 6818 | |
| 6819 | boolean mIsAttachedToWindow = false; |
| 6820 | |
| 6821 | boolean mAutoMeasure = false; |
| 6822 | |
| 6823 | /** |
| 6824 | * LayoutManager has its own more strict measurement cache to avoid re-measuring a child |
| 6825 | * if the space that will be given to it is already larger than what it has measured before. |
| 6826 | */ |
| 6827 | private boolean mMeasurementCacheEnabled = true; |
| 6828 | |
| 6829 | private boolean mItemPrefetchEnabled = true; |
| 6830 | |
| 6831 | /** |
| 6832 | * Written by {@link GapWorker} when prefetches occur to track largest number of view ever |
| 6833 | * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or |
| 6834 | * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call. |
| 6835 | * |
| 6836 | * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, |
| 6837 | * will be reset upon layout to prevent initial prefetches (often large, since they're |
| 6838 | * proportional to expected child count) from expanding cache permanently. |
| 6839 | */ |
| 6840 | int mPrefetchMaxCountObserved; |
| 6841 | |
| 6842 | /** |
| 6843 | * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset. |
| 6844 | */ |
| 6845 | boolean mPrefetchMaxObservedInInitialPrefetch; |
| 6846 | |
| 6847 | /** |
| 6848 | * These measure specs might be the measure specs that were passed into RecyclerView's |
| 6849 | * onMeasure method OR fake measure specs created by the RecyclerView. |
| 6850 | * For example, when a layout is run, RecyclerView always sets these specs to be |
| 6851 | * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass. |
| 6852 | * <p> |
| 6853 | * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the |
| 6854 | * API level and sets the size to 0 pre-M to avoid any issue that might be caused by |
| 6855 | * corrupt values. Older platforms have no responsibility to provide a size if they set |
| 6856 | * mode to unspecified. |
| 6857 | */ |
| 6858 | private int mWidthMode, mHeightMode; |
| 6859 | private int mWidth, mHeight; |
| 6860 | |
| 6861 | |
| 6862 | /** |
| 6863 | * Interface for LayoutManagers to request items to be prefetched, based on position, with |
| 6864 | * specified distance from viewport, which indicates priority. |
| 6865 | * |
| 6866 | * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) |
| 6867 | * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) |
| 6868 | */ |
| 6869 | public interface LayoutPrefetchRegistry { |
| 6870 | /** |
| 6871 | * Requests an an item to be prefetched, based on position, with a specified distance, |
| 6872 | * indicating priority. |
| 6873 | * |
| 6874 | * @param layoutPosition Position of the item to prefetch. |
| 6875 | * @param pixelDistance Distance from the current viewport to the bounds of the item, |
| 6876 | * must be non-negative. |
| 6877 | */ |
| 6878 | void addPosition(int layoutPosition, int pixelDistance); |
| 6879 | } |
| 6880 | |
| 6881 | void setRecyclerView(RecyclerView recyclerView) { |
| 6882 | if (recyclerView == null) { |
| 6883 | mRecyclerView = null; |
| 6884 | mChildHelper = null; |
| 6885 | mWidth = 0; |
| 6886 | mHeight = 0; |
| 6887 | } else { |
| 6888 | mRecyclerView = recyclerView; |
| 6889 | mChildHelper = recyclerView.mChildHelper; |
| 6890 | mWidth = recyclerView.getWidth(); |
| 6891 | mHeight = recyclerView.getHeight(); |
| 6892 | } |
| 6893 | mWidthMode = MeasureSpec.EXACTLY; |
| 6894 | mHeightMode = MeasureSpec.EXACTLY; |
| 6895 | } |
| 6896 | |
| 6897 | void setMeasureSpecs(int wSpec, int hSpec) { |
| 6898 | mWidth = MeasureSpec.getSize(wSpec); |
| 6899 | mWidthMode = MeasureSpec.getMode(wSpec); |
| 6900 | if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { |
| 6901 | mWidth = 0; |
| 6902 | } |
| 6903 | |
| 6904 | mHeight = MeasureSpec.getSize(hSpec); |
| 6905 | mHeightMode = MeasureSpec.getMode(hSpec); |
| 6906 | if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) { |
| 6907 | mHeight = 0; |
| 6908 | } |
| 6909 | } |
| 6910 | |
| 6911 | /** |
| 6912 | * Called after a layout is calculated during a measure pass when using auto-measure. |
| 6913 | * <p> |
| 6914 | * It simply traverses all children to calculate a bounding box then calls |
| 6915 | * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method |
| 6916 | * if they need to handle the bounding box differently. |
| 6917 | * <p> |
| 6918 | * For example, GridLayoutManager override that method to ensure that even if a column is |
| 6919 | * empty, the GridLayoutManager still measures wide enough to include it. |
| 6920 | * |
| 6921 | * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure |
| 6922 | * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure |
| 6923 | */ |
| 6924 | void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { |
| 6925 | final int count = getChildCount(); |
| 6926 | if (count == 0) { |
| 6927 | mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); |
| 6928 | return; |
| 6929 | } |
| 6930 | int minX = Integer.MAX_VALUE; |
| 6931 | int minY = Integer.MAX_VALUE; |
| 6932 | int maxX = Integer.MIN_VALUE; |
| 6933 | int maxY = Integer.MIN_VALUE; |
| 6934 | |
| 6935 | for (int i = 0; i < count; i++) { |
| 6936 | View child = getChildAt(i); |
| 6937 | final Rect bounds = mRecyclerView.mTempRect; |
| 6938 | getDecoratedBoundsWithMargins(child, bounds); |
| 6939 | if (bounds.left < minX) { |
| 6940 | minX = bounds.left; |
| 6941 | } |
| 6942 | if (bounds.right > maxX) { |
| 6943 | maxX = bounds.right; |
| 6944 | } |
| 6945 | if (bounds.top < minY) { |
| 6946 | minY = bounds.top; |
| 6947 | } |
| 6948 | if (bounds.bottom > maxY) { |
| 6949 | maxY = bounds.bottom; |
| 6950 | } |
| 6951 | } |
| 6952 | mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); |
| 6953 | setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec); |
| 6954 | } |
| 6955 | |
| 6956 | /** |
| 6957 | * Sets the measured dimensions from the given bounding box of the children and the |
| 6958 | * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is |
| 6959 | * called after the RecyclerView calls |
| 6960 | * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a measurement pass. |
| 6961 | * <p> |
| 6962 | * This method should call {@link #setMeasuredDimension(int, int)}. |
| 6963 | * <p> |
| 6964 | * The default implementation adds the RecyclerView's padding to the given bounding box |
| 6965 | * then caps the value to be within the given measurement specs. |
| 6966 | * <p> |
| 6967 | * This method is only called if the LayoutManager opted into the auto measurement API. |
| 6968 | * |
| 6969 | * @param childrenBounds The bounding box of all children |
| 6970 | * @param wSpec The widthMeasureSpec that was passed into the RecyclerView. |
| 6971 | * @param hSpec The heightMeasureSpec that was passed into the RecyclerView. |
| 6972 | * |
| 6973 | * @see #setAutoMeasureEnabled(boolean) |
| 6974 | */ |
| 6975 | public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) { |
| 6976 | int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight(); |
| 6977 | int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom(); |
| 6978 | int width = chooseSize(wSpec, usedWidth, getMinimumWidth()); |
| 6979 | int height = chooseSize(hSpec, usedHeight, getMinimumHeight()); |
| 6980 | setMeasuredDimension(width, height); |
| 6981 | } |
| 6982 | |
| 6983 | /** |
| 6984 | * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView |
| 6985 | */ |
| 6986 | public void requestLayout() { |
| 6987 | if (mRecyclerView != null) { |
| 6988 | mRecyclerView.requestLayout(); |
| 6989 | } |
| 6990 | } |
| 6991 | |
| 6992 | /** |
| 6993 | * Checks if RecyclerView is in the middle of a layout or scroll and throws an |
| 6994 | * {@link IllegalStateException} if it <b>is not</b>. |
| 6995 | * |
| 6996 | * @param message The message for the exception. Can be null. |
| 6997 | * @see #assertNotInLayoutOrScroll(String) |
| 6998 | */ |
| 6999 | public void assertInLayoutOrScroll(String message) { |
| 7000 | if (mRecyclerView != null) { |
| 7001 | mRecyclerView.assertInLayoutOrScroll(message); |
| 7002 | } |
| 7003 | } |
| 7004 | |
| 7005 | /** |
| 7006 | * Chooses a size from the given specs and parameters that is closest to the desired size |
| 7007 | * and also complies with the spec. |
| 7008 | * |
| 7009 | * @param spec The measureSpec |
| 7010 | * @param desired The preferred measurement |
| 7011 | * @param min The minimum value |
| 7012 | * |
| 7013 | * @return A size that fits to the given specs |
| 7014 | */ |
| 7015 | public static int chooseSize(int spec, int desired, int min) { |
| 7016 | final int mode = View.MeasureSpec.getMode(spec); |
| 7017 | final int size = View.MeasureSpec.getSize(spec); |
| 7018 | switch (mode) { |
| 7019 | case View.MeasureSpec.EXACTLY: |
| 7020 | return size; |
| 7021 | case View.MeasureSpec.AT_MOST: |
| 7022 | return Math.min(size, Math.max(desired, min)); |
| 7023 | case View.MeasureSpec.UNSPECIFIED: |
| 7024 | default: |
| 7025 | return Math.max(desired, min); |
| 7026 | } |
| 7027 | } |
| 7028 | |
| 7029 | /** |
| 7030 | * Checks if RecyclerView is in the middle of a layout or scroll and throws an |
| 7031 | * {@link IllegalStateException} if it <b>is</b>. |
| 7032 | * |
| 7033 | * @param message The message for the exception. Can be null. |
| 7034 | * @see #assertInLayoutOrScroll(String) |
| 7035 | */ |
| 7036 | public void assertNotInLayoutOrScroll(String message) { |
| 7037 | if (mRecyclerView != null) { |
| 7038 | mRecyclerView.assertNotInLayoutOrScroll(message); |
| 7039 | } |
| 7040 | } |
| 7041 | |
| 7042 | /** |
| 7043 | * Defines whether the layout should be measured by the RecyclerView or the LayoutManager |
| 7044 | * wants to handle the layout measurements itself. |
| 7045 | * <p> |
| 7046 | * This method is usually called by the LayoutManager with value {@code true} if it wants |
| 7047 | * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize |
| 7048 | * the measurement logic, you can call this method with {@code false} and override |
| 7049 | * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic. |
| 7050 | * <p> |
| 7051 | * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or |
| 7052 | * handle various specs provided by the RecyclerView's parent. |
| 7053 | * It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an |
| 7054 | * {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based |
| 7055 | * on children's positions. It does this while supporting all existing animation |
| 7056 | * capabilities of the RecyclerView. |
| 7057 | * <p> |
| 7058 | * AutoMeasure works as follows: |
| 7059 | * <ol> |
| 7060 | * <li>LayoutManager should call {@code setAutoMeasureEnabled(true)} to enable it. All of |
| 7061 | * the framework LayoutManagers use {@code auto-measure}.</li> |
| 7062 | * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided specs are |
| 7063 | * exact, RecyclerView will only call LayoutManager's {@code onMeasure} and return without |
| 7064 | * doing any layout calculation.</li> |
| 7065 | * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the |
| 7066 | * layout process in {@code onMeasure} call. It will process all pending Adapter updates and |
| 7067 | * decide whether to run a predictive layout or not. If it decides to do so, it will first |
| 7068 | * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to |
| 7069 | * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still |
| 7070 | * return the width and height of the RecyclerView as of the last layout calculation. |
| 7071 | * <p> |
| 7072 | * After handling the predictive case, RecyclerView will call |
| 7073 | * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to |
| 7074 | * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can |
| 7075 | * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()}, |
| 7076 | * {@link #getWidth()} and {@link #getWidthMode()}.</li> |
| 7077 | * <li>After the layout calculation, RecyclerView sets the measured width & height by |
| 7078 | * calculating the bounding box for the children (+ RecyclerView's padding). The |
| 7079 | * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose |
| 7080 | * different values. For instance, GridLayoutManager overrides this value to handle the case |
| 7081 | * where if it is vertical and has 3 columns but only 2 items, it should still measure its |
| 7082 | * width to fit 3 items, not 2.</li> |
| 7083 | * <li>Any following on measure call to the RecyclerView will run |
| 7084 | * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to |
| 7085 | * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will |
| 7086 | * take care of which views are actually added / removed / moved / changed for animations so |
| 7087 | * that the LayoutManager should not worry about them and handle each |
| 7088 | * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one. |
| 7089 | * </li> |
| 7090 | * <li>When measure is complete and RecyclerView's |
| 7091 | * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks |
| 7092 | * whether it already did layout calculations during the measure pass and if so, it re-uses |
| 7093 | * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)} |
| 7094 | * if the last measure spec was different from the final dimensions or adapter contents |
| 7095 | * have changed between the measure call and the layout call.</li> |
| 7096 | * <li>Finally, animations are calculated and run as usual.</li> |
| 7097 | * </ol> |
| 7098 | * |
| 7099 | * @param enabled <code>True</code> if the Layout should be measured by the |
| 7100 | * RecyclerView, <code>false</code> if the LayoutManager wants |
| 7101 | * to measure itself. |
| 7102 | * |
| 7103 | * @see #setMeasuredDimension(Rect, int, int) |
| 7104 | * @see #isAutoMeasureEnabled() |
| 7105 | */ |
| 7106 | public void setAutoMeasureEnabled(boolean enabled) { |
| 7107 | mAutoMeasure = enabled; |
| 7108 | } |
| 7109 | |
| 7110 | /** |
| 7111 | * Returns whether the LayoutManager uses the automatic measurement API or not. |
| 7112 | * |
| 7113 | * @return <code>True</code> if the LayoutManager is measured by the RecyclerView or |
| 7114 | * <code>false</code> if it measures itself. |
| 7115 | * |
| 7116 | * @see #setAutoMeasureEnabled(boolean) |
| 7117 | */ |
| 7118 | public boolean isAutoMeasureEnabled() { |
| 7119 | return mAutoMeasure; |
| 7120 | } |
| 7121 | |
| 7122 | /** |
| 7123 | * Returns whether this LayoutManager supports automatic item animations. |
| 7124 | * A LayoutManager wishing to support item animations should obey certain |
| 7125 | * rules as outlined in {@link #onLayoutChildren(Recycler, State)}. |
| 7126 | * The default return value is <code>false</code>, so subclasses of LayoutManager |
| 7127 | * will not get predictive item animations by default. |
| 7128 | * |
| 7129 | * <p>Whether item animations are enabled in a RecyclerView is determined both |
| 7130 | * by the return value from this method and the |
| 7131 | * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the |
| 7132 | * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this |
| 7133 | * method returns false, then simple item animations will be enabled, in which |
| 7134 | * views that are moving onto or off of the screen are simply faded in/out. If |
| 7135 | * the RecyclerView has a non-null ItemAnimator and this method returns true, |
| 7136 | * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to |
| 7137 | * setup up the information needed to more intelligently predict where appearing |
| 7138 | * and disappearing views should be animated from/to.</p> |
| 7139 | * |
| 7140 | * @return true if predictive item animations should be enabled, false otherwise |
| 7141 | */ |
| 7142 | public boolean supportsPredictiveItemAnimations() { |
| 7143 | return false; |
| 7144 | } |
| 7145 | |
| 7146 | /** |
| 7147 | * Sets whether the LayoutManager should be queried for views outside of |
| 7148 | * its viewport while the UI thread is idle between frames. |
| 7149 | * |
| 7150 | * <p>If enabled, the LayoutManager will be queried for items to inflate/bind in between |
| 7151 | * view system traversals on devices running API 21 or greater. Default value is true.</p> |
| 7152 | * |
| 7153 | * <p>On platforms API level 21 and higher, the UI thread is idle between passing a frame |
| 7154 | * to RenderThread and the starting up its next frame at the next VSync pulse. By |
| 7155 | * prefetching out of window views in this time period, delays from inflation and view |
| 7156 | * binding are much less likely to cause jank and stuttering during scrolls and flings.</p> |
| 7157 | * |
| 7158 | * <p>While prefetch is enabled, it will have the side effect of expanding the effective |
| 7159 | * size of the View cache to hold prefetched views.</p> |
| 7160 | * |
| 7161 | * @param enabled <code>True</code> if items should be prefetched in between traversals. |
| 7162 | * |
| 7163 | * @see #isItemPrefetchEnabled() |
| 7164 | */ |
| 7165 | public final void setItemPrefetchEnabled(boolean enabled) { |
| 7166 | if (enabled != mItemPrefetchEnabled) { |
| 7167 | mItemPrefetchEnabled = enabled; |
| 7168 | mPrefetchMaxCountObserved = 0; |
| 7169 | if (mRecyclerView != null) { |
| 7170 | mRecyclerView.mRecycler.updateViewCacheSize(); |
| 7171 | } |
| 7172 | } |
| 7173 | } |
| 7174 | |
| 7175 | /** |
| 7176 | * Sets whether the LayoutManager should be queried for views outside of |
| 7177 | * its viewport while the UI thread is idle between frames. |
| 7178 | * |
| 7179 | * @see #setItemPrefetchEnabled(boolean) |
| 7180 | * |
| 7181 | * @return true if item prefetch is enabled, false otherwise |
| 7182 | */ |
| 7183 | public final boolean isItemPrefetchEnabled() { |
| 7184 | return mItemPrefetchEnabled; |
| 7185 | } |
| 7186 | |
| 7187 | /** |
| 7188 | * Gather all positions from the LayoutManager to be prefetched, given specified momentum. |
| 7189 | * |
| 7190 | * <p>If item prefetch is enabled, this method is called in between traversals to gather |
| 7191 | * which positions the LayoutManager will soon need, given upcoming movement in subsequent |
| 7192 | * traversals.</p> |
| 7193 | * |
| 7194 | * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for |
| 7195 | * each item to be prepared, and these positions will have their ViewHolders created and |
| 7196 | * bound, if there is sufficient time available, in advance of being needed by a |
| 7197 | * scroll or layout.</p> |
| 7198 | * |
| 7199 | * @param dx X movement component. |
| 7200 | * @param dy Y movement component. |
| 7201 | * @param state State of RecyclerView |
| 7202 | * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. |
| 7203 | * |
| 7204 | * @see #isItemPrefetchEnabled() |
| 7205 | * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) |
| 7206 | */ |
| 7207 | public void collectAdjacentPrefetchPositions(int dx, int dy, State state, |
| 7208 | LayoutPrefetchRegistry layoutPrefetchRegistry) {} |
| 7209 | |
| 7210 | /** |
| 7211 | * Gather all positions from the LayoutManager to be prefetched in preperation for its |
| 7212 | * RecyclerView to come on screen, due to the movement of another, containing RecyclerView. |
| 7213 | * |
| 7214 | * <p>This method is only called when a RecyclerView is nested in another RecyclerView.</p> |
| 7215 | * |
| 7216 | * <p>If item prefetch is enabled for this LayoutManager, as well in another containing |
| 7217 | * LayoutManager, this method is called in between draw traversals to gather |
| 7218 | * which positions this LayoutManager will first need, once it appears on the screen.</p> |
| 7219 | * |
| 7220 | * <p>For example, if this LayoutManager represents a horizontally scrolling list within a |
| 7221 | * vertically scrolling LayoutManager, this method would be called when the horizontal list |
| 7222 | * is about to come onscreen.</p> |
| 7223 | * |
| 7224 | * <p>The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for |
| 7225 | * each item to be prepared, and these positions will have their ViewHolders created and |
| 7226 | * bound, if there is sufficient time available, in advance of being needed by a |
| 7227 | * scroll or layout.</p> |
| 7228 | * |
| 7229 | * @param adapterItemCount number of items in the associated adapter. |
| 7230 | * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. |
| 7231 | * |
| 7232 | * @see #isItemPrefetchEnabled() |
| 7233 | * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) |
| 7234 | */ |
| 7235 | public void collectInitialPrefetchPositions(int adapterItemCount, |
| 7236 | LayoutPrefetchRegistry layoutPrefetchRegistry) {} |
| 7237 | |
| 7238 | void dispatchAttachedToWindow(RecyclerView view) { |
| 7239 | mIsAttachedToWindow = true; |
| 7240 | onAttachedToWindow(view); |
| 7241 | } |
| 7242 | |
| 7243 | void dispatchDetachedFromWindow(RecyclerView view, Recycler recycler) { |
| 7244 | mIsAttachedToWindow = false; |
| 7245 | onDetachedFromWindow(view, recycler); |
| 7246 | } |
| 7247 | |
| 7248 | /** |
| 7249 | * Returns whether LayoutManager is currently attached to a RecyclerView which is attached |
| 7250 | * to a window. |
| 7251 | * |
| 7252 | * @return True if this LayoutManager is controlling a RecyclerView and the RecyclerView |
| 7253 | * is attached to window. |
| 7254 | */ |
| 7255 | public boolean isAttachedToWindow() { |
| 7256 | return mIsAttachedToWindow; |
| 7257 | } |
| 7258 | |
| 7259 | /** |
| 7260 | * Causes the Runnable to execute on the next animation time step. |
| 7261 | * The runnable will be run on the user interface thread. |
| 7262 | * <p> |
| 7263 | * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. |
| 7264 | * |
| 7265 | * @param action The Runnable that will be executed. |
| 7266 | * |
| 7267 | * @see #removeCallbacks |
| 7268 | */ |
| 7269 | public void postOnAnimation(Runnable action) { |
| 7270 | if (mRecyclerView != null) { |
| 7271 | mRecyclerView.postOnAnimation(action); |
| 7272 | } |
| 7273 | } |
| 7274 | |
| 7275 | /** |
| 7276 | * Removes the specified Runnable from the message queue. |
| 7277 | * <p> |
| 7278 | * Calling this method when LayoutManager is not attached to a RecyclerView has no effect. |
| 7279 | * |
| 7280 | * @param action The Runnable to remove from the message handling queue |
| 7281 | * |
| 7282 | * @return true if RecyclerView could ask the Handler to remove the Runnable, |
| 7283 | * false otherwise. When the returned value is true, the Runnable |
| 7284 | * may or may not have been actually removed from the message queue |
| 7285 | * (for instance, if the Runnable was not in the queue already.) |
| 7286 | * |
| 7287 | * @see #postOnAnimation |
| 7288 | */ |
| 7289 | public boolean removeCallbacks(Runnable action) { |
| 7290 | if (mRecyclerView != null) { |
| 7291 | return mRecyclerView.removeCallbacks(action); |
| 7292 | } |
| 7293 | return false; |
| 7294 | } |
| 7295 | /** |
| 7296 | * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView |
| 7297 | * is attached to a window. |
| 7298 | * <p> |
| 7299 | * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not |
| 7300 | * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was |
| 7301 | * not requested on the RecyclerView while it was detached. |
| 7302 | * <p> |
| 7303 | * Subclass implementations should always call through to the superclass implementation. |
| 7304 | * |
| 7305 | * @param view The RecyclerView this LayoutManager is bound to |
| 7306 | * |
| 7307 | * @see #onDetachedFromWindow(RecyclerView, Recycler) |
| 7308 | */ |
| 7309 | @CallSuper |
| 7310 | public void onAttachedToWindow(RecyclerView view) { |
| 7311 | } |
| 7312 | |
| 7313 | /** |
| 7314 | * @deprecated |
| 7315 | * override {@link #onDetachedFromWindow(RecyclerView, Recycler)} |
| 7316 | */ |
| 7317 | @Deprecated |
| 7318 | public void onDetachedFromWindow(RecyclerView view) { |
| 7319 | |
| 7320 | } |
| 7321 | |
| 7322 | /** |
| 7323 | * Called when this LayoutManager is detached from its parent RecyclerView or when |
| 7324 | * its parent RecyclerView is detached from its window. |
| 7325 | * <p> |
| 7326 | * LayoutManager should clear all of its View references as another LayoutManager might be |
| 7327 | * assigned to the RecyclerView. |
| 7328 | * <p> |
| 7329 | * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not |
| 7330 | * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was |
| 7331 | * not requested on the RecyclerView while it was detached. |
| 7332 | * <p> |
| 7333 | * If your LayoutManager has View references that it cleans in on-detach, it should also |
| 7334 | * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when |
| 7335 | * RecyclerView is re-attached. |
| 7336 | * <p> |
| 7337 | * Subclass implementations should always call through to the superclass implementation. |
| 7338 | * |
| 7339 | * @param view The RecyclerView this LayoutManager is bound to |
| 7340 | * @param recycler The recycler to use if you prefer to recycle your children instead of |
| 7341 | * keeping them around. |
| 7342 | * |
| 7343 | * @see #onAttachedToWindow(RecyclerView) |
| 7344 | */ |
| 7345 | @CallSuper |
| 7346 | public void onDetachedFromWindow(RecyclerView view, Recycler recycler) { |
| 7347 | onDetachedFromWindow(view); |
| 7348 | } |
| 7349 | |
| 7350 | /** |
| 7351 | * Check if the RecyclerView is configured to clip child views to its padding. |
| 7352 | * |
| 7353 | * @return true if this RecyclerView clips children to its padding, false otherwise |
| 7354 | */ |
| 7355 | public boolean getClipToPadding() { |
| 7356 | return mRecyclerView != null && mRecyclerView.mClipToPadding; |
| 7357 | } |
| 7358 | |
| 7359 | /** |
| 7360 | * Lay out all relevant child views from the given adapter. |
| 7361 | * |
| 7362 | * The LayoutManager is in charge of the behavior of item animations. By default, |
| 7363 | * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple |
| 7364 | * item animations are enabled. This means that add/remove operations on the |
| 7365 | * adapter will result in animations to add new or appearing items, removed or |
| 7366 | * disappearing items, and moved items. If a LayoutManager returns false from |
| 7367 | * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a |
| 7368 | * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the |
| 7369 | * RecyclerView will have enough information to run those animations in a simple |
| 7370 | * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will |
| 7371 | * simply fade views in and out, whether they are actually added/removed or whether |
| 7372 | * they are moved on or off the screen due to other add/remove operations. |
| 7373 | * |
| 7374 | * <p>A LayoutManager wanting a better item animation experience, where items can be |
| 7375 | * animated onto and off of the screen according to where the items exist when they |
| 7376 | * are not on screen, then the LayoutManager should return true from |
| 7377 | * {@link #supportsPredictiveItemAnimations()} and add additional logic to |
| 7378 | * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations |
| 7379 | * means that {@link #onLayoutChildren(Recycler, State)} will be called twice; |
| 7380 | * once as a "pre" layout step to determine where items would have been prior to |
| 7381 | * a real layout, and again to do the "real" layout. In the pre-layout phase, |
| 7382 | * items will remember their pre-layout positions to allow them to be laid out |
| 7383 | * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will |
| 7384 | * be returned from the scrap to help determine correct placement of other items. |
| 7385 | * These removed items should not be added to the child list, but should be used |
| 7386 | * to help calculate correct positioning of other views, including views that |
| 7387 | * were not previously onscreen (referred to as APPEARING views), but whose |
| 7388 | * pre-layout offscreen position can be determined given the extra |
| 7389 | * information about the pre-layout removed views.</p> |
| 7390 | * |
| 7391 | * <p>The second layout pass is the real layout in which only non-removed views |
| 7392 | * will be used. The only additional requirement during this pass is, if |
| 7393 | * {@link #supportsPredictiveItemAnimations()} returns true, to note which |
| 7394 | * views exist in the child list prior to layout and which are not there after |
| 7395 | * layout (referred to as DISAPPEARING views), and to position/layout those views |
| 7396 | * appropriately, without regard to the actual bounds of the RecyclerView. This allows |
| 7397 | * the animation system to know the location to which to animate these disappearing |
| 7398 | * views.</p> |
| 7399 | * |
| 7400 | * <p>The default LayoutManager implementations for RecyclerView handle all of these |
| 7401 | * requirements for animations already. Clients of RecyclerView can either use one |
| 7402 | * of these layout managers directly or look at their implementations of |
| 7403 | * onLayoutChildren() to see how they account for the APPEARING and |
| 7404 | * DISAPPEARING views.</p> |
| 7405 | * |
| 7406 | * @param recycler Recycler to use for fetching potentially cached views for a |
| 7407 | * position |
| 7408 | * @param state Transient state of RecyclerView |
| 7409 | */ |
| 7410 | public void onLayoutChildren(Recycler recycler, State state) { |
| 7411 | Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); |
| 7412 | } |
| 7413 | |
| 7414 | /** |
| 7415 | * Called after a full layout calculation is finished. The layout calculation may include |
| 7416 | * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or |
| 7417 | * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call. |
| 7418 | * This method will be called at the end of {@link View#layout(int, int, int, int)} call. |
| 7419 | * <p> |
| 7420 | * This is a good place for the LayoutManager to do some cleanup like pending scroll |
| 7421 | * position, saved state etc. |
| 7422 | * |
| 7423 | * @param state Transient state of RecyclerView |
| 7424 | */ |
| 7425 | public void onLayoutCompleted(State state) { |
| 7426 | } |
| 7427 | |
| 7428 | /** |
| 7429 | * Create a default <code>LayoutParams</code> object for a child of the RecyclerView. |
| 7430 | * |
| 7431 | * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type |
| 7432 | * to store extra information specific to the layout. Client code should subclass |
| 7433 | * {@link RecyclerView.LayoutParams} for this purpose.</p> |
| 7434 | * |
| 7435 | * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type |
| 7436 | * you must also override |
| 7437 | * {@link #checkLayoutParams(LayoutParams)}, |
| 7438 | * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and |
| 7439 | * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> |
| 7440 | * |
| 7441 | * @return A new LayoutParams for a child view |
| 7442 | */ |
| 7443 | public abstract LayoutParams generateDefaultLayoutParams(); |
| 7444 | |
| 7445 | /** |
| 7446 | * Determines the validity of the supplied LayoutParams object. |
| 7447 | * |
| 7448 | * <p>This should check to make sure that the object is of the correct type |
| 7449 | * and all values are within acceptable ranges. The default implementation |
| 7450 | * returns <code>true</code> for non-null params.</p> |
| 7451 | * |
| 7452 | * @param lp LayoutParams object to check |
| 7453 | * @return true if this LayoutParams object is valid, false otherwise |
| 7454 | */ |
| 7455 | public boolean checkLayoutParams(LayoutParams lp) { |
| 7456 | return lp != null; |
| 7457 | } |
| 7458 | |
| 7459 | /** |
| 7460 | * Create a LayoutParams object suitable for this LayoutManager, copying relevant |
| 7461 | * values from the supplied LayoutParams object if possible. |
| 7462 | * |
| 7463 | * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type |
| 7464 | * you must also override |
| 7465 | * {@link #checkLayoutParams(LayoutParams)}, |
| 7466 | * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and |
| 7467 | * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> |
| 7468 | * |
| 7469 | * @param lp Source LayoutParams object to copy values from |
| 7470 | * @return a new LayoutParams object |
| 7471 | */ |
| 7472 | public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { |
| 7473 | if (lp instanceof LayoutParams) { |
| 7474 | return new LayoutParams((LayoutParams) lp); |
| 7475 | } else if (lp instanceof MarginLayoutParams) { |
| 7476 | return new LayoutParams((MarginLayoutParams) lp); |
| 7477 | } else { |
| 7478 | return new LayoutParams(lp); |
| 7479 | } |
| 7480 | } |
| 7481 | |
| 7482 | /** |
| 7483 | * Create a LayoutParams object suitable for this LayoutManager from |
| 7484 | * an inflated layout resource. |
| 7485 | * |
| 7486 | * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type |
| 7487 | * you must also override |
| 7488 | * {@link #checkLayoutParams(LayoutParams)}, |
| 7489 | * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and |
| 7490 | * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p> |
| 7491 | * |
| 7492 | * @param c Context for obtaining styled attributes |
| 7493 | * @param attrs AttributeSet describing the supplied arguments |
| 7494 | * @return a new LayoutParams object |
| 7495 | */ |
| 7496 | public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) { |
| 7497 | return new LayoutParams(c, attrs); |
| 7498 | } |
| 7499 | |
| 7500 | /** |
| 7501 | * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled. |
| 7502 | * The default implementation does nothing and returns 0. |
| 7503 | * |
| 7504 | * @param dx distance to scroll by in pixels. X increases as scroll position |
| 7505 | * approaches the right. |
| 7506 | * @param recycler Recycler to use for fetching potentially cached views for a |
| 7507 | * position |
| 7508 | * @param state Transient state of RecyclerView |
| 7509 | * @return The actual distance scrolled. The return value will be negative if dx was |
| 7510 | * negative and scrolling proceeeded in that direction. |
| 7511 | * <code>Math.abs(result)</code> may be less than dx if a boundary was reached. |
| 7512 | */ |
| 7513 | public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { |
| 7514 | return 0; |
| 7515 | } |
| 7516 | |
| 7517 | /** |
| 7518 | * Scroll vertically by dy pixels in screen coordinates and return the distance traveled. |
| 7519 | * The default implementation does nothing and returns 0. |
| 7520 | * |
| 7521 | * @param dy distance to scroll in pixels. Y increases as scroll position |
| 7522 | * approaches the bottom. |
| 7523 | * @param recycler Recycler to use for fetching potentially cached views for a |
| 7524 | * position |
| 7525 | * @param state Transient state of RecyclerView |
| 7526 | * @return The actual distance scrolled. The return value will be negative if dy was |
| 7527 | * negative and scrolling proceeeded in that direction. |
| 7528 | * <code>Math.abs(result)</code> may be less than dy if a boundary was reached. |
| 7529 | */ |
| 7530 | public int scrollVerticallyBy(int dy, Recycler recycler, State state) { |
| 7531 | return 0; |
| 7532 | } |
| 7533 | |
| 7534 | /** |
| 7535 | * Query if horizontal scrolling is currently supported. The default implementation |
| 7536 | * returns false. |
| 7537 | * |
| 7538 | * @return True if this LayoutManager can scroll the current contents horizontally |
| 7539 | */ |
| 7540 | public boolean canScrollHorizontally() { |
| 7541 | return false; |
| 7542 | } |
| 7543 | |
| 7544 | /** |
| 7545 | * Query if vertical scrolling is currently supported. The default implementation |
| 7546 | * returns false. |
| 7547 | * |
| 7548 | * @return True if this LayoutManager can scroll the current contents vertically |
| 7549 | */ |
| 7550 | public boolean canScrollVertically() { |
| 7551 | return false; |
| 7552 | } |
| 7553 | |
| 7554 | /** |
| 7555 | * Scroll to the specified adapter position. |
| 7556 | * |
| 7557 | * Actual position of the item on the screen depends on the LayoutManager implementation. |
| 7558 | * @param position Scroll to this adapter position. |
| 7559 | */ |
| 7560 | public void scrollToPosition(int position) { |
| 7561 | if (DEBUG) { |
| 7562 | Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract"); |
| 7563 | } |
| 7564 | } |
| 7565 | |
| 7566 | /** |
| 7567 | * <p>Smooth scroll to the specified adapter position.</p> |
| 7568 | * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller} |
| 7569 | * instance and call {@link #startSmoothScroll(SmoothScroller)}. |
| 7570 | * </p> |
| 7571 | * @param recyclerView The RecyclerView to which this layout manager is attached |
| 7572 | * @param state Current State of RecyclerView |
| 7573 | * @param position Scroll to this adapter position. |
| 7574 | */ |
| 7575 | public void smoothScrollToPosition(RecyclerView recyclerView, State state, |
| 7576 | int position) { |
| 7577 | Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling"); |
| 7578 | } |
| 7579 | |
| 7580 | /** |
| 7581 | * <p>Starts a smooth scroll using the provided SmoothScroller.</p> |
| 7582 | * <p>Calling this method will cancel any previous smooth scroll request.</p> |
| 7583 | * @param smoothScroller Instance which defines how smooth scroll should be animated |
| 7584 | */ |
| 7585 | public void startSmoothScroll(SmoothScroller smoothScroller) { |
| 7586 | if (mSmoothScroller != null && smoothScroller != mSmoothScroller |
| 7587 | && mSmoothScroller.isRunning()) { |
| 7588 | mSmoothScroller.stop(); |
| 7589 | } |
| 7590 | mSmoothScroller = smoothScroller; |
| 7591 | mSmoothScroller.start(mRecyclerView, this); |
| 7592 | } |
| 7593 | |
| 7594 | /** |
| 7595 | * @return true if RecycylerView is currently in the state of smooth scrolling. |
| 7596 | */ |
| 7597 | public boolean isSmoothScrolling() { |
| 7598 | return mSmoothScroller != null && mSmoothScroller.isRunning(); |
| 7599 | } |
| 7600 | |
| 7601 | |
| 7602 | /** |
| 7603 | * Returns the resolved layout direction for this RecyclerView. |
| 7604 | * |
| 7605 | * @return {@link android.view.View#LAYOUT_DIRECTION_RTL} if the layout |
| 7606 | * direction is RTL or returns |
| 7607 | * {@link android.view.View#LAYOUT_DIRECTION_LTR} if the layout direction |
| 7608 | * is not RTL. |
| 7609 | */ |
| 7610 | public int getLayoutDirection() { |
| 7611 | return mRecyclerView.getLayoutDirection(); |
| 7612 | } |
| 7613 | |
| 7614 | /** |
| 7615 | * Ends all animations on the view created by the {@link ItemAnimator}. |
| 7616 | * |
| 7617 | * @param view The View for which the animations should be ended. |
| 7618 | * @see RecyclerView.ItemAnimator#endAnimations() |
| 7619 | */ |
| 7620 | public void endAnimation(View view) { |
| 7621 | if (mRecyclerView.mItemAnimator != null) { |
| 7622 | mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view)); |
| 7623 | } |
| 7624 | } |
| 7625 | |
| 7626 | /** |
| 7627 | * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view |
| 7628 | * to the layout that is known to be going away, either because it has been |
| 7629 | * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the |
| 7630 | * visible portion of the container but is being laid out in order to inform RecyclerView |
| 7631 | * in how to animate the item out of view. |
| 7632 | * <p> |
| 7633 | * Views added via this method are going to be invisible to LayoutManager after the |
| 7634 | * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} |
| 7635 | * or won't be included in {@link #getChildCount()} method. |
| 7636 | * |
| 7637 | * @param child View to add and then remove with animation. |
| 7638 | */ |
| 7639 | public void addDisappearingView(View child) { |
| 7640 | addDisappearingView(child, -1); |
| 7641 | } |
| 7642 | |
| 7643 | /** |
| 7644 | * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view |
| 7645 | * to the layout that is known to be going away, either because it has been |
| 7646 | * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the |
| 7647 | * visible portion of the container but is being laid out in order to inform RecyclerView |
| 7648 | * in how to animate the item out of view. |
| 7649 | * <p> |
| 7650 | * Views added via this method are going to be invisible to LayoutManager after the |
| 7651 | * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)} |
| 7652 | * or won't be included in {@link #getChildCount()} method. |
| 7653 | * |
| 7654 | * @param child View to add and then remove with animation. |
| 7655 | * @param index Index of the view. |
| 7656 | */ |
| 7657 | public void addDisappearingView(View child, int index) { |
| 7658 | addViewInt(child, index, true); |
| 7659 | } |
| 7660 | |
| 7661 | /** |
| 7662 | * Add a view to the currently attached RecyclerView if needed. LayoutManagers should |
| 7663 | * use this method to add views obtained from a {@link Recycler} using |
| 7664 | * {@link Recycler#getViewForPosition(int)}. |
| 7665 | * |
| 7666 | * @param child View to add |
| 7667 | */ |
| 7668 | public void addView(View child) { |
| 7669 | addView(child, -1); |
| 7670 | } |
| 7671 | |
| 7672 | /** |
| 7673 | * Add a view to the currently attached RecyclerView if needed. LayoutManagers should |
| 7674 | * use this method to add views obtained from a {@link Recycler} using |
| 7675 | * {@link Recycler#getViewForPosition(int)}. |
| 7676 | * |
| 7677 | * @param child View to add |
| 7678 | * @param index Index to add child at |
| 7679 | */ |
| 7680 | public void addView(View child, int index) { |
| 7681 | addViewInt(child, index, false); |
| 7682 | } |
| 7683 | |
| 7684 | private void addViewInt(View child, int index, boolean disappearing) { |
| 7685 | final ViewHolder holder = getChildViewHolderInt(child); |
| 7686 | if (disappearing || holder.isRemoved()) { |
| 7687 | // these views will be hidden at the end of the layout pass. |
| 7688 | mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder); |
| 7689 | } else { |
| 7690 | // This may look like unnecessary but may happen if layout manager supports |
| 7691 | // predictive layouts and adapter removed then re-added the same item. |
| 7692 | // In this case, added version will be visible in the post layout (because add is |
| 7693 | // deferred) but RV will still bind it to the same View. |
| 7694 | // So if a View re-appears in post layout pass, remove it from disappearing list. |
| 7695 | mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder); |
| 7696 | } |
| 7697 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| 7698 | if (holder.wasReturnedFromScrap() || holder.isScrap()) { |
| 7699 | if (holder.isScrap()) { |
| 7700 | holder.unScrap(); |
| 7701 | } else { |
| 7702 | holder.clearReturnedFromScrapFlag(); |
| 7703 | } |
| 7704 | mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false); |
| 7705 | if (DISPATCH_TEMP_DETACH) { |
| 7706 | child.dispatchFinishTemporaryDetach(); |
| 7707 | } |
| 7708 | } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child |
| 7709 | // ensure in correct position |
| 7710 | int currentIndex = mChildHelper.indexOfChild(child); |
| 7711 | if (index == -1) { |
| 7712 | index = mChildHelper.getChildCount(); |
| 7713 | } |
| 7714 | if (currentIndex == -1) { |
| 7715 | throw new IllegalStateException("Added View has RecyclerView as parent but" |
| 7716 | + " view is not a real child. Unfiltered index:" |
| 7717 | + mRecyclerView.indexOfChild(child)); |
| 7718 | } |
| 7719 | if (currentIndex != index) { |
| 7720 | mRecyclerView.mLayout.moveView(currentIndex, index); |
| 7721 | } |
| 7722 | } else { |
| 7723 | mChildHelper.addView(child, index, false); |
| 7724 | lp.mInsetsDirty = true; |
| 7725 | if (mSmoothScroller != null && mSmoothScroller.isRunning()) { |
| 7726 | mSmoothScroller.onChildAttachedToWindow(child); |
| 7727 | } |
| 7728 | } |
| 7729 | if (lp.mPendingInvalidate) { |
| 7730 | if (DEBUG) { |
| 7731 | Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder); |
| 7732 | } |
| 7733 | holder.itemView.invalidate(); |
| 7734 | lp.mPendingInvalidate = false; |
| 7735 | } |
| 7736 | } |
| 7737 | |
| 7738 | /** |
| 7739 | * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should |
| 7740 | * use this method to completely remove a child view that is no longer needed. |
| 7741 | * LayoutManagers should strongly consider recycling removed views using |
| 7742 | * {@link Recycler#recycleView(android.view.View)}. |
| 7743 | * |
| 7744 | * @param child View to remove |
| 7745 | */ |
| 7746 | public void removeView(View child) { |
| 7747 | mChildHelper.removeView(child); |
| 7748 | } |
| 7749 | |
| 7750 | /** |
| 7751 | * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should |
| 7752 | * use this method to completely remove a child view that is no longer needed. |
| 7753 | * LayoutManagers should strongly consider recycling removed views using |
| 7754 | * {@link Recycler#recycleView(android.view.View)}. |
| 7755 | * |
| 7756 | * @param index Index of the child view to remove |
| 7757 | */ |
| 7758 | public void removeViewAt(int index) { |
| 7759 | final View child = getChildAt(index); |
| 7760 | if (child != null) { |
| 7761 | mChildHelper.removeViewAt(index); |
| 7762 | } |
| 7763 | } |
| 7764 | |
| 7765 | /** |
| 7766 | * Remove all views from the currently attached RecyclerView. This will not recycle |
| 7767 | * any of the affected views; the LayoutManager is responsible for doing so if desired. |
| 7768 | */ |
| 7769 | public void removeAllViews() { |
| 7770 | // Only remove non-animating views |
| 7771 | final int childCount = getChildCount(); |
| 7772 | for (int i = childCount - 1; i >= 0; i--) { |
| 7773 | mChildHelper.removeViewAt(i); |
| 7774 | } |
| 7775 | } |
| 7776 | |
| 7777 | /** |
| 7778 | * Returns offset of the RecyclerView's text baseline from the its top boundary. |
| 7779 | * |
| 7780 | * @return The offset of the RecyclerView's text baseline from the its top boundary; -1 if |
| 7781 | * there is no baseline. |
| 7782 | */ |
| 7783 | public int getBaseline() { |
| 7784 | return -1; |
| 7785 | } |
| 7786 | |
| 7787 | /** |
| 7788 | * Returns the adapter position of the item represented by the given View. This does not |
| 7789 | * contain any adapter changes that might have happened after the last layout. |
| 7790 | * |
| 7791 | * @param view The view to query |
| 7792 | * @return The adapter position of the item which is rendered by this View. |
| 7793 | */ |
| 7794 | public int getPosition(View view) { |
| 7795 | return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition(); |
| 7796 | } |
| 7797 | |
| 7798 | /** |
| 7799 | * Returns the View type defined by the adapter. |
| 7800 | * |
| 7801 | * @param view The view to query |
| 7802 | * @return The type of the view assigned by the adapter. |
| 7803 | */ |
| 7804 | public int getItemViewType(View view) { |
| 7805 | return getChildViewHolderInt(view).getItemViewType(); |
| 7806 | } |
| 7807 | |
| 7808 | /** |
| 7809 | * Traverses the ancestors of the given view and returns the item view that contains it |
| 7810 | * and also a direct child of the LayoutManager. |
| 7811 | * <p> |
| 7812 | * Note that this method may return null if the view is a child of the RecyclerView but |
| 7813 | * not a child of the LayoutManager (e.g. running a disappear animation). |
| 7814 | * |
| 7815 | * @param view The view that is a descendant of the LayoutManager. |
| 7816 | * |
| 7817 | * @return The direct child of the LayoutManager which contains the given view or null if |
| 7818 | * the provided view is not a descendant of this LayoutManager. |
| 7819 | * |
| 7820 | * @see RecyclerView#getChildViewHolder(View) |
| 7821 | * @see RecyclerView#findContainingViewHolder(View) |
| 7822 | */ |
| 7823 | @Nullable |
| 7824 | public View findContainingItemView(View view) { |
| 7825 | if (mRecyclerView == null) { |
| 7826 | return null; |
| 7827 | } |
| 7828 | View found = mRecyclerView.findContainingItemView(view); |
| 7829 | if (found == null) { |
| 7830 | return null; |
| 7831 | } |
| 7832 | if (mChildHelper.isHidden(found)) { |
| 7833 | return null; |
| 7834 | } |
| 7835 | return found; |
| 7836 | } |
| 7837 | |
| 7838 | /** |
| 7839 | * Finds the view which represents the given adapter position. |
| 7840 | * <p> |
| 7841 | * This method traverses each child since it has no information about child order. |
| 7842 | * Override this method to improve performance if your LayoutManager keeps data about |
| 7843 | * child views. |
| 7844 | * <p> |
| 7845 | * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method. |
| 7846 | * |
| 7847 | * @param position Position of the item in adapter |
| 7848 | * @return The child view that represents the given position or null if the position is not |
| 7849 | * laid out |
| 7850 | */ |
| 7851 | public View findViewByPosition(int position) { |
| 7852 | final int childCount = getChildCount(); |
| 7853 | for (int i = 0; i < childCount; i++) { |
| 7854 | View child = getChildAt(i); |
| 7855 | ViewHolder vh = getChildViewHolderInt(child); |
| 7856 | if (vh == null) { |
| 7857 | continue; |
| 7858 | } |
| 7859 | if (vh.getLayoutPosition() == position && !vh.shouldIgnore() |
| 7860 | && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) { |
| 7861 | return child; |
| 7862 | } |
| 7863 | } |
| 7864 | return null; |
| 7865 | } |
| 7866 | |
| 7867 | /** |
| 7868 | * Temporarily detach a child view. |
| 7869 | * |
| 7870 | * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange |
| 7871 | * views currently attached to the RecyclerView. Generally LayoutManager implementations |
| 7872 | * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} |
| 7873 | * so that the detached view may be rebound and reused.</p> |
| 7874 | * |
| 7875 | * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> |
| 7876 | * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} |
| 7877 | * or {@link #removeDetachedView(android.view.View) fully remove} the detached view |
| 7878 | * before the LayoutManager entry point method called by RecyclerView returns.</p> |
| 7879 | * |
| 7880 | * @param child Child to detach |
| 7881 | */ |
| 7882 | public void detachView(View child) { |
| 7883 | final int ind = mChildHelper.indexOfChild(child); |
| 7884 | if (ind >= 0) { |
| 7885 | detachViewInternal(ind, child); |
| 7886 | } |
| 7887 | } |
| 7888 | |
| 7889 | /** |
| 7890 | * Temporarily detach a child view. |
| 7891 | * |
| 7892 | * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange |
| 7893 | * views currently attached to the RecyclerView. Generally LayoutManager implementations |
| 7894 | * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} |
| 7895 | * so that the detached view may be rebound and reused.</p> |
| 7896 | * |
| 7897 | * <p>If a LayoutManager uses this method to detach a view, it <em>must</em> |
| 7898 | * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach} |
| 7899 | * or {@link #removeDetachedView(android.view.View) fully remove} the detached view |
| 7900 | * before the LayoutManager entry point method called by RecyclerView returns.</p> |
| 7901 | * |
| 7902 | * @param index Index of the child to detach |
| 7903 | */ |
| 7904 | public void detachViewAt(int index) { |
| 7905 | detachViewInternal(index, getChildAt(index)); |
| 7906 | } |
| 7907 | |
| 7908 | private void detachViewInternal(int index, View view) { |
| 7909 | if (DISPATCH_TEMP_DETACH) { |
| 7910 | view.dispatchStartTemporaryDetach(); |
| 7911 | } |
| 7912 | mChildHelper.detachViewFromParent(index); |
| 7913 | } |
| 7914 | |
| 7915 | /** |
| 7916 | * Reattach a previously {@link #detachView(android.view.View) detached} view. |
| 7917 | * This method should not be used to reattach views that were previously |
| 7918 | * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. |
| 7919 | * |
| 7920 | * @param child Child to reattach |
| 7921 | * @param index Intended child index for child |
| 7922 | * @param lp LayoutParams for child |
| 7923 | */ |
| 7924 | public void attachView(View child, int index, LayoutParams lp) { |
| 7925 | ViewHolder vh = getChildViewHolderInt(child); |
| 7926 | if (vh.isRemoved()) { |
| 7927 | mRecyclerView.mViewInfoStore.addToDisappearedInLayout(vh); |
| 7928 | } else { |
| 7929 | mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(vh); |
| 7930 | } |
| 7931 | mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved()); |
| 7932 | if (DISPATCH_TEMP_DETACH) { |
| 7933 | child.dispatchFinishTemporaryDetach(); |
| 7934 | } |
| 7935 | } |
| 7936 | |
| 7937 | /** |
| 7938 | * Reattach a previously {@link #detachView(android.view.View) detached} view. |
| 7939 | * This method should not be used to reattach views that were previously |
| 7940 | * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. |
| 7941 | * |
| 7942 | * @param child Child to reattach |
| 7943 | * @param index Intended child index for child |
| 7944 | */ |
| 7945 | public void attachView(View child, int index) { |
| 7946 | attachView(child, index, (LayoutParams) child.getLayoutParams()); |
| 7947 | } |
| 7948 | |
| 7949 | /** |
| 7950 | * Reattach a previously {@link #detachView(android.view.View) detached} view. |
| 7951 | * This method should not be used to reattach views that were previously |
| 7952 | * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)} scrapped}. |
| 7953 | * |
| 7954 | * @param child Child to reattach |
| 7955 | */ |
| 7956 | public void attachView(View child) { |
| 7957 | attachView(child, -1); |
| 7958 | } |
| 7959 | |
| 7960 | /** |
| 7961 | * Finish removing a view that was previously temporarily |
| 7962 | * {@link #detachView(android.view.View) detached}. |
| 7963 | * |
| 7964 | * @param child Detached child to remove |
| 7965 | */ |
| 7966 | public void removeDetachedView(View child) { |
| 7967 | mRecyclerView.removeDetachedView(child, false); |
| 7968 | } |
| 7969 | |
| 7970 | /** |
| 7971 | * Moves a View from one position to another. |
| 7972 | * |
| 7973 | * @param fromIndex The View's initial index |
| 7974 | * @param toIndex The View's target index |
| 7975 | */ |
| 7976 | public void moveView(int fromIndex, int toIndex) { |
| 7977 | View view = getChildAt(fromIndex); |
| 7978 | if (view == null) { |
| 7979 | throw new IllegalArgumentException("Cannot move a child from non-existing index:" |
| 7980 | + fromIndex); |
| 7981 | } |
| 7982 | detachViewAt(fromIndex); |
| 7983 | attachView(view, toIndex); |
| 7984 | } |
| 7985 | |
| 7986 | /** |
| 7987 | * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. |
| 7988 | * |
| 7989 | * <p>Scrapping a view allows it to be rebound and reused to show updated or |
| 7990 | * different data.</p> |
| 7991 | * |
| 7992 | * @param child Child to detach and scrap |
| 7993 | * @param recycler Recycler to deposit the new scrap view into |
| 7994 | */ |
| 7995 | public void detachAndScrapView(View child, Recycler recycler) { |
| 7996 | int index = mChildHelper.indexOfChild(child); |
| 7997 | scrapOrRecycleView(recycler, index, child); |
| 7998 | } |
| 7999 | |
| 8000 | /** |
| 8001 | * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap. |
| 8002 | * |
| 8003 | * <p>Scrapping a view allows it to be rebound and reused to show updated or |
| 8004 | * different data.</p> |
| 8005 | * |
| 8006 | * @param index Index of child to detach and scrap |
| 8007 | * @param recycler Recycler to deposit the new scrap view into |
| 8008 | */ |
| 8009 | public void detachAndScrapViewAt(int index, Recycler recycler) { |
| 8010 | final View child = getChildAt(index); |
| 8011 | scrapOrRecycleView(recycler, index, child); |
| 8012 | } |
| 8013 | |
| 8014 | /** |
| 8015 | * Remove a child view and recycle it using the given Recycler. |
| 8016 | * |
| 8017 | * @param child Child to remove and recycle |
| 8018 | * @param recycler Recycler to use to recycle child |
| 8019 | */ |
| 8020 | public void removeAndRecycleView(View child, Recycler recycler) { |
| 8021 | removeView(child); |
| 8022 | recycler.recycleView(child); |
| 8023 | } |
| 8024 | |
| 8025 | /** |
| 8026 | * Remove a child view and recycle it using the given Recycler. |
| 8027 | * |
| 8028 | * @param index Index of child to remove and recycle |
| 8029 | * @param recycler Recycler to use to recycle child |
| 8030 | */ |
| 8031 | public void removeAndRecycleViewAt(int index, Recycler recycler) { |
| 8032 | final View view = getChildAt(index); |
| 8033 | removeViewAt(index); |
| 8034 | recycler.recycleView(view); |
| 8035 | } |
| 8036 | |
| 8037 | /** |
| 8038 | * Return the current number of child views attached to the parent RecyclerView. |
| 8039 | * This does not include child views that were temporarily detached and/or scrapped. |
| 8040 | * |
| 8041 | * @return Number of attached children |
| 8042 | */ |
| 8043 | public int getChildCount() { |
| 8044 | return mChildHelper != null ? mChildHelper.getChildCount() : 0; |
| 8045 | } |
| 8046 | |
| 8047 | /** |
| 8048 | * Return the child view at the given index |
| 8049 | * @param index Index of child to return |
| 8050 | * @return Child view at index |
| 8051 | */ |
| 8052 | public View getChildAt(int index) { |
| 8053 | return mChildHelper != null ? mChildHelper.getChildAt(index) : null; |
| 8054 | } |
| 8055 | |
| 8056 | /** |
| 8057 | * Return the width measurement spec mode of the RecyclerView. |
| 8058 | * <p> |
| 8059 | * This value is set only if the LayoutManager opts into the auto measure api via |
| 8060 | * {@link #setAutoMeasureEnabled(boolean)}. |
| 8061 | * <p> |
| 8062 | * When RecyclerView is running a layout, this value is always set to |
| 8063 | * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. |
| 8064 | * |
| 8065 | * @return Width measure spec mode. |
| 8066 | * |
| 8067 | * @see View.MeasureSpec#getMode(int) |
| 8068 | * @see View#onMeasure(int, int) |
| 8069 | */ |
| 8070 | public int getWidthMode() { |
| 8071 | return mWidthMode; |
| 8072 | } |
| 8073 | |
| 8074 | /** |
| 8075 | * Return the height measurement spec mode of the RecyclerView. |
| 8076 | * <p> |
| 8077 | * This value is set only if the LayoutManager opts into the auto measure api via |
| 8078 | * {@link #setAutoMeasureEnabled(boolean)}. |
| 8079 | * <p> |
| 8080 | * When RecyclerView is running a layout, this value is always set to |
| 8081 | * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. |
| 8082 | * |
| 8083 | * @return Height measure spec mode. |
| 8084 | * |
| 8085 | * @see View.MeasureSpec#getMode(int) |
| 8086 | * @see View#onMeasure(int, int) |
| 8087 | */ |
| 8088 | public int getHeightMode() { |
| 8089 | return mHeightMode; |
| 8090 | } |
| 8091 | |
| 8092 | /** |
| 8093 | * Return the width of the parent RecyclerView |
| 8094 | * |
| 8095 | * @return Width in pixels |
| 8096 | */ |
| 8097 | public int getWidth() { |
| 8098 | return mWidth; |
| 8099 | } |
| 8100 | |
| 8101 | /** |
| 8102 | * Return the height of the parent RecyclerView |
| 8103 | * |
| 8104 | * @return Height in pixels |
| 8105 | */ |
| 8106 | public int getHeight() { |
| 8107 | return mHeight; |
| 8108 | } |
| 8109 | |
| 8110 | /** |
| 8111 | * Return the left padding of the parent RecyclerView |
| 8112 | * |
| 8113 | * @return Padding in pixels |
| 8114 | */ |
| 8115 | public int getPaddingLeft() { |
| 8116 | return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0; |
| 8117 | } |
| 8118 | |
| 8119 | /** |
| 8120 | * Return the top padding of the parent RecyclerView |
| 8121 | * |
| 8122 | * @return Padding in pixels |
| 8123 | */ |
| 8124 | public int getPaddingTop() { |
| 8125 | return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0; |
| 8126 | } |
| 8127 | |
| 8128 | /** |
| 8129 | * Return the right padding of the parent RecyclerView |
| 8130 | * |
| 8131 | * @return Padding in pixels |
| 8132 | */ |
| 8133 | public int getPaddingRight() { |
| 8134 | return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0; |
| 8135 | } |
| 8136 | |
| 8137 | /** |
| 8138 | * Return the bottom padding of the parent RecyclerView |
| 8139 | * |
| 8140 | * @return Padding in pixels |
| 8141 | */ |
| 8142 | public int getPaddingBottom() { |
| 8143 | return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0; |
| 8144 | } |
| 8145 | |
| 8146 | /** |
| 8147 | * Return the start padding of the parent RecyclerView |
| 8148 | * |
| 8149 | * @return Padding in pixels |
| 8150 | */ |
| 8151 | public int getPaddingStart() { |
| 8152 | return mRecyclerView != null ? mRecyclerView.getPaddingStart() : 0; |
| 8153 | } |
| 8154 | |
| 8155 | /** |
| 8156 | * Return the end padding of the parent RecyclerView |
| 8157 | * |
| 8158 | * @return Padding in pixels |
| 8159 | */ |
| 8160 | public int getPaddingEnd() { |
| 8161 | return mRecyclerView != null ? mRecyclerView.getPaddingEnd() : 0; |
| 8162 | } |
| 8163 | |
| 8164 | /** |
| 8165 | * Returns true if the RecyclerView this LayoutManager is bound to has focus. |
| 8166 | * |
| 8167 | * @return True if the RecyclerView has focus, false otherwise. |
| 8168 | * @see View#isFocused() |
| 8169 | */ |
| 8170 | public boolean isFocused() { |
| 8171 | return mRecyclerView != null && mRecyclerView.isFocused(); |
| 8172 | } |
| 8173 | |
| 8174 | /** |
| 8175 | * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus. |
| 8176 | * |
| 8177 | * @return true if the RecyclerView has or contains focus |
| 8178 | * @see View#hasFocus() |
| 8179 | */ |
| 8180 | public boolean hasFocus() { |
| 8181 | return mRecyclerView != null && mRecyclerView.hasFocus(); |
| 8182 | } |
| 8183 | |
| 8184 | /** |
| 8185 | * Returns the item View which has or contains focus. |
| 8186 | * |
| 8187 | * @return A direct child of RecyclerView which has focus or contains the focused child. |
| 8188 | */ |
| 8189 | public View getFocusedChild() { |
| 8190 | if (mRecyclerView == null) { |
| 8191 | return null; |
| 8192 | } |
| 8193 | final View focused = mRecyclerView.getFocusedChild(); |
| 8194 | if (focused == null || mChildHelper.isHidden(focused)) { |
| 8195 | return null; |
| 8196 | } |
| 8197 | return focused; |
| 8198 | } |
| 8199 | |
| 8200 | /** |
| 8201 | * Returns the number of items in the adapter bound to the parent RecyclerView. |
| 8202 | * <p> |
| 8203 | * Note that this number is not necessarily equal to |
| 8204 | * {@link State#getItemCount() State#getItemCount()}. In methods where {@link State} is |
| 8205 | * available, you should use {@link State#getItemCount() State#getItemCount()} instead. |
| 8206 | * For more details, check the documentation for |
| 8207 | * {@link State#getItemCount() State#getItemCount()}. |
| 8208 | * |
| 8209 | * @return The number of items in the bound adapter |
| 8210 | * @see State#getItemCount() |
| 8211 | */ |
| 8212 | public int getItemCount() { |
| 8213 | final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null; |
| 8214 | return a != null ? a.getItemCount() : 0; |
| 8215 | } |
| 8216 | |
| 8217 | /** |
| 8218 | * Offset all child views attached to the parent RecyclerView by dx pixels along |
| 8219 | * the horizontal axis. |
| 8220 | * |
| 8221 | * @param dx Pixels to offset by |
| 8222 | */ |
| 8223 | public void offsetChildrenHorizontal(int dx) { |
| 8224 | if (mRecyclerView != null) { |
| 8225 | mRecyclerView.offsetChildrenHorizontal(dx); |
| 8226 | } |
| 8227 | } |
| 8228 | |
| 8229 | /** |
| 8230 | * Offset all child views attached to the parent RecyclerView by dy pixels along |
| 8231 | * the vertical axis. |
| 8232 | * |
| 8233 | * @param dy Pixels to offset by |
| 8234 | */ |
| 8235 | public void offsetChildrenVertical(int dy) { |
| 8236 | if (mRecyclerView != null) { |
| 8237 | mRecyclerView.offsetChildrenVertical(dy); |
| 8238 | } |
| 8239 | } |
| 8240 | |
| 8241 | /** |
| 8242 | * Flags a view so that it will not be scrapped or recycled. |
| 8243 | * <p> |
| 8244 | * Scope of ignoring a child is strictly restricted to position tracking, scrapping and |
| 8245 | * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child |
| 8246 | * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not |
| 8247 | * ignore the child. |
| 8248 | * <p> |
| 8249 | * Before this child can be recycled again, you have to call |
| 8250 | * {@link #stopIgnoringView(View)}. |
| 8251 | * <p> |
| 8252 | * You can call this method only if your LayoutManger is in onLayout or onScroll callback. |
| 8253 | * |
| 8254 | * @param view View to ignore. |
| 8255 | * @see #stopIgnoringView(View) |
| 8256 | */ |
| 8257 | public void ignoreView(View view) { |
| 8258 | if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) { |
| 8259 | // checking this because calling this method on a recycled or detached view may |
| 8260 | // cause loss of state. |
| 8261 | throw new IllegalArgumentException("View should be fully attached to be ignored"); |
| 8262 | } |
| 8263 | final ViewHolder vh = getChildViewHolderInt(view); |
| 8264 | vh.addFlags(ViewHolder.FLAG_IGNORE); |
| 8265 | mRecyclerView.mViewInfoStore.removeViewHolder(vh); |
| 8266 | } |
| 8267 | |
| 8268 | /** |
| 8269 | * View can be scrapped and recycled again. |
| 8270 | * <p> |
| 8271 | * Note that calling this method removes all information in the view holder. |
| 8272 | * <p> |
| 8273 | * You can call this method only if your LayoutManger is in onLayout or onScroll callback. |
| 8274 | * |
| 8275 | * @param view View to ignore. |
| 8276 | */ |
| 8277 | public void stopIgnoringView(View view) { |
| 8278 | final ViewHolder vh = getChildViewHolderInt(view); |
| 8279 | vh.stopIgnoring(); |
| 8280 | vh.resetInternal(); |
| 8281 | vh.addFlags(ViewHolder.FLAG_INVALID); |
| 8282 | } |
| 8283 | |
| 8284 | /** |
| 8285 | * Temporarily detach and scrap all currently attached child views. Views will be scrapped |
| 8286 | * into the given Recycler. The Recycler may prefer to reuse scrap views before |
| 8287 | * other views that were previously recycled. |
| 8288 | * |
| 8289 | * @param recycler Recycler to scrap views into |
| 8290 | */ |
| 8291 | public void detachAndScrapAttachedViews(Recycler recycler) { |
| 8292 | final int childCount = getChildCount(); |
| 8293 | for (int i = childCount - 1; i >= 0; i--) { |
| 8294 | final View v = getChildAt(i); |
| 8295 | scrapOrRecycleView(recycler, i, v); |
| 8296 | } |
| 8297 | } |
| 8298 | |
| 8299 | private void scrapOrRecycleView(Recycler recycler, int index, View view) { |
| 8300 | final ViewHolder viewHolder = getChildViewHolderInt(view); |
| 8301 | if (viewHolder.shouldIgnore()) { |
| 8302 | if (DEBUG) { |
| 8303 | Log.d(TAG, "ignoring view " + viewHolder); |
| 8304 | } |
| 8305 | return; |
| 8306 | } |
| 8307 | if (viewHolder.isInvalid() && !viewHolder.isRemoved() |
| 8308 | && !mRecyclerView.mAdapter.hasStableIds()) { |
| 8309 | removeViewAt(index); |
| 8310 | recycler.recycleViewHolderInternal(viewHolder); |
| 8311 | } else { |
| 8312 | detachViewAt(index); |
| 8313 | recycler.scrapView(view); |
| 8314 | mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); |
| 8315 | } |
| 8316 | } |
| 8317 | |
| 8318 | /** |
| 8319 | * Recycles the scrapped views. |
| 8320 | * <p> |
| 8321 | * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is |
| 8322 | * the expected behavior if scrapped views are used for animations. Otherwise, we need to |
| 8323 | * call remove and invalidate RecyclerView to ensure UI update. |
| 8324 | * |
| 8325 | * @param recycler Recycler |
| 8326 | */ |
| 8327 | void removeAndRecycleScrapInt(Recycler recycler) { |
| 8328 | final int scrapCount = recycler.getScrapCount(); |
| 8329 | // Loop backward, recycler might be changed by removeDetachedView() |
| 8330 | for (int i = scrapCount - 1; i >= 0; i--) { |
| 8331 | final View scrap = recycler.getScrapViewAt(i); |
| 8332 | final ViewHolder vh = getChildViewHolderInt(scrap); |
| 8333 | if (vh.shouldIgnore()) { |
| 8334 | continue; |
| 8335 | } |
| 8336 | // If the scrap view is animating, we need to cancel them first. If we cancel it |
| 8337 | // here, ItemAnimator callback may recycle it which will cause double recycling. |
| 8338 | // To avoid this, we mark it as not recycleable before calling the item animator. |
| 8339 | // Since removeDetachedView calls a user API, a common mistake (ending animations on |
| 8340 | // the view) may recycle it too, so we guard it before we call user APIs. |
| 8341 | vh.setIsRecyclable(false); |
| 8342 | if (vh.isTmpDetached()) { |
| 8343 | mRecyclerView.removeDetachedView(scrap, false); |
| 8344 | } |
| 8345 | if (mRecyclerView.mItemAnimator != null) { |
| 8346 | mRecyclerView.mItemAnimator.endAnimation(vh); |
| 8347 | } |
| 8348 | vh.setIsRecyclable(true); |
| 8349 | recycler.quickRecycleScrapView(scrap); |
| 8350 | } |
| 8351 | recycler.clearScrap(); |
| 8352 | if (scrapCount > 0) { |
| 8353 | mRecyclerView.invalidate(); |
| 8354 | } |
| 8355 | } |
| 8356 | |
| 8357 | |
| 8358 | /** |
| 8359 | * Measure a child view using standard measurement policy, taking the padding |
| 8360 | * of the parent RecyclerView and any added item decorations into account. |
| 8361 | * |
| 8362 | * <p>If the RecyclerView can be scrolled in either dimension the caller may |
| 8363 | * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> |
| 8364 | * |
| 8365 | * @param child Child view to measure |
| 8366 | * @param widthUsed Width in pixels currently consumed by other views, if relevant |
| 8367 | * @param heightUsed Height in pixels currently consumed by other views, if relevant |
| 8368 | */ |
| 8369 | public void measureChild(View child, int widthUsed, int heightUsed) { |
| 8370 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| 8371 | |
| 8372 | final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); |
| 8373 | widthUsed += insets.left + insets.right; |
| 8374 | heightUsed += insets.top + insets.bottom; |
| 8375 | final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), |
| 8376 | getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, |
| 8377 | canScrollHorizontally()); |
| 8378 | final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), |
| 8379 | getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, |
| 8380 | canScrollVertically()); |
| 8381 | if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { |
| 8382 | child.measure(widthSpec, heightSpec); |
| 8383 | } |
| 8384 | } |
| 8385 | |
| 8386 | /** |
| 8387 | * RecyclerView internally does its own View measurement caching which should help with |
| 8388 | * WRAP_CONTENT. |
| 8389 | * <p> |
| 8390 | * Use this method if the View is already measured once in this layout pass. |
| 8391 | */ |
| 8392 | boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { |
| 8393 | return !mMeasurementCacheEnabled |
| 8394 | || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width) |
| 8395 | || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height); |
| 8396 | } |
| 8397 | |
| 8398 | // we may consider making this public |
| 8399 | /** |
| 8400 | * RecyclerView internally does its own View measurement caching which should help with |
| 8401 | * WRAP_CONTENT. |
| 8402 | * <p> |
| 8403 | * Use this method if the View is not yet measured and you need to decide whether to |
| 8404 | * measure this View or not. |
| 8405 | */ |
| 8406 | boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) { |
| 8407 | return child.isLayoutRequested() |
| 8408 | || !mMeasurementCacheEnabled |
| 8409 | || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width) |
| 8410 | || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height); |
| 8411 | } |
| 8412 | |
| 8413 | /** |
| 8414 | * In addition to the View Framework's measurement cache, RecyclerView uses its own |
| 8415 | * additional measurement cache for its children to avoid re-measuring them when not |
| 8416 | * necessary. It is on by default but it can be turned off via |
| 8417 | * {@link #setMeasurementCacheEnabled(boolean)}. |
| 8418 | * |
| 8419 | * @return True if measurement cache is enabled, false otherwise. |
| 8420 | * |
| 8421 | * @see #setMeasurementCacheEnabled(boolean) |
| 8422 | */ |
| 8423 | public boolean isMeasurementCacheEnabled() { |
| 8424 | return mMeasurementCacheEnabled; |
| 8425 | } |
| 8426 | |
| 8427 | /** |
| 8428 | * Sets whether RecyclerView should use its own measurement cache for the children. This is |
| 8429 | * a more aggressive cache than the framework uses. |
| 8430 | * |
| 8431 | * @param measurementCacheEnabled True to enable the measurement cache, false otherwise. |
| 8432 | * |
| 8433 | * @see #isMeasurementCacheEnabled() |
| 8434 | */ |
| 8435 | public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) { |
| 8436 | mMeasurementCacheEnabled = measurementCacheEnabled; |
| 8437 | } |
| 8438 | |
| 8439 | private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) { |
| 8440 | final int specMode = MeasureSpec.getMode(spec); |
| 8441 | final int specSize = MeasureSpec.getSize(spec); |
| 8442 | if (dimension > 0 && childSize != dimension) { |
| 8443 | return false; |
| 8444 | } |
| 8445 | switch (specMode) { |
| 8446 | case MeasureSpec.UNSPECIFIED: |
| 8447 | return true; |
| 8448 | case MeasureSpec.AT_MOST: |
| 8449 | return specSize >= childSize; |
| 8450 | case MeasureSpec.EXACTLY: |
| 8451 | return specSize == childSize; |
| 8452 | } |
| 8453 | return false; |
| 8454 | } |
| 8455 | |
| 8456 | /** |
| 8457 | * Measure a child view using standard measurement policy, taking the padding |
| 8458 | * of the parent RecyclerView, any added item decorations and the child margins |
| 8459 | * into account. |
| 8460 | * |
| 8461 | * <p>If the RecyclerView can be scrolled in either dimension the caller may |
| 8462 | * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p> |
| 8463 | * |
| 8464 | * @param child Child view to measure |
| 8465 | * @param widthUsed Width in pixels currently consumed by other views, if relevant |
| 8466 | * @param heightUsed Height in pixels currently consumed by other views, if relevant |
| 8467 | */ |
| 8468 | public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { |
| 8469 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| 8470 | |
| 8471 | final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); |
| 8472 | widthUsed += insets.left + insets.right; |
| 8473 | heightUsed += insets.top + insets.bottom; |
| 8474 | |
| 8475 | final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), |
| 8476 | getPaddingLeft() + getPaddingRight() |
| 8477 | + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, |
| 8478 | canScrollHorizontally()); |
| 8479 | final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), |
| 8480 | getPaddingTop() + getPaddingBottom() |
| 8481 | + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, |
| 8482 | canScrollVertically()); |
| 8483 | if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { |
| 8484 | child.measure(widthSpec, heightSpec); |
| 8485 | } |
| 8486 | } |
| 8487 | |
| 8488 | /** |
| 8489 | * Calculate a MeasureSpec value for measuring a child view in one dimension. |
| 8490 | * |
| 8491 | * @param parentSize Size of the parent view where the child will be placed |
| 8492 | * @param padding Total space currently consumed by other elements of the parent |
| 8493 | * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. |
| 8494 | * Generally obtained from the child view's LayoutParams |
| 8495 | * @param canScroll true if the parent RecyclerView can scroll in this dimension |
| 8496 | * |
| 8497 | * @return a MeasureSpec value for the child view |
| 8498 | * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)} |
| 8499 | */ |
| 8500 | @Deprecated |
| 8501 | public static int getChildMeasureSpec(int parentSize, int padding, int childDimension, |
| 8502 | boolean canScroll) { |
| 8503 | int size = Math.max(0, parentSize - padding); |
| 8504 | int resultSize = 0; |
| 8505 | int resultMode = 0; |
| 8506 | if (canScroll) { |
| 8507 | if (childDimension >= 0) { |
| 8508 | resultSize = childDimension; |
| 8509 | resultMode = MeasureSpec.EXACTLY; |
| 8510 | } else { |
| 8511 | // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap |
| 8512 | // instead using UNSPECIFIED. |
| 8513 | resultSize = 0; |
| 8514 | resultMode = MeasureSpec.UNSPECIFIED; |
| 8515 | } |
| 8516 | } else { |
| 8517 | if (childDimension >= 0) { |
| 8518 | resultSize = childDimension; |
| 8519 | resultMode = MeasureSpec.EXACTLY; |
| 8520 | } else if (childDimension == LayoutParams.MATCH_PARENT) { |
| 8521 | resultSize = size; |
| 8522 | // TODO this should be my spec. |
| 8523 | resultMode = MeasureSpec.EXACTLY; |
| 8524 | } else if (childDimension == LayoutParams.WRAP_CONTENT) { |
| 8525 | resultSize = size; |
| 8526 | resultMode = MeasureSpec.AT_MOST; |
| 8527 | } |
| 8528 | } |
| 8529 | return MeasureSpec.makeMeasureSpec(resultSize, resultMode); |
| 8530 | } |
| 8531 | |
| 8532 | /** |
| 8533 | * Calculate a MeasureSpec value for measuring a child view in one dimension. |
| 8534 | * |
| 8535 | * @param parentSize Size of the parent view where the child will be placed |
| 8536 | * @param parentMode The measurement spec mode of the parent |
| 8537 | * @param padding Total space currently consumed by other elements of parent |
| 8538 | * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. |
| 8539 | * Generally obtained from the child view's LayoutParams |
| 8540 | * @param canScroll true if the parent RecyclerView can scroll in this dimension |
| 8541 | * |
| 8542 | * @return a MeasureSpec value for the child view |
| 8543 | */ |
| 8544 | public static int getChildMeasureSpec(int parentSize, int parentMode, int padding, |
| 8545 | int childDimension, boolean canScroll) { |
| 8546 | int size = Math.max(0, parentSize - padding); |
| 8547 | int resultSize = 0; |
| 8548 | int resultMode = 0; |
| 8549 | if (canScroll) { |
| 8550 | if (childDimension >= 0) { |
| 8551 | resultSize = childDimension; |
| 8552 | resultMode = MeasureSpec.EXACTLY; |
| 8553 | } else if (childDimension == LayoutParams.MATCH_PARENT) { |
| 8554 | switch (parentMode) { |
| 8555 | case MeasureSpec.AT_MOST: |
| 8556 | case MeasureSpec.EXACTLY: |
| 8557 | resultSize = size; |
| 8558 | resultMode = parentMode; |
| 8559 | break; |
| 8560 | case MeasureSpec.UNSPECIFIED: |
| 8561 | resultSize = 0; |
| 8562 | resultMode = MeasureSpec.UNSPECIFIED; |
| 8563 | break; |
| 8564 | } |
| 8565 | } else if (childDimension == LayoutParams.WRAP_CONTENT) { |
| 8566 | resultSize = 0; |
| 8567 | resultMode = MeasureSpec.UNSPECIFIED; |
| 8568 | } |
| 8569 | } else { |
| 8570 | if (childDimension >= 0) { |
| 8571 | resultSize = childDimension; |
| 8572 | resultMode = MeasureSpec.EXACTLY; |
| 8573 | } else if (childDimension == LayoutParams.MATCH_PARENT) { |
| 8574 | resultSize = size; |
| 8575 | resultMode = parentMode; |
| 8576 | } else if (childDimension == LayoutParams.WRAP_CONTENT) { |
| 8577 | resultSize = size; |
| 8578 | if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) { |
| 8579 | resultMode = MeasureSpec.AT_MOST; |
| 8580 | } else { |
| 8581 | resultMode = MeasureSpec.UNSPECIFIED; |
| 8582 | } |
| 8583 | |
| 8584 | } |
| 8585 | } |
| 8586 | //noinspection WrongConstant |
| 8587 | return MeasureSpec.makeMeasureSpec(resultSize, resultMode); |
| 8588 | } |
| 8589 | |
| 8590 | /** |
| 8591 | * Returns the measured width of the given child, plus the additional size of |
| 8592 | * any insets applied by {@link ItemDecoration ItemDecorations}. |
| 8593 | * |
| 8594 | * @param child Child view to query |
| 8595 | * @return child's measured width plus <code>ItemDecoration</code> insets |
| 8596 | * |
| 8597 | * @see View#getMeasuredWidth() |
| 8598 | */ |
| 8599 | public int getDecoratedMeasuredWidth(View child) { |
| 8600 | final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; |
| 8601 | return child.getMeasuredWidth() + insets.left + insets.right; |
| 8602 | } |
| 8603 | |
| 8604 | /** |
| 8605 | * Returns the measured height of the given child, plus the additional size of |
| 8606 | * any insets applied by {@link ItemDecoration ItemDecorations}. |
| 8607 | * |
| 8608 | * @param child Child view to query |
| 8609 | * @return child's measured height plus <code>ItemDecoration</code> insets |
| 8610 | * |
| 8611 | * @see View#getMeasuredHeight() |
| 8612 | */ |
| 8613 | public int getDecoratedMeasuredHeight(View child) { |
| 8614 | final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; |
| 8615 | return child.getMeasuredHeight() + insets.top + insets.bottom; |
| 8616 | } |
| 8617 | |
| 8618 | /** |
| 8619 | * Lay out the given child view within the RecyclerView using coordinates that |
| 8620 | * include any current {@link ItemDecoration ItemDecorations}. |
| 8621 | * |
| 8622 | * <p>LayoutManagers should prefer working in sizes and coordinates that include |
| 8623 | * item decoration insets whenever possible. This allows the LayoutManager to effectively |
| 8624 | * ignore decoration insets within measurement and layout code. See the following |
| 8625 | * methods:</p> |
| 8626 | * <ul> |
| 8627 | * <li>{@link #layoutDecoratedWithMargins(View, int, int, int, int)}</li> |
| 8628 | * <li>{@link #getDecoratedBoundsWithMargins(View, Rect)}</li> |
| 8629 | * <li>{@link #measureChild(View, int, int)}</li> |
| 8630 | * <li>{@link #measureChildWithMargins(View, int, int)}</li> |
| 8631 | * <li>{@link #getDecoratedLeft(View)}</li> |
| 8632 | * <li>{@link #getDecoratedTop(View)}</li> |
| 8633 | * <li>{@link #getDecoratedRight(View)}</li> |
| 8634 | * <li>{@link #getDecoratedBottom(View)}</li> |
| 8635 | * <li>{@link #getDecoratedMeasuredWidth(View)}</li> |
| 8636 | * <li>{@link #getDecoratedMeasuredHeight(View)}</li> |
| 8637 | * </ul> |
| 8638 | * |
| 8639 | * @param child Child to lay out |
| 8640 | * @param left Left edge, with item decoration insets included |
| 8641 | * @param top Top edge, with item decoration insets included |
| 8642 | * @param right Right edge, with item decoration insets included |
| 8643 | * @param bottom Bottom edge, with item decoration insets included |
| 8644 | * |
| 8645 | * @see View#layout(int, int, int, int) |
| 8646 | * @see #layoutDecoratedWithMargins(View, int, int, int, int) |
| 8647 | */ |
| 8648 | public void layoutDecorated(View child, int left, int top, int right, int bottom) { |
| 8649 | final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; |
| 8650 | child.layout(left + insets.left, top + insets.top, right - insets.right, |
| 8651 | bottom - insets.bottom); |
| 8652 | } |
| 8653 | |
| 8654 | /** |
| 8655 | * Lay out the given child view within the RecyclerView using coordinates that |
| 8656 | * include any current {@link ItemDecoration ItemDecorations} and margins. |
| 8657 | * |
| 8658 | * <p>LayoutManagers should prefer working in sizes and coordinates that include |
| 8659 | * item decoration insets whenever possible. This allows the LayoutManager to effectively |
| 8660 | * ignore decoration insets within measurement and layout code. See the following |
| 8661 | * methods:</p> |
| 8662 | * <ul> |
| 8663 | * <li>{@link #layoutDecorated(View, int, int, int, int)}</li> |
| 8664 | * <li>{@link #measureChild(View, int, int)}</li> |
| 8665 | * <li>{@link #measureChildWithMargins(View, int, int)}</li> |
| 8666 | * <li>{@link #getDecoratedLeft(View)}</li> |
| 8667 | * <li>{@link #getDecoratedTop(View)}</li> |
| 8668 | * <li>{@link #getDecoratedRight(View)}</li> |
| 8669 | * <li>{@link #getDecoratedBottom(View)}</li> |
| 8670 | * <li>{@link #getDecoratedMeasuredWidth(View)}</li> |
| 8671 | * <li>{@link #getDecoratedMeasuredHeight(View)}</li> |
| 8672 | * </ul> |
| 8673 | * |
| 8674 | * @param child Child to lay out |
| 8675 | * @param left Left edge, with item decoration insets and left margin included |
| 8676 | * @param top Top edge, with item decoration insets and top margin included |
| 8677 | * @param right Right edge, with item decoration insets and right margin included |
| 8678 | * @param bottom Bottom edge, with item decoration insets and bottom margin included |
| 8679 | * |
| 8680 | * @see View#layout(int, int, int, int) |
| 8681 | * @see #layoutDecorated(View, int, int, int, int) |
| 8682 | */ |
| 8683 | public void layoutDecoratedWithMargins(View child, int left, int top, int right, |
| 8684 | int bottom) { |
| 8685 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
| 8686 | final Rect insets = lp.mDecorInsets; |
| 8687 | child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin, |
| 8688 | right - insets.right - lp.rightMargin, |
| 8689 | bottom - insets.bottom - lp.bottomMargin); |
| 8690 | } |
| 8691 | |
| 8692 | /** |
| 8693 | * Calculates the bounding box of the View while taking into account its matrix changes |
| 8694 | * (translation, scale etc) with respect to the RecyclerView. |
| 8695 | * <p> |
| 8696 | * If {@code includeDecorInsets} is {@code true}, they are applied first before applying |
| 8697 | * the View's matrix so that the decor offsets also go through the same transformation. |
| 8698 | * |
| 8699 | * @param child The ItemView whose bounding box should be calculated. |
| 8700 | * @param includeDecorInsets True if the decor insets should be included in the bounding box |
| 8701 | * @param out The rectangle into which the output will be written. |
| 8702 | */ |
| 8703 | public void getTransformedBoundingBox(View child, boolean includeDecorInsets, Rect out) { |
| 8704 | if (includeDecorInsets) { |
| 8705 | Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; |
| 8706 | out.set(-insets.left, -insets.top, |
| 8707 | child.getWidth() + insets.right, child.getHeight() + insets.bottom); |
| 8708 | } else { |
| 8709 | out.set(0, 0, child.getWidth(), child.getHeight()); |
| 8710 | } |
| 8711 | |
| 8712 | if (mRecyclerView != null) { |
| 8713 | final Matrix childMatrix = child.getMatrix(); |
| 8714 | if (childMatrix != null && !childMatrix.isIdentity()) { |
| 8715 | final RectF tempRectF = mRecyclerView.mTempRectF; |
| 8716 | tempRectF.set(out); |
| 8717 | childMatrix.mapRect(tempRectF); |
| 8718 | out.set( |
| 8719 | (int) Math.floor(tempRectF.left), |
| 8720 | (int) Math.floor(tempRectF.top), |
| 8721 | (int) Math.ceil(tempRectF.right), |
| 8722 | (int) Math.ceil(tempRectF.bottom) |
| 8723 | ); |
| 8724 | } |
| 8725 | } |
| 8726 | out.offset(child.getLeft(), child.getTop()); |
| 8727 | } |
| 8728 | |
| 8729 | /** |
| 8730 | * Returns the bounds of the view including its decoration and margins. |
| 8731 | * |
| 8732 | * @param view The view element to check |
| 8733 | * @param outBounds A rect that will receive the bounds of the element including its |
| 8734 | * decoration and margins. |
| 8735 | */ |
| 8736 | public void getDecoratedBoundsWithMargins(View view, Rect outBounds) { |
| 8737 | RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds); |
| 8738 | } |
| 8739 | |
| 8740 | /** |
| 8741 | * Returns the left edge of the given child view within its parent, offset by any applied |
| 8742 | * {@link ItemDecoration ItemDecorations}. |
| 8743 | * |
| 8744 | * @param child Child to query |
| 8745 | * @return Child left edge with offsets applied |
| 8746 | * @see #getLeftDecorationWidth(View) |
| 8747 | */ |
| 8748 | public int getDecoratedLeft(View child) { |
| 8749 | return child.getLeft() - getLeftDecorationWidth(child); |
| 8750 | } |
| 8751 | |
| 8752 | /** |
| 8753 | * Returns the top edge of the given child view within its parent, offset by any applied |
| 8754 | * {@link ItemDecoration ItemDecorations}. |
| 8755 | * |
| 8756 | * @param child Child to query |
| 8757 | * @return Child top edge with offsets applied |
| 8758 | * @see #getTopDecorationHeight(View) |
| 8759 | */ |
| 8760 | public int getDecoratedTop(View child) { |
| 8761 | return child.getTop() - getTopDecorationHeight(child); |
| 8762 | } |
| 8763 | |
| 8764 | /** |
| 8765 | * Returns the right edge of the given child view within its parent, offset by any applied |
| 8766 | * {@link ItemDecoration ItemDecorations}. |
| 8767 | * |
| 8768 | * @param child Child to query |
| 8769 | * @return Child right edge with offsets applied |
| 8770 | * @see #getRightDecorationWidth(View) |
| 8771 | */ |
| 8772 | public int getDecoratedRight(View child) { |
| 8773 | return child.getRight() + getRightDecorationWidth(child); |
| 8774 | } |
| 8775 | |
| 8776 | /** |
| 8777 | * Returns the bottom edge of the given child view within its parent, offset by any applied |
| 8778 | * {@link ItemDecoration ItemDecorations}. |
| 8779 | * |
| 8780 | * @param child Child to query |
| 8781 | * @return Child bottom edge with offsets applied |
| 8782 | * @see #getBottomDecorationHeight(View) |
| 8783 | */ |
| 8784 | public int getDecoratedBottom(View child) { |
| 8785 | return child.getBottom() + getBottomDecorationHeight(child); |
| 8786 | } |
| 8787 | |
| 8788 | /** |
| 8789 | * Calculates the item decor insets applied to the given child and updates the provided |
| 8790 | * Rect instance with the inset values. |
| 8791 | * <ul> |
| 8792 | * <li>The Rect's left is set to the total width of left decorations.</li> |
| 8793 | * <li>The Rect's top is set to the total height of top decorations.</li> |
| 8794 | * <li>The Rect's right is set to the total width of right decorations.</li> |
| 8795 | * <li>The Rect's bottom is set to total height of bottom decorations.</li> |
| 8796 | * </ul> |
| 8797 | * <p> |
| 8798 | * Note that item decorations are automatically calculated when one of the LayoutManager's |
| 8799 | * measure child methods is called. If you need to measure the child with custom specs via |
| 8800 | * {@link View#measure(int, int)}, you can use this method to get decorations. |
| 8801 | * |
| 8802 | * @param child The child view whose decorations should be calculated |
| 8803 | * @param outRect The Rect to hold result values |
| 8804 | */ |
| 8805 | public void calculateItemDecorationsForChild(View child, Rect outRect) { |
| 8806 | if (mRecyclerView == null) { |
| 8807 | outRect.set(0, 0, 0, 0); |
| 8808 | return; |
| 8809 | } |
| 8810 | Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); |
| 8811 | outRect.set(insets); |
| 8812 | } |
| 8813 | |
| 8814 | /** |
| 8815 | * Returns the total height of item decorations applied to child's top. |
| 8816 | * <p> |
| 8817 | * Note that this value is not updated until the View is measured or |
| 8818 | * {@link #calculateItemDecorationsForChild(View, Rect)} is called. |
| 8819 | * |
| 8820 | * @param child Child to query |
| 8821 | * @return The total height of item decorations applied to the child's top. |
| 8822 | * @see #getDecoratedTop(View) |
| 8823 | * @see #calculateItemDecorationsForChild(View, Rect) |
| 8824 | */ |
| 8825 | public int getTopDecorationHeight(View child) { |
| 8826 | return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top; |
| 8827 | } |
| 8828 | |
| 8829 | /** |
| 8830 | * Returns the total height of item decorations applied to child's bottom. |
| 8831 | * <p> |
| 8832 | * Note that this value is not updated until the View is measured or |
| 8833 | * {@link #calculateItemDecorationsForChild(View, Rect)} is called. |
| 8834 | * |
| 8835 | * @param child Child to query |
| 8836 | * @return The total height of item decorations applied to the child's bottom. |
| 8837 | * @see #getDecoratedBottom(View) |
| 8838 | * @see #calculateItemDecorationsForChild(View, Rect) |
| 8839 | */ |
| 8840 | public int getBottomDecorationHeight(View child) { |
| 8841 | return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom; |
| 8842 | } |
| 8843 | |
| 8844 | /** |
| 8845 | * Returns the total width of item decorations applied to child's left. |
| 8846 | * <p> |
| 8847 | * Note that this value is not updated until the View is measured or |
| 8848 | * {@link #calculateItemDecorationsForChild(View, Rect)} is called. |
| 8849 | * |
| 8850 | * @param child Child to query |
| 8851 | * @return The total width of item decorations applied to the child's left. |
| 8852 | * @see #getDecoratedLeft(View) |
| 8853 | * @see #calculateItemDecorationsForChild(View, Rect) |
| 8854 | */ |
| 8855 | public int getLeftDecorationWidth(View child) { |
| 8856 | return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left; |
| 8857 | } |
| 8858 | |
| 8859 | /** |
| 8860 | * Returns the total width of item decorations applied to child's right. |
| 8861 | * <p> |
| 8862 | * Note that this value is not updated until the View is measured or |
| 8863 | * {@link #calculateItemDecorationsForChild(View, Rect)} is called. |
| 8864 | * |
| 8865 | * @param child Child to query |
| 8866 | * @return The total width of item decorations applied to the child's right. |
| 8867 | * @see #getDecoratedRight(View) |
| 8868 | * @see #calculateItemDecorationsForChild(View, Rect) |
| 8869 | */ |
| 8870 | public int getRightDecorationWidth(View child) { |
| 8871 | return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right; |
| 8872 | } |
| 8873 | |
| 8874 | /** |
| 8875 | * Called when searching for a focusable view in the given direction has failed |
| 8876 | * for the current content of the RecyclerView. |
| 8877 | * |
| 8878 | * <p>This is the LayoutManager's opportunity to populate views in the given direction |
| 8879 | * to fulfill the request if it can. The LayoutManager should attach and return |
| 8880 | * the view to be focused. The default implementation returns null.</p> |
| 8881 | * |
| 8882 | * @param focused The currently focused view |
| 8883 | * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, |
| 8884 | * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, |
| 8885 | * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} |
| 8886 | * or 0 for not applicable |
| 8887 | * @param recycler The recycler to use for obtaining views for currently offscreen items |
| 8888 | * @param state Transient state of RecyclerView |
| 8889 | * @return The chosen view to be focused |
| 8890 | */ |
| 8891 | @Nullable |
| 8892 | public View onFocusSearchFailed(View focused, int direction, Recycler recycler, |
| 8893 | State state) { |
| 8894 | return null; |
| 8895 | } |
| 8896 | |
| 8897 | /** |
| 8898 | * This method gives a LayoutManager an opportunity to intercept the initial focus search |
| 8899 | * before the default behavior of {@link FocusFinder} is used. If this method returns |
| 8900 | * null FocusFinder will attempt to find a focusable child view. If it fails |
| 8901 | * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} |
| 8902 | * will be called to give the LayoutManager an opportunity to add new views for items |
| 8903 | * that did not have attached views representing them. The LayoutManager should not add |
| 8904 | * or remove views from this method. |
| 8905 | * |
| 8906 | * @param focused The currently focused view |
| 8907 | * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, |
| 8908 | * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, |
| 8909 | * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} |
| 8910 | * @return A descendant view to focus or null to fall back to default behavior. |
| 8911 | * The default implementation returns null. |
| 8912 | */ |
| 8913 | public View onInterceptFocusSearch(View focused, int direction) { |
| 8914 | return null; |
| 8915 | } |
| 8916 | |
| 8917 | /** |
| 8918 | * Called when a child of the RecyclerView wants a particular rectangle to be positioned |
| 8919 | * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View, |
| 8920 | * android.graphics.Rect, boolean)} for more details. |
| 8921 | * |
| 8922 | * <p>The base implementation will attempt to perform a standard programmatic scroll |
| 8923 | * to bring the given rect into view, within the padded area of the RecyclerView.</p> |
| 8924 | * |
| 8925 | * @param child The direct child making the request. |
| 8926 | * @param rect The rectangle in the child's coordinates the child |
| 8927 | * wishes to be on the screen. |
| 8928 | * @param immediate True to forbid animated or delayed scrolling, |
| 8929 | * false otherwise |
| 8930 | * @return Whether the group scrolled to handle the operation |
| 8931 | */ |
| 8932 | public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect, |
| 8933 | boolean immediate) { |
| 8934 | final int parentLeft = getPaddingLeft(); |
| 8935 | final int parentTop = getPaddingTop(); |
| 8936 | final int parentRight = getWidth() - getPaddingRight(); |
| 8937 | final int parentBottom = getHeight() - getPaddingBottom(); |
| 8938 | final int childLeft = child.getLeft() + rect.left - child.getScrollX(); |
| 8939 | final int childTop = child.getTop() + rect.top - child.getScrollY(); |
| 8940 | final int childRight = childLeft + rect.width(); |
| 8941 | final int childBottom = childTop + rect.height(); |
| 8942 | |
| 8943 | final int offScreenLeft = Math.min(0, childLeft - parentLeft); |
| 8944 | final int offScreenTop = Math.min(0, childTop - parentTop); |
| 8945 | final int offScreenRight = Math.max(0, childRight - parentRight); |
| 8946 | final int offScreenBottom = Math.max(0, childBottom - parentBottom); |
| 8947 | |
| 8948 | // Favor the "start" layout direction over the end when bringing one side or the other |
| 8949 | // of a large rect into view. If we decide to bring in end because start is already |
| 8950 | // visible, limit the scroll such that start won't go out of bounds. |
| 8951 | final int dx; |
| 8952 | if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { |
| 8953 | dx = offScreenRight != 0 ? offScreenRight |
| 8954 | : Math.max(offScreenLeft, childRight - parentRight); |
| 8955 | } else { |
| 8956 | dx = offScreenLeft != 0 ? offScreenLeft |
| 8957 | : Math.min(childLeft - parentLeft, offScreenRight); |
| 8958 | } |
| 8959 | |
| 8960 | // Favor bringing the top into view over the bottom. If top is already visible and |
| 8961 | // we should scroll to make bottom visible, make sure top does not go out of bounds. |
| 8962 | final int dy = offScreenTop != 0 ? offScreenTop |
| 8963 | : Math.min(childTop - parentTop, offScreenBottom); |
| 8964 | |
| 8965 | if (dx != 0 || dy != 0) { |
| 8966 | if (immediate) { |
| 8967 | parent.scrollBy(dx, dy); |
| 8968 | } else { |
| 8969 | parent.smoothScrollBy(dx, dy); |
| 8970 | } |
| 8971 | return true; |
| 8972 | } |
| 8973 | return false; |
| 8974 | } |
| 8975 | |
| 8976 | /** |
| 8977 | * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)} |
| 8978 | */ |
| 8979 | @Deprecated |
| 8980 | public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) { |
| 8981 | // eat the request if we are in the middle of a scroll or layout |
| 8982 | return isSmoothScrolling() || parent.isComputingLayout(); |
| 8983 | } |
| 8984 | |
| 8985 | /** |
| 8986 | * Called when a descendant view of the RecyclerView requests focus. |
| 8987 | * |
| 8988 | * <p>A LayoutManager wishing to keep focused views aligned in a specific |
| 8989 | * portion of the view may implement that behavior in an override of this method.</p> |
| 8990 | * |
| 8991 | * <p>If the LayoutManager executes different behavior that should override the default |
| 8992 | * behavior of scrolling the focused child on screen instead of running alongside it, |
| 8993 | * this method should return true.</p> |
| 8994 | * |
| 8995 | * @param parent The RecyclerView hosting this LayoutManager |
| 8996 | * @param state Current state of RecyclerView |
| 8997 | * @param child Direct child of the RecyclerView containing the newly focused view |
| 8998 | * @param focused The newly focused view. This may be the same view as child or it may be |
| 8999 | * null |
| 9000 | * @return true if the default scroll behavior should be suppressed |
| 9001 | */ |
| 9002 | public boolean onRequestChildFocus(RecyclerView parent, State state, View child, |
| 9003 | View focused) { |
| 9004 | return onRequestChildFocus(parent, child, focused); |
| 9005 | } |
| 9006 | |
| 9007 | /** |
| 9008 | * Called if the RecyclerView this LayoutManager is bound to has a different adapter set. |
| 9009 | * The LayoutManager may use this opportunity to clear caches and configure state such |
| 9010 | * that it can relayout appropriately with the new data and potentially new view types. |
| 9011 | * |
| 9012 | * <p>The default implementation removes all currently attached views.</p> |
| 9013 | * |
| 9014 | * @param oldAdapter The previous adapter instance. Will be null if there was previously no |
| 9015 | * adapter. |
| 9016 | * @param newAdapter The new adapter instance. Might be null if |
| 9017 | * {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}. |
| 9018 | */ |
| 9019 | public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) { |
| 9020 | } |
| 9021 | |
| 9022 | /** |
| 9023 | * Called to populate focusable views within the RecyclerView. |
| 9024 | * |
| 9025 | * <p>The LayoutManager implementation should return <code>true</code> if the default |
| 9026 | * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be |
| 9027 | * suppressed.</p> |
| 9028 | * |
| 9029 | * <p>The default implementation returns <code>false</code> to trigger RecyclerView |
| 9030 | * to fall back to the default ViewGroup behavior.</p> |
| 9031 | * |
| 9032 | * @param recyclerView The RecyclerView hosting this LayoutManager |
| 9033 | * @param views List of output views. This method should add valid focusable views |
| 9034 | * to this list. |
| 9035 | * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, |
| 9036 | * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, |
| 9037 | * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD} |
| 9038 | * @param focusableMode The type of focusables to be added. |
| 9039 | * |
| 9040 | * @return true to suppress the default behavior, false to add default focusables after |
| 9041 | * this method returns. |
| 9042 | * |
| 9043 | * @see #FOCUSABLES_ALL |
| 9044 | * @see #FOCUSABLES_TOUCH_MODE |
| 9045 | */ |
| 9046 | public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views, |
| 9047 | int direction, int focusableMode) { |
| 9048 | return false; |
| 9049 | } |
| 9050 | |
| 9051 | /** |
| 9052 | * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving |
| 9053 | * detailed information on what has actually changed. |
| 9054 | * |
| 9055 | * @param recyclerView |
| 9056 | */ |
| 9057 | public void onItemsChanged(RecyclerView recyclerView) { |
| 9058 | } |
| 9059 | |
| 9060 | /** |
| 9061 | * Called when items have been added to the adapter. The LayoutManager may choose to |
| 9062 | * requestLayout if the inserted items would require refreshing the currently visible set |
| 9063 | * of child views. (e.g. currently empty space would be filled by appended items, etc.) |
| 9064 | * |
| 9065 | * @param recyclerView |
| 9066 | * @param positionStart |
| 9067 | * @param itemCount |
| 9068 | */ |
| 9069 | public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) { |
| 9070 | } |
| 9071 | |
| 9072 | /** |
| 9073 | * Called when items have been removed from the adapter. |
| 9074 | * |
| 9075 | * @param recyclerView |
| 9076 | * @param positionStart |
| 9077 | * @param itemCount |
| 9078 | */ |
| 9079 | public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { |
| 9080 | } |
| 9081 | |
| 9082 | /** |
| 9083 | * Called when items have been changed in the adapter. |
| 9084 | * To receive payload, override {@link #onItemsUpdated(RecyclerView, int, int, Object)} |
| 9085 | * instead, then this callback will not be invoked. |
| 9086 | * |
| 9087 | * @param recyclerView |
| 9088 | * @param positionStart |
| 9089 | * @param itemCount |
| 9090 | */ |
| 9091 | public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) { |
| 9092 | } |
| 9093 | |
| 9094 | /** |
| 9095 | * Called when items have been changed in the adapter and with optional payload. |
| 9096 | * Default implementation calls {@link #onItemsUpdated(RecyclerView, int, int)}. |
| 9097 | * |
| 9098 | * @param recyclerView |
| 9099 | * @param positionStart |
| 9100 | * @param itemCount |
| 9101 | * @param payload |
| 9102 | */ |
| 9103 | public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, |
| 9104 | Object payload) { |
| 9105 | onItemsUpdated(recyclerView, positionStart, itemCount); |
| 9106 | } |
| 9107 | |
| 9108 | /** |
| 9109 | * Called when an item is moved withing the adapter. |
| 9110 | * <p> |
| 9111 | * Note that, an item may also change position in response to another ADD/REMOVE/MOVE |
| 9112 | * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved} |
| 9113 | * is called. |
| 9114 | * |
| 9115 | * @param recyclerView |
| 9116 | * @param from |
| 9117 | * @param to |
| 9118 | * @param itemCount |
| 9119 | */ |
| 9120 | public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) { |
| 9121 | |
| 9122 | } |
| 9123 | |
| 9124 | |
| 9125 | /** |
| 9126 | * <p>Override this method if you want to support scroll bars.</p> |
| 9127 | * |
| 9128 | * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p> |
| 9129 | * |
| 9130 | * <p>Default implementation returns 0.</p> |
| 9131 | * |
| 9132 | * @param state Current state of RecyclerView |
| 9133 | * @return The horizontal extent of the scrollbar's thumb |
| 9134 | * @see RecyclerView#computeHorizontalScrollExtent() |
| 9135 | */ |
| 9136 | public int computeHorizontalScrollExtent(State state) { |
| 9137 | return 0; |
| 9138 | } |
| 9139 | |
| 9140 | /** |
| 9141 | * <p>Override this method if you want to support scroll bars.</p> |
| 9142 | * |
| 9143 | * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p> |
| 9144 | * |
| 9145 | * <p>Default implementation returns 0.</p> |
| 9146 | * |
| 9147 | * @param state Current State of RecyclerView where you can find total item count |
| 9148 | * @return The horizontal offset of the scrollbar's thumb |
| 9149 | * @see RecyclerView#computeHorizontalScrollOffset() |
| 9150 | */ |
| 9151 | public int computeHorizontalScrollOffset(State state) { |
| 9152 | return 0; |
| 9153 | } |
| 9154 | |
| 9155 | /** |
| 9156 | * <p>Override this method if you want to support scroll bars.</p> |
| 9157 | * |
| 9158 | * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p> |
| 9159 | * |
| 9160 | * <p>Default implementation returns 0.</p> |
| 9161 | * |
| 9162 | * @param state Current State of RecyclerView where you can find total item count |
| 9163 | * @return The total horizontal range represented by the vertical scrollbar |
| 9164 | * @see RecyclerView#computeHorizontalScrollRange() |
| 9165 | */ |
| 9166 | public int computeHorizontalScrollRange(State state) { |
| 9167 | return 0; |
| 9168 | } |
| 9169 | |
| 9170 | /** |
| 9171 | * <p>Override this method if you want to support scroll bars.</p> |
| 9172 | * |
| 9173 | * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p> |
| 9174 | * |
| 9175 | * <p>Default implementation returns 0.</p> |
| 9176 | * |
| 9177 | * @param state Current state of RecyclerView |
| 9178 | * @return The vertical extent of the scrollbar's thumb |
| 9179 | * @see RecyclerView#computeVerticalScrollExtent() |
| 9180 | */ |
| 9181 | public int computeVerticalScrollExtent(State state) { |
| 9182 | return 0; |
| 9183 | } |
| 9184 | |
| 9185 | /** |
| 9186 | * <p>Override this method if you want to support scroll bars.</p> |
| 9187 | * |
| 9188 | * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p> |
| 9189 | * |
| 9190 | * <p>Default implementation returns 0.</p> |
| 9191 | * |
| 9192 | * @param state Current State of RecyclerView where you can find total item count |
| 9193 | * @return The vertical offset of the scrollbar's thumb |
| 9194 | * @see RecyclerView#computeVerticalScrollOffset() |
| 9195 | */ |
| 9196 | public int computeVerticalScrollOffset(State state) { |
| 9197 | return 0; |
| 9198 | } |
| 9199 | |
| 9200 | /** |
| 9201 | * <p>Override this method if you want to support scroll bars.</p> |
| 9202 | * |
| 9203 | * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p> |
| 9204 | * |
| 9205 | * <p>Default implementation returns 0.</p> |
| 9206 | * |
| 9207 | * @param state Current State of RecyclerView where you can find total item count |
| 9208 | * @return The total vertical range represented by the vertical scrollbar |
| 9209 | * @see RecyclerView#computeVerticalScrollRange() |
| 9210 | */ |
| 9211 | public int computeVerticalScrollRange(State state) { |
| 9212 | return 0; |
| 9213 | } |
| 9214 | |
| 9215 | /** |
| 9216 | * Measure the attached RecyclerView. Implementations must call |
| 9217 | * {@link #setMeasuredDimension(int, int)} before returning. |
| 9218 | * |
| 9219 | * <p>The default implementation will handle EXACTLY measurements and respect |
| 9220 | * the minimum width and height properties of the host RecyclerView if measured |
| 9221 | * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView |
| 9222 | * will consume all available space.</p> |
| 9223 | * |
| 9224 | * @param recycler Recycler |
| 9225 | * @param state Transient state of RecyclerView |
| 9226 | * @param widthSpec Width {@link android.view.View.MeasureSpec} |
| 9227 | * @param heightSpec Height {@link android.view.View.MeasureSpec} |
| 9228 | */ |
| 9229 | public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { |
| 9230 | mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); |
| 9231 | } |
| 9232 | |
| 9233 | /** |
| 9234 | * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the |
| 9235 | * host RecyclerView. |
| 9236 | * |
| 9237 | * @param widthSize Measured width |
| 9238 | * @param heightSize Measured height |
| 9239 | */ |
| 9240 | public void setMeasuredDimension(int widthSize, int heightSize) { |
| 9241 | mRecyclerView.setMeasuredDimension(widthSize, heightSize); |
| 9242 | } |
| 9243 | |
| 9244 | /** |
| 9245 | * @return The host RecyclerView's {@link View#getMinimumWidth()} |
| 9246 | */ |
| 9247 | public int getMinimumWidth() { |
| 9248 | return mRecyclerView.getMinimumWidth(); |
| 9249 | } |
| 9250 | |
| 9251 | /** |
| 9252 | * @return The host RecyclerView's {@link View#getMinimumHeight()} |
| 9253 | */ |
| 9254 | public int getMinimumHeight() { |
| 9255 | return mRecyclerView.getMinimumHeight(); |
| 9256 | } |
| 9257 | /** |
| 9258 | * <p>Called when the LayoutManager should save its state. This is a good time to save your |
| 9259 | * scroll position, configuration and anything else that may be required to restore the same |
| 9260 | * layout state if the LayoutManager is recreated.</p> |
| 9261 | * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and |
| 9262 | * restore. This will let you share information between your LayoutManagers but it is also |
| 9263 | * your responsibility to make sure they use the same parcelable class.</p> |
| 9264 | * |
| 9265 | * @return Necessary information for LayoutManager to be able to restore its state |
| 9266 | */ |
| 9267 | public Parcelable onSaveInstanceState() { |
| 9268 | return null; |
| 9269 | } |
| 9270 | |
| 9271 | |
| 9272 | public void onRestoreInstanceState(Parcelable state) { |
| 9273 | |
| 9274 | } |
| 9275 | |
| 9276 | void stopSmoothScroller() { |
| 9277 | if (mSmoothScroller != null) { |
| 9278 | mSmoothScroller.stop(); |
| 9279 | } |
| 9280 | } |
| 9281 | |
| 9282 | private void onSmoothScrollerStopped(SmoothScroller smoothScroller) { |
| 9283 | if (mSmoothScroller == smoothScroller) { |
| 9284 | mSmoothScroller = null; |
| 9285 | } |
| 9286 | } |
| 9287 | |
| 9288 | /** |
| 9289 | * RecyclerView calls this method to notify LayoutManager that scroll state has changed. |
| 9290 | * |
| 9291 | * @param state The new scroll state for RecyclerView |
| 9292 | */ |
| 9293 | public void onScrollStateChanged(int state) { |
| 9294 | } |
| 9295 | |
| 9296 | /** |
| 9297 | * Removes all views and recycles them using the given recycler. |
| 9298 | * <p> |
| 9299 | * If you want to clean cached views as well, you should call {@link Recycler#clear()} too. |
| 9300 | * <p> |
| 9301 | * If a View is marked as "ignored", it is not removed nor recycled. |
| 9302 | * |
| 9303 | * @param recycler Recycler to use to recycle children |
| 9304 | * @see #removeAndRecycleView(View, Recycler) |
| 9305 | * @see #removeAndRecycleViewAt(int, Recycler) |
| 9306 | * @see #ignoreView(View) |
| 9307 | */ |
| 9308 | public void removeAndRecycleAllViews(Recycler recycler) { |
| 9309 | for (int i = getChildCount() - 1; i >= 0; i--) { |
| 9310 | final View view = getChildAt(i); |
| 9311 | if (!getChildViewHolderInt(view).shouldIgnore()) { |
| 9312 | removeAndRecycleViewAt(i, recycler); |
| 9313 | } |
| 9314 | } |
| 9315 | } |
| 9316 | |
| 9317 | // called by accessibility delegate |
| 9318 | void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { |
| 9319 | onInitializeAccessibilityNodeInfo(mRecyclerView.mRecycler, mRecyclerView.mState, info); |
| 9320 | } |
| 9321 | |
| 9322 | /** |
| 9323 | * Called by the AccessibilityDelegate when the information about the current layout should |
| 9324 | * be populated. |
| 9325 | * <p> |
| 9326 | * Default implementation adds a {@link |
| 9327 | * android.view.accessibility.AccessibilityNodeInfo.CollectionInfo}. |
| 9328 | * <p> |
| 9329 | * You should override |
| 9330 | * {@link #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, |
| 9331 | * {@link #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State)}, |
| 9332 | * {@link #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State)} and |
| 9333 | * {@link #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State)} for |
| 9334 | * more accurate accessibility information. |
| 9335 | * |
| 9336 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9337 | * positions |
| 9338 | * @param state The current state of RecyclerView |
| 9339 | * @param info The info that should be filled by the LayoutManager |
| 9340 | * @see View#onInitializeAccessibilityNodeInfo( |
| 9341 | *android.view.accessibility.AccessibilityNodeInfo) |
| 9342 | * @see #getRowCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) |
| 9343 | * @see #getColumnCountForAccessibility(RecyclerView.Recycler, RecyclerView.State) |
| 9344 | * @see #isLayoutHierarchical(RecyclerView.Recycler, RecyclerView.State) |
| 9345 | * @see #getSelectionModeForAccessibility(RecyclerView.Recycler, RecyclerView.State) |
| 9346 | */ |
| 9347 | public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state, |
| 9348 | AccessibilityNodeInfo info) { |
| 9349 | if (mRecyclerView.canScrollVertically(-1) |
| 9350 | || mRecyclerView.canScrollHorizontally(-1)) { |
| 9351 | info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); |
| 9352 | info.setScrollable(true); |
| 9353 | } |
| 9354 | if (mRecyclerView.canScrollVertically(1) |
| 9355 | || mRecyclerView.canScrollHorizontally(1)) { |
| 9356 | info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); |
| 9357 | info.setScrollable(true); |
| 9358 | } |
| 9359 | final AccessibilityNodeInfo.CollectionInfo collectionInfo = |
| 9360 | AccessibilityNodeInfo.CollectionInfo |
| 9361 | .obtain(getRowCountForAccessibility(recycler, state), |
| 9362 | getColumnCountForAccessibility(recycler, state), |
| 9363 | isLayoutHierarchical(recycler, state), |
| 9364 | getSelectionModeForAccessibility(recycler, state)); |
| 9365 | info.setCollectionInfo(collectionInfo); |
| 9366 | } |
| 9367 | |
| 9368 | // called by accessibility delegate |
| 9369 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) { |
| 9370 | onInitializeAccessibilityEvent(mRecyclerView.mRecycler, mRecyclerView.mState, event); |
| 9371 | } |
| 9372 | |
| 9373 | /** |
| 9374 | * Called by the accessibility delegate to initialize an accessibility event. |
| 9375 | * <p> |
| 9376 | * Default implementation adds item count and scroll information to the event. |
| 9377 | * |
| 9378 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9379 | * positions |
| 9380 | * @param state The current state of RecyclerView |
| 9381 | * @param event The event instance to initialize |
| 9382 | * @see View#onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent) |
| 9383 | */ |
| 9384 | public void onInitializeAccessibilityEvent(Recycler recycler, State state, |
| 9385 | AccessibilityEvent event) { |
| 9386 | if (mRecyclerView == null || event == null) { |
| 9387 | return; |
| 9388 | } |
| 9389 | event.setScrollable(mRecyclerView.canScrollVertically(1) |
| 9390 | || mRecyclerView.canScrollVertically(-1) |
| 9391 | || mRecyclerView.canScrollHorizontally(-1) |
| 9392 | || mRecyclerView.canScrollHorizontally(1)); |
| 9393 | |
| 9394 | if (mRecyclerView.mAdapter != null) { |
| 9395 | event.setItemCount(mRecyclerView.mAdapter.getItemCount()); |
| 9396 | } |
| 9397 | } |
| 9398 | |
| 9399 | // called by accessibility delegate |
| 9400 | void onInitializeAccessibilityNodeInfoForItem(View host, AccessibilityNodeInfo info) { |
| 9401 | final ViewHolder vh = getChildViewHolderInt(host); |
| 9402 | // avoid trying to create accessibility node info for removed children |
| 9403 | if (vh != null && !vh.isRemoved() && !mChildHelper.isHidden(vh.itemView)) { |
| 9404 | onInitializeAccessibilityNodeInfoForItem(mRecyclerView.mRecycler, |
| 9405 | mRecyclerView.mState, host, info); |
| 9406 | } |
| 9407 | } |
| 9408 | |
| 9409 | /** |
| 9410 | * Called by the AccessibilityDelegate when the accessibility information for a specific |
| 9411 | * item should be populated. |
| 9412 | * <p> |
| 9413 | * Default implementation adds basic positioning information about the item. |
| 9414 | * |
| 9415 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9416 | * positions |
| 9417 | * @param state The current state of RecyclerView |
| 9418 | * @param host The child for which accessibility node info should be populated |
| 9419 | * @param info The info to fill out about the item |
| 9420 | * @see android.widget.AbsListView#onInitializeAccessibilityNodeInfoForItem(View, int, |
| 9421 | * android.view.accessibility.AccessibilityNodeInfo) |
| 9422 | */ |
| 9423 | public void onInitializeAccessibilityNodeInfoForItem(Recycler recycler, State state, |
| 9424 | View host, AccessibilityNodeInfo info) { |
| 9425 | int rowIndexGuess = canScrollVertically() ? getPosition(host) : 0; |
| 9426 | int columnIndexGuess = canScrollHorizontally() ? getPosition(host) : 0; |
| 9427 | final AccessibilityNodeInfo.CollectionItemInfo itemInfo = |
| 9428 | AccessibilityNodeInfo.CollectionItemInfo.obtain(rowIndexGuess, 1, |
| 9429 | columnIndexGuess, 1, false, false); |
| 9430 | info.setCollectionItemInfo(itemInfo); |
| 9431 | } |
| 9432 | |
| 9433 | /** |
| 9434 | * A LayoutManager can call this method to force RecyclerView to run simple animations in |
| 9435 | * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data |
| 9436 | * change). |
| 9437 | * <p> |
| 9438 | * Note that, calling this method will not guarantee that RecyclerView will run animations |
| 9439 | * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will |
| 9440 | * not run any animations but will still clear this flag after the layout is complete. |
| 9441 | * |
| 9442 | */ |
| 9443 | public void requestSimpleAnimationsInNextLayout() { |
| 9444 | mRequestedSimpleAnimations = true; |
| 9445 | } |
| 9446 | |
| 9447 | /** |
| 9448 | * Returns the selection mode for accessibility. Should be |
| 9449 | * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}, |
| 9450 | * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_SINGLE} or |
| 9451 | * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_MULTIPLE}. |
| 9452 | * <p> |
| 9453 | * Default implementation returns |
| 9454 | * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}. |
| 9455 | * |
| 9456 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9457 | * positions |
| 9458 | * @param state The current state of RecyclerView |
| 9459 | * @return Selection mode for accessibility. Default implementation returns |
| 9460 | * {@link AccessibilityNodeInfo.CollectionInfo#SELECTION_MODE_NONE}. |
| 9461 | */ |
| 9462 | public int getSelectionModeForAccessibility(Recycler recycler, State state) { |
| 9463 | return AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE; |
| 9464 | } |
| 9465 | |
| 9466 | /** |
| 9467 | * Returns the number of rows for accessibility. |
| 9468 | * <p> |
| 9469 | * Default implementation returns the number of items in the adapter if LayoutManager |
| 9470 | * supports vertical scrolling or 1 if LayoutManager does not support vertical |
| 9471 | * scrolling. |
| 9472 | * |
| 9473 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9474 | * positions |
| 9475 | * @param state The current state of RecyclerView |
| 9476 | * @return The number of rows in LayoutManager for accessibility. |
| 9477 | */ |
| 9478 | public int getRowCountForAccessibility(Recycler recycler, State state) { |
| 9479 | if (mRecyclerView == null || mRecyclerView.mAdapter == null) { |
| 9480 | return 1; |
| 9481 | } |
| 9482 | return canScrollVertically() ? mRecyclerView.mAdapter.getItemCount() : 1; |
| 9483 | } |
| 9484 | |
| 9485 | /** |
| 9486 | * Returns the number of columns for accessibility. |
| 9487 | * <p> |
| 9488 | * Default implementation returns the number of items in the adapter if LayoutManager |
| 9489 | * supports horizontal scrolling or 1 if LayoutManager does not support horizontal |
| 9490 | * scrolling. |
| 9491 | * |
| 9492 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9493 | * positions |
| 9494 | * @param state The current state of RecyclerView |
| 9495 | * @return The number of rows in LayoutManager for accessibility. |
| 9496 | */ |
| 9497 | public int getColumnCountForAccessibility(Recycler recycler, State state) { |
| 9498 | if (mRecyclerView == null || mRecyclerView.mAdapter == null) { |
| 9499 | return 1; |
| 9500 | } |
| 9501 | return canScrollHorizontally() ? mRecyclerView.mAdapter.getItemCount() : 1; |
| 9502 | } |
| 9503 | |
| 9504 | /** |
| 9505 | * Returns whether layout is hierarchical or not to be used for accessibility. |
| 9506 | * <p> |
| 9507 | * Default implementation returns false. |
| 9508 | * |
| 9509 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9510 | * positions |
| 9511 | * @param state The current state of RecyclerView |
| 9512 | * @return True if layout is hierarchical. |
| 9513 | */ |
| 9514 | public boolean isLayoutHierarchical(Recycler recycler, State state) { |
| 9515 | return false; |
| 9516 | } |
| 9517 | |
| 9518 | // called by accessibility delegate |
| 9519 | boolean performAccessibilityAction(int action, Bundle args) { |
| 9520 | return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState, |
| 9521 | action, args); |
| 9522 | } |
| 9523 | |
| 9524 | /** |
| 9525 | * Called by AccessibilityDelegate when an action is requested from the RecyclerView. |
| 9526 | * |
| 9527 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9528 | * positions |
| 9529 | * @param state The current state of RecyclerView |
| 9530 | * @param action The action to perform |
| 9531 | * @param args Optional action arguments |
| 9532 | * @see View#performAccessibilityAction(int, android.os.Bundle) |
| 9533 | */ |
| 9534 | public boolean performAccessibilityAction(Recycler recycler, State state, int action, |
| 9535 | Bundle args) { |
| 9536 | if (mRecyclerView == null) { |
| 9537 | return false; |
| 9538 | } |
| 9539 | int vScroll = 0, hScroll = 0; |
| 9540 | switch (action) { |
| 9541 | case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: |
| 9542 | if (mRecyclerView.canScrollVertically(-1)) { |
| 9543 | vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom()); |
| 9544 | } |
| 9545 | if (mRecyclerView.canScrollHorizontally(-1)) { |
| 9546 | hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight()); |
| 9547 | } |
| 9548 | break; |
| 9549 | case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: |
| 9550 | if (mRecyclerView.canScrollVertically(1)) { |
| 9551 | vScroll = getHeight() - getPaddingTop() - getPaddingBottom(); |
| 9552 | } |
| 9553 | if (mRecyclerView.canScrollHorizontally(1)) { |
| 9554 | hScroll = getWidth() - getPaddingLeft() - getPaddingRight(); |
| 9555 | } |
| 9556 | break; |
| 9557 | } |
| 9558 | if (vScroll == 0 && hScroll == 0) { |
| 9559 | return false; |
| 9560 | } |
Eugene Susla | 38343bc | 2017-12-20 10:43:41 -0800 | [diff] [blame] | 9561 | mRecyclerView.smoothScrollBy(hScroll, vScroll); |
Aurimas Liutikas | 7149a63 | 2017-01-18 17:36:10 -0800 | [diff] [blame] | 9562 | return true; |
| 9563 | } |
| 9564 | |
| 9565 | // called by accessibility delegate |
| 9566 | boolean performAccessibilityActionForItem(View view, int action, Bundle args) { |
| 9567 | return performAccessibilityActionForItem(mRecyclerView.mRecycler, mRecyclerView.mState, |
| 9568 | view, action, args); |
| 9569 | } |
| 9570 | |
| 9571 | /** |
| 9572 | * Called by AccessibilityDelegate when an accessibility action is requested on one of the |
| 9573 | * children of LayoutManager. |
| 9574 | * <p> |
| 9575 | * Default implementation does not do anything. |
| 9576 | * |
| 9577 | * @param recycler The Recycler that can be used to convert view positions into adapter |
| 9578 | * positions |
| 9579 | * @param state The current state of RecyclerView |
| 9580 | * @param view The child view on which the action is performed |
| 9581 | * @param action The action to perform |
| 9582 | * @param args Optional action arguments |
| 9583 | * @return true if action is handled |
| 9584 | * @see View#performAccessibilityAction(int, android.os.Bundle) |
| 9585 | */ |
| 9586 | public boolean performAccessibilityActionForItem(Recycler recycler, State state, View view, |
| 9587 | int action, Bundle args) { |
| 9588 | return false; |
| 9589 | } |
| 9590 | |
| 9591 | /** |
| 9592 | * Parse the xml attributes to get the most common properties used by layout managers. |
| 9593 | * |
Aurimas Liutikas | 7149a63 | 2017-01-18 17:36:10 -0800 | [diff] [blame] | 9594 | * @return an object containing the properties as specified in the attrs. |
| 9595 | */ |
| 9596 | public static Properties getProperties(Context context, AttributeSet attrs, |
| 9597 | int defStyleAttr, int defStyleRes) { |
| 9598 | Properties properties = new Properties(); |
| 9599 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, |
| 9600 | defStyleAttr, defStyleRes); |
| 9601 | properties.orientation = a.getInt(R.styleable.RecyclerView_orientation, VERTICAL); |
| 9602 | properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1); |
| 9603 | properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false); |
| 9604 | properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false); |
| 9605 | a.recycle(); |
| 9606 | return properties; |
| 9607 | } |
| 9608 | |
| 9609 | void setExactMeasureSpecsFrom(RecyclerView recyclerView) { |
| 9610 | setMeasureSpecs( |
| 9611 | MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY), |
| 9612 | MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY) |
| 9613 | ); |
| 9614 | } |
| 9615 | |
| 9616 | /** |
| 9617 | * Internal API to allow LayoutManagers to be measured twice. |
| 9618 | * <p> |
| 9619 | * This is not public because LayoutManagers should be able to handle their layouts in one |
| 9620 | * pass but it is very convenient to make existing LayoutManagers support wrapping content |
| 9621 | * when both orientations are undefined. |
| 9622 | * <p> |
| 9623 | * This API will be removed after default LayoutManagers properly implement wrap content in |
| 9624 | * non-scroll orientation. |
| 9625 | */ |
| 9626 | boolean shouldMeasureTwice() { |
| 9627 | return false; |
| 9628 | } |
| 9629 | |
| 9630 | boolean hasFlexibleChildInBothOrientations() { |
| 9631 | final int childCount = getChildCount(); |
| 9632 | for (int i = 0; i < childCount; i++) { |
| 9633 | final View child = getChildAt(i); |
| 9634 | final ViewGroup.LayoutParams lp = child.getLayoutParams(); |
| 9635 | if (lp.width < 0 && lp.height < 0) { |
| 9636 | return true; |
| 9637 | } |
| 9638 | } |
| 9639 | return false; |
| 9640 | } |
| 9641 | |
| 9642 | /** |
| 9643 | * Some general properties that a LayoutManager may want to use. |
| 9644 | */ |
| 9645 | public static class Properties { |
| 9646 | /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */ |
| 9647 | public int orientation; |
| 9648 | /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */ |
| 9649 | public int spanCount; |
| 9650 | /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */ |
| 9651 | public boolean reverseLayout; |
| 9652 | /** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */ |
| 9653 | public boolean stackFromEnd; |
| 9654 | } |
| 9655 | } |
| 9656 | |
| 9657 | /** |
| 9658 | * An ItemDecoration allows the application to add a special drawing and layout offset |
| 9659 | * to specific item views from the adapter's data set. This can be useful for drawing dividers |
| 9660 | * between items, highlights, visual grouping boundaries and more. |
| 9661 | * |
| 9662 | * <p>All ItemDecorations are drawn in the order they were added, before the item |
| 9663 | * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()} |
| 9664 | * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView, |
| 9665 | * RecyclerView.State)}.</p> |
| 9666 | */ |
| 9667 | public abstract static class ItemDecoration { |
| 9668 | /** |
| 9669 | * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. |
| 9670 | * Any content drawn by this method will be drawn before the item views are drawn, |
| 9671 | * and will thus appear underneath the views. |
| 9672 | * |
| 9673 | * @param c Canvas to draw into |
| 9674 | * @param parent RecyclerView this ItemDecoration is drawing into |
| 9675 | * @param state The current state of RecyclerView |
| 9676 | */ |
| 9677 | public void onDraw(Canvas c, RecyclerView parent, State state) { |
| 9678 | onDraw(c, parent); |
| 9679 | } |
| 9680 | |
| 9681 | /** |
| 9682 | * @deprecated |
| 9683 | * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)} |
| 9684 | */ |
| 9685 | @Deprecated |
| 9686 | public void onDraw(Canvas c, RecyclerView parent) { |
| 9687 | } |
| 9688 | |
| 9689 | /** |
| 9690 | * Draw any appropriate decorations into the Canvas supplied to the RecyclerView. |
| 9691 | * Any content drawn by this method will be drawn after the item views are drawn |
| 9692 | * and will thus appear over the views. |
| 9693 | * |
| 9694 | * @param c Canvas to draw into |
| 9695 | * @param parent RecyclerView this ItemDecoration is drawing into |
| 9696 | * @param state The current state of RecyclerView. |
| 9697 | */ |
| 9698 | public void onDrawOver(Canvas c, RecyclerView parent, State state) { |
| 9699 | onDrawOver(c, parent); |
| 9700 | } |
| 9701 | |
| 9702 | /** |
| 9703 | * @deprecated |
| 9704 | * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)} |
| 9705 | */ |
| 9706 | @Deprecated |
| 9707 | public void onDrawOver(Canvas c, RecyclerView parent) { |
| 9708 | } |
| 9709 | |
| 9710 | |
| 9711 | /** |
| 9712 | * @deprecated |
| 9713 | * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)} |
| 9714 | */ |
| 9715 | @Deprecated |
| 9716 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { |
| 9717 | outRect.set(0, 0, 0, 0); |
| 9718 | } |
| 9719 | |
| 9720 | /** |
| 9721 | * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies |
| 9722 | * the number of pixels that the item view should be inset by, similar to padding or margin. |
| 9723 | * The default implementation sets the bounds of outRect to 0 and returns. |
| 9724 | * |
| 9725 | * <p> |
| 9726 | * If this ItemDecoration does not affect the positioning of item views, it should set |
| 9727 | * all four fields of <code>outRect</code> (left, top, right, bottom) to zero |
| 9728 | * before returning. |
| 9729 | * |
| 9730 | * <p> |
| 9731 | * If you need to access Adapter for additional data, you can call |
| 9732 | * {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the |
| 9733 | * View. |
| 9734 | * |
| 9735 | * @param outRect Rect to receive the output. |
| 9736 | * @param view The child view to decorate |
| 9737 | * @param parent RecyclerView this ItemDecoration is decorating |
| 9738 | * @param state The current state of RecyclerView. |
| 9739 | */ |
| 9740 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { |
| 9741 | getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), |
| 9742 | parent); |
| 9743 | } |
| 9744 | } |
| 9745 | |
| 9746 | /** |
| 9747 | * An OnItemTouchListener allows the application to intercept touch events in progress at the |
| 9748 | * view hierarchy level of the RecyclerView before those touch events are considered for |
| 9749 | * RecyclerView's own scrolling behavior. |
| 9750 | * |
| 9751 | * <p>This can be useful for applications that wish to implement various forms of gestural |
| 9752 | * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept |
| 9753 | * a touch interaction already in progress even if the RecyclerView is already handling that |
| 9754 | * gesture stream itself for the purposes of scrolling.</p> |
| 9755 | * |
| 9756 | * @see SimpleOnItemTouchListener |
| 9757 | */ |
| 9758 | public interface OnItemTouchListener { |
| 9759 | /** |
| 9760 | * Silently observe and/or take over touch events sent to the RecyclerView |
| 9761 | * before they are handled by either the RecyclerView itself or its child views. |
| 9762 | * |
| 9763 | * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run |
| 9764 | * in the order in which each listener was added, before any other touch processing |
| 9765 | * by the RecyclerView itself or child views occurs.</p> |
| 9766 | * |
| 9767 | * @param e MotionEvent describing the touch event. All coordinates are in |
| 9768 | * the RecyclerView's coordinate system. |
| 9769 | * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false |
| 9770 | * to continue with the current behavior and continue observing future events in |
| 9771 | * the gesture. |
| 9772 | */ |
| 9773 | boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e); |
| 9774 | |
| 9775 | /** |
| 9776 | * Process a touch event as part of a gesture that was claimed by returning true from |
| 9777 | * a previous call to {@link #onInterceptTouchEvent}. |
| 9778 | * |
| 9779 | * @param e MotionEvent describing the touch event. All coordinates are in |
| 9780 | * the RecyclerView's coordinate system. |
| 9781 | */ |
| 9782 | void onTouchEvent(RecyclerView rv, MotionEvent e); |
| 9783 | |
| 9784 | /** |
| 9785 | * Called when a child of RecyclerView does not want RecyclerView and its ancestors to |
| 9786 | * intercept touch events with |
| 9787 | * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}. |
| 9788 | * |
| 9789 | * @param disallowIntercept True if the child does not want the parent to |
| 9790 | * intercept touch events. |
| 9791 | * @see ViewParent#requestDisallowInterceptTouchEvent(boolean) |
| 9792 | */ |
| 9793 | void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept); |
| 9794 | } |
| 9795 | |
| 9796 | /** |
| 9797 | * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies |
| 9798 | * and default return values. |
| 9799 | * <p> |
| 9800 | * You may prefer to extend this class if you don't need to override all methods. Another |
| 9801 | * benefit of using this class is future compatibility. As the interface may change, we'll |
| 9802 | * always provide a default implementation on this class so that your code won't break when |
| 9803 | * you update to a new version of the support library. |
| 9804 | */ |
| 9805 | public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener { |
| 9806 | @Override |
| 9807 | public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { |
| 9808 | return false; |
| 9809 | } |
| 9810 | |
| 9811 | @Override |
| 9812 | public void onTouchEvent(RecyclerView rv, MotionEvent e) { |
| 9813 | } |
| 9814 | |
| 9815 | @Override |
| 9816 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { |
| 9817 | } |
| 9818 | } |
| 9819 | |
| 9820 | |
| 9821 | /** |
| 9822 | * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event |
| 9823 | * has occurred on that RecyclerView. |
| 9824 | * <p> |
| 9825 | * @see RecyclerView#addOnScrollListener(OnScrollListener) |
| 9826 | * @see RecyclerView#clearOnChildAttachStateChangeListeners() |
| 9827 | * |
| 9828 | */ |
| 9829 | public abstract static class OnScrollListener { |
| 9830 | /** |
| 9831 | * Callback method to be invoked when RecyclerView's scroll state changes. |
| 9832 | * |
| 9833 | * @param recyclerView The RecyclerView whose scroll state has changed. |
| 9834 | * @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE}, |
| 9835 | * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. |
| 9836 | */ |
| 9837 | public void onScrollStateChanged(RecyclerView recyclerView, int newState){} |
| 9838 | |
| 9839 | /** |
| 9840 | * Callback method to be invoked when the RecyclerView has been scrolled. This will be |
| 9841 | * called after the scroll has completed. |
| 9842 | * <p> |
| 9843 | * This callback will also be called if visible item range changes after a layout |
| 9844 | * calculation. In that case, dx and dy will be 0. |
| 9845 | * |
| 9846 | * @param recyclerView The RecyclerView which scrolled. |
| 9847 | * @param dx The amount of horizontal scroll. |
| 9848 | * @param dy The amount of vertical scroll. |
| 9849 | */ |
| 9850 | public void onScrolled(RecyclerView recyclerView, int dx, int dy){} |
| 9851 | } |
| 9852 | |
| 9853 | /** |
| 9854 | * A RecyclerListener can be set on a RecyclerView to receive messages whenever |
| 9855 | * a view is recycled. |
| 9856 | * |
| 9857 | * @see RecyclerView#setRecyclerListener(RecyclerListener) |
| 9858 | */ |
| 9859 | public interface RecyclerListener { |
| 9860 | |
| 9861 | /** |
| 9862 | * This method is called whenever the view in the ViewHolder is recycled. |
| 9863 | * |
| 9864 | * RecyclerView calls this method right before clearing ViewHolder's internal data and |
| 9865 | * sending it to RecycledViewPool. This way, if ViewHolder was holding valid information |
| 9866 | * before being recycled, you can call {@link ViewHolder#getAdapterPosition()} to get |
| 9867 | * its adapter position. |
| 9868 | * |
| 9869 | * @param holder The ViewHolder containing the view that was recycled |
| 9870 | */ |
| 9871 | void onViewRecycled(ViewHolder holder); |
| 9872 | } |
| 9873 | |
| 9874 | /** |
| 9875 | * A Listener interface that can be attached to a RecylcerView to get notified |
| 9876 | * whenever a ViewHolder is attached to or detached from RecyclerView. |
| 9877 | */ |
| 9878 | public interface OnChildAttachStateChangeListener { |
| 9879 | |
| 9880 | /** |
| 9881 | * Called when a view is attached to the RecyclerView. |
| 9882 | * |
| 9883 | * @param view The View which is attached to the RecyclerView |
| 9884 | */ |
| 9885 | void onChildViewAttachedToWindow(View view); |
| 9886 | |
| 9887 | /** |
| 9888 | * Called when a view is detached from RecyclerView. |
| 9889 | * |
| 9890 | * @param view The View which is being detached from the RecyclerView |
| 9891 | */ |
| 9892 | void onChildViewDetachedFromWindow(View view); |
| 9893 | } |
| 9894 | |
| 9895 | /** |
| 9896 | * A ViewHolder describes an item view and metadata about its place within the RecyclerView. |
| 9897 | * |
| 9898 | * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching |
| 9899 | * potentially expensive {@link View#findViewById(int)} results.</p> |
| 9900 | * |
| 9901 | * <p>While {@link LayoutParams} belong to the {@link LayoutManager}, |
| 9902 | * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use |
| 9903 | * their own custom ViewHolder implementations to store data that makes binding view contents |
| 9904 | * easier. Implementations should assume that individual item views will hold strong references |
| 9905 | * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold |
| 9906 | * strong references to extra off-screen item views for caching purposes</p> |
| 9907 | */ |
| 9908 | public abstract static class ViewHolder { |
| 9909 | public final View itemView; |
| 9910 | WeakReference<RecyclerView> mNestedRecyclerView; |
| 9911 | int mPosition = NO_POSITION; |
| 9912 | int mOldPosition = NO_POSITION; |
| 9913 | long mItemId = NO_ID; |
| 9914 | int mItemViewType = INVALID_TYPE; |
| 9915 | int mPreLayoutPosition = NO_POSITION; |
| 9916 | |
| 9917 | // The item that this holder is shadowing during an item change event/animation |
| 9918 | ViewHolder mShadowedHolder = null; |
| 9919 | // The item that is shadowing this holder during an item change event/animation |
| 9920 | ViewHolder mShadowingHolder = null; |
| 9921 | |
| 9922 | /** |
| 9923 | * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType |
| 9924 | * are all valid. |
| 9925 | */ |
| 9926 | static final int FLAG_BOUND = 1 << 0; |
| 9927 | |
| 9928 | /** |
| 9929 | * The data this ViewHolder's view reflects is stale and needs to be rebound |
| 9930 | * by the adapter. mPosition and mItemId are consistent. |
| 9931 | */ |
| 9932 | static final int FLAG_UPDATE = 1 << 1; |
| 9933 | |
| 9934 | /** |
| 9935 | * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId |
| 9936 | * are not to be trusted and may no longer match the item view type. |
| 9937 | * This ViewHolder must be fully rebound to different data. |
| 9938 | */ |
| 9939 | static final int FLAG_INVALID = 1 << 2; |
| 9940 | |
| 9941 | /** |
| 9942 | * This ViewHolder points at data that represents an item previously removed from the |
| 9943 | * data set. Its view may still be used for things like outgoing animations. |
| 9944 | */ |
| 9945 | static final int FLAG_REMOVED = 1 << 3; |
| 9946 | |
| 9947 | /** |
| 9948 | * This ViewHolder should not be recycled. This flag is set via setIsRecyclable() |
| 9949 | * and is intended to keep views around during animations. |
| 9950 | */ |
| 9951 | static final int FLAG_NOT_RECYCLABLE = 1 << 4; |
| 9952 | |
| 9953 | /** |
| 9954 | * This ViewHolder is returned from scrap which means we are expecting an addView call |
| 9955 | * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until |
| 9956 | * the end of the layout pass and then recycled by RecyclerView if it is not added back to |
| 9957 | * the RecyclerView. |
| 9958 | */ |
| 9959 | static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5; |
| 9960 | |
| 9961 | /** |
| 9962 | * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove |
| 9963 | * it unless LayoutManager is replaced. |
| 9964 | * It is still fully visible to the LayoutManager. |
| 9965 | */ |
| 9966 | static final int FLAG_IGNORE = 1 << 7; |
| 9967 | |
| 9968 | /** |
| 9969 | * When the View is detached form the parent, we set this flag so that we can take correct |
| 9970 | * action when we need to remove it or add it back. |
| 9971 | */ |
| 9972 | static final int FLAG_TMP_DETACHED = 1 << 8; |
| 9973 | |
| 9974 | /** |
| 9975 | * Set when we can no longer determine the adapter position of this ViewHolder until it is |
| 9976 | * rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is |
| 9977 | * set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon |
| 9978 | * as adapter notification arrives vs FLAG_INVALID is set lazily before layout is |
| 9979 | * re-calculated. |
| 9980 | */ |
| 9981 | static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9; |
| 9982 | |
| 9983 | /** |
| 9984 | * Set when a addChangePayload(null) is called |
| 9985 | */ |
| 9986 | static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10; |
| 9987 | |
| 9988 | /** |
| 9989 | * Used by ItemAnimator when a ViewHolder's position changes |
| 9990 | */ |
| 9991 | static final int FLAG_MOVED = 1 << 11; |
| 9992 | |
| 9993 | /** |
| 9994 | * Used by ItemAnimator when a ViewHolder appears in pre-layout |
| 9995 | */ |
| 9996 | static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12; |
| 9997 | |
| 9998 | static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1; |
| 9999 | |
| 10000 | /** |
| 10001 | * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from |
| 10002 | * hidden list (as if it was scrap) without being recycled in between. |
| 10003 | * |
| 10004 | * When a ViewHolder is hidden, there are 2 paths it can be re-used: |
| 10005 | * a) Animation ends, view is recycled and used from the recycle pool. |
| 10006 | * b) LayoutManager asks for the View for that position while the ViewHolder is hidden. |
| 10007 | * |
| 10008 | * This flag is used to represent "case b" where the ViewHolder is reused without being |
| 10009 | * recycled (thus "bounced" from the hidden list). This state requires special handling |
| 10010 | * because the ViewHolder must be added to pre layout maps for animations as if it was |
| 10011 | * already there. |
| 10012 | */ |
| 10013 | static final int FLAG_BOUNCED_FROM_HIDDEN_LIST = 1 << 13; |
| 10014 | |
| 10015 | private int mFlags; |
| 10016 | |
| 10017 | private static final List<Object> FULLUPDATE_PAYLOADS = Collections.EMPTY_LIST; |
| 10018 | |
| 10019 | List<Object> mPayloads = null; |
| 10020 | List<Object> mUnmodifiedPayloads = null; |
| 10021 | |
| 10022 | private int mIsRecyclableCount = 0; |
| 10023 | |
| 10024 | // If non-null, view is currently considered scrap and may be reused for other data by the |
| 10025 | // scrap container. |
| 10026 | private Recycler mScrapContainer = null; |
| 10027 | // Keeps whether this ViewHolder lives in Change scrap or Attached scrap |
| 10028 | private boolean mInChangeScrap = false; |
| 10029 | |
| 10030 | // Saves isImportantForAccessibility value for the view item while it's in hidden state and |
| 10031 | // marked as unimportant for accessibility. |
| 10032 | private int mWasImportantForAccessibilityBeforeHidden = |
| 10033 | View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; |
| 10034 | // set if we defer the accessibility state change of the view holder |
| 10035 | @VisibleForTesting |
| 10036 | int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; |
| 10037 | |
| 10038 | /** |
| 10039 | * Is set when VH is bound from the adapter and cleaned right before it is sent to |
| 10040 | * {@link RecycledViewPool}. |
| 10041 | */ |
| 10042 | RecyclerView mOwnerRecyclerView; |
| 10043 | |
| 10044 | public ViewHolder(View itemView) { |
| 10045 | if (itemView == null) { |
| 10046 | throw new IllegalArgumentException("itemView may not be null"); |
| 10047 | } |
| 10048 | this.itemView = itemView; |
| 10049 | } |
| 10050 | |
| 10051 | void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) { |
| 10052 | addFlags(ViewHolder.FLAG_REMOVED); |
| 10053 | offsetPosition(offset, applyToPreLayout); |
| 10054 | mPosition = mNewPosition; |
| 10055 | } |
| 10056 | |
| 10057 | void offsetPosition(int offset, boolean applyToPreLayout) { |
| 10058 | if (mOldPosition == NO_POSITION) { |
| 10059 | mOldPosition = mPosition; |
| 10060 | } |
| 10061 | if (mPreLayoutPosition == NO_POSITION) { |
| 10062 | mPreLayoutPosition = mPosition; |
| 10063 | } |
| 10064 | if (applyToPreLayout) { |
| 10065 | mPreLayoutPosition += offset; |
| 10066 | } |
| 10067 | mPosition += offset; |
| 10068 | if (itemView.getLayoutParams() != null) { |
| 10069 | ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true; |
| 10070 | } |
| 10071 | } |
| 10072 | |
| 10073 | void clearOldPosition() { |
| 10074 | mOldPosition = NO_POSITION; |
| 10075 | mPreLayoutPosition = NO_POSITION; |
| 10076 | } |
| 10077 | |
| 10078 | void saveOldPosition() { |
| 10079 | if (mOldPosition == NO_POSITION) { |
| 10080 | mOldPosition = mPosition; |
| 10081 | } |
| 10082 | } |
| 10083 | |
| 10084 | boolean shouldIgnore() { |
| 10085 | return (mFlags & FLAG_IGNORE) != 0; |
| 10086 | } |
| 10087 | |
| 10088 | /** |
| 10089 | * @deprecated This method is deprecated because its meaning is ambiguous due to the async |
| 10090 | * handling of adapter updates. Please use {@link #getLayoutPosition()} or |
| 10091 | * {@link #getAdapterPosition()} depending on your use case. |
| 10092 | * |
| 10093 | * @see #getLayoutPosition() |
| 10094 | * @see #getAdapterPosition() |
| 10095 | */ |
| 10096 | @Deprecated |
| 10097 | public final int getPosition() { |
| 10098 | return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; |
| 10099 | } |
| 10100 | |
| 10101 | /** |
| 10102 | * Returns the position of the ViewHolder in terms of the latest layout pass. |
| 10103 | * <p> |
| 10104 | * This position is mostly used by RecyclerView components to be consistent while |
| 10105 | * RecyclerView lazily processes adapter updates. |
| 10106 | * <p> |
| 10107 | * For performance and animation reasons, RecyclerView batches all adapter updates until the |
| 10108 | * next layout pass. This may cause mismatches between the Adapter position of the item and |
| 10109 | * the position it had in the latest layout calculations. |
| 10110 | * <p> |
| 10111 | * LayoutManagers should always call this method while doing calculations based on item |
| 10112 | * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State}, |
| 10113 | * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position |
| 10114 | * of the item. |
| 10115 | * <p> |
| 10116 | * If LayoutManager needs to call an external method that requires the adapter position of |
| 10117 | * the item, it can use {@link #getAdapterPosition()} or |
| 10118 | * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}. |
| 10119 | * |
| 10120 | * @return Returns the adapter position of the ViewHolder in the latest layout pass. |
| 10121 | * @see #getAdapterPosition() |
| 10122 | */ |
| 10123 | public final int getLayoutPosition() { |
| 10124 | return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; |
| 10125 | } |
| 10126 | |
| 10127 | /** |
| 10128 | * Returns the Adapter position of the item represented by this ViewHolder. |
| 10129 | * <p> |
| 10130 | * Note that this might be different than the {@link #getLayoutPosition()} if there are |
| 10131 | * pending adapter updates but a new layout pass has not happened yet. |
| 10132 | * <p> |
| 10133 | * RecyclerView does not handle any adapter updates until the next layout traversal. This |
| 10134 | * may create temporary inconsistencies between what user sees on the screen and what |
| 10135 | * adapter contents have. This inconsistency is not important since it will be less than |
| 10136 | * 16ms but it might be a problem if you want to use ViewHolder position to access the |
| 10137 | * adapter. Sometimes, you may need to get the exact adapter position to do |
| 10138 | * some actions in response to user events. In that case, you should use this method which |
| 10139 | * will calculate the Adapter position of the ViewHolder. |
| 10140 | * <p> |
| 10141 | * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the |
| 10142 | * next layout pass, the return value of this method will be {@link #NO_POSITION}. |
| 10143 | * |
| 10144 | * @return The adapter position of the item if it still exists in the adapter. |
| 10145 | * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter, |
| 10146 | * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last |
| 10147 | * layout pass or the ViewHolder has already been recycled. |
| 10148 | */ |
| 10149 | public final int getAdapterPosition() { |
| 10150 | if (mOwnerRecyclerView == null) { |
| 10151 | return NO_POSITION; |
| 10152 | } |
| 10153 | return mOwnerRecyclerView.getAdapterPositionFor(this); |
| 10154 | } |
| 10155 | |
| 10156 | /** |
| 10157 | * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders |
| 10158 | * to perform animations. |
| 10159 | * <p> |
| 10160 | * If a ViewHolder was laid out in the previous onLayout call, old position will keep its |
| 10161 | * adapter index in the previous layout. |
| 10162 | * |
| 10163 | * @return The previous adapter index of the Item represented by this ViewHolder or |
| 10164 | * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is |
| 10165 | * complete). |
| 10166 | */ |
| 10167 | public final int getOldPosition() { |
| 10168 | return mOldPosition; |
| 10169 | } |
| 10170 | |
| 10171 | /** |
| 10172 | * Returns The itemId represented by this ViewHolder. |
| 10173 | * |
| 10174 | * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID} |
| 10175 | * otherwise |
| 10176 | */ |
| 10177 | public final long getItemId() { |
| 10178 | return mItemId; |
| 10179 | } |
| 10180 | |
| 10181 | /** |
| 10182 | * @return The view type of this ViewHolder. |
| 10183 | */ |
| 10184 | public final int getItemViewType() { |
| 10185 | return mItemViewType; |
| 10186 | } |
| 10187 | |
| 10188 | boolean isScrap() { |
| 10189 | return mScrapContainer != null; |
| 10190 | } |
| 10191 | |
| 10192 | void unScrap() { |
| 10193 | mScrapContainer.unscrapView(this); |
| 10194 | } |
| 10195 | |
| 10196 | boolean wasReturnedFromScrap() { |
| 10197 | return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0; |
| 10198 | } |
| 10199 | |
| 10200 | void clearReturnedFromScrapFlag() { |
| 10201 | mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP; |
| 10202 | } |
| 10203 | |
| 10204 | void clearTmpDetachFlag() { |
| 10205 | mFlags = mFlags & ~FLAG_TMP_DETACHED; |
| 10206 | } |
| 10207 | |
| 10208 | void stopIgnoring() { |
| 10209 | mFlags = mFlags & ~FLAG_IGNORE; |
| 10210 | } |
| 10211 | |
| 10212 | void setScrapContainer(Recycler recycler, boolean isChangeScrap) { |
| 10213 | mScrapContainer = recycler; |
| 10214 | mInChangeScrap = isChangeScrap; |
| 10215 | } |
| 10216 | |
| 10217 | boolean isInvalid() { |
| 10218 | return (mFlags & FLAG_INVALID) != 0; |
| 10219 | } |
| 10220 | |
| 10221 | boolean needsUpdate() { |
| 10222 | return (mFlags & FLAG_UPDATE) != 0; |
| 10223 | } |
| 10224 | |
| 10225 | boolean isBound() { |
| 10226 | return (mFlags & FLAG_BOUND) != 0; |
| 10227 | } |
| 10228 | |
| 10229 | boolean isRemoved() { |
| 10230 | return (mFlags & FLAG_REMOVED) != 0; |
| 10231 | } |
| 10232 | |
| 10233 | boolean hasAnyOfTheFlags(int flags) { |
| 10234 | return (mFlags & flags) != 0; |
| 10235 | } |
| 10236 | |
| 10237 | boolean isTmpDetached() { |
| 10238 | return (mFlags & FLAG_TMP_DETACHED) != 0; |
| 10239 | } |
| 10240 | |
| 10241 | boolean isAdapterPositionUnknown() { |
| 10242 | return (mFlags & FLAG_ADAPTER_POSITION_UNKNOWN) != 0 || isInvalid(); |
| 10243 | } |
| 10244 | |
| 10245 | void setFlags(int flags, int mask) { |
| 10246 | mFlags = (mFlags & ~mask) | (flags & mask); |
| 10247 | } |
| 10248 | |
| 10249 | void addFlags(int flags) { |
| 10250 | mFlags |= flags; |
| 10251 | } |
| 10252 | |
| 10253 | void addChangePayload(Object payload) { |
| 10254 | if (payload == null) { |
| 10255 | addFlags(FLAG_ADAPTER_FULLUPDATE); |
| 10256 | } else if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { |
| 10257 | createPayloadsIfNeeded(); |
| 10258 | mPayloads.add(payload); |
| 10259 | } |
| 10260 | } |
| 10261 | |
| 10262 | private void createPayloadsIfNeeded() { |
| 10263 | if (mPayloads == null) { |
| 10264 | mPayloads = new ArrayList<Object>(); |
| 10265 | mUnmodifiedPayloads = Collections.unmodifiableList(mPayloads); |
| 10266 | } |
| 10267 | } |
| 10268 | |
| 10269 | void clearPayload() { |
| 10270 | if (mPayloads != null) { |
| 10271 | mPayloads.clear(); |
| 10272 | } |
| 10273 | mFlags = mFlags & ~FLAG_ADAPTER_FULLUPDATE; |
| 10274 | } |
| 10275 | |
| 10276 | List<Object> getUnmodifiedPayloads() { |
| 10277 | if ((mFlags & FLAG_ADAPTER_FULLUPDATE) == 0) { |
| 10278 | if (mPayloads == null || mPayloads.size() == 0) { |
| 10279 | // Initial state, no update being called. |
| 10280 | return FULLUPDATE_PAYLOADS; |
| 10281 | } |
| 10282 | // there are none-null payloads |
| 10283 | return mUnmodifiedPayloads; |
| 10284 | } else { |
| 10285 | // a full update has been called. |
| 10286 | return FULLUPDATE_PAYLOADS; |
| 10287 | } |
| 10288 | } |
| 10289 | |
| 10290 | void resetInternal() { |
| 10291 | mFlags = 0; |
| 10292 | mPosition = NO_POSITION; |
| 10293 | mOldPosition = NO_POSITION; |
| 10294 | mItemId = NO_ID; |
| 10295 | mPreLayoutPosition = NO_POSITION; |
| 10296 | mIsRecyclableCount = 0; |
| 10297 | mShadowedHolder = null; |
| 10298 | mShadowingHolder = null; |
| 10299 | clearPayload(); |
| 10300 | mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; |
| 10301 | mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; |
| 10302 | clearNestedRecyclerViewIfNotNested(this); |
| 10303 | } |
| 10304 | |
| 10305 | /** |
| 10306 | * Called when the child view enters the hidden state |
| 10307 | */ |
| 10308 | private void onEnteredHiddenState(RecyclerView parent) { |
| 10309 | // While the view item is in hidden state, make it invisible for the accessibility. |
| 10310 | mWasImportantForAccessibilityBeforeHidden = |
| 10311 | itemView.getImportantForAccessibility(); |
| 10312 | parent.setChildImportantForAccessibilityInternal(this, |
| 10313 | View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); |
| 10314 | } |
| 10315 | |
| 10316 | /** |
| 10317 | * Called when the child view leaves the hidden state |
| 10318 | */ |
| 10319 | private void onLeftHiddenState(RecyclerView parent) { |
| 10320 | parent.setChildImportantForAccessibilityInternal(this, |
| 10321 | mWasImportantForAccessibilityBeforeHidden); |
| 10322 | mWasImportantForAccessibilityBeforeHidden = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; |
| 10323 | } |
| 10324 | |
| 10325 | @Override |
| 10326 | public String toString() { |
| 10327 | final StringBuilder sb = new StringBuilder("ViewHolder{" |
| 10328 | + Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId |
| 10329 | + ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition); |
| 10330 | if (isScrap()) { |
| 10331 | sb.append(" scrap ") |
| 10332 | .append(mInChangeScrap ? "[changeScrap]" : "[attachedScrap]"); |
| 10333 | } |
| 10334 | if (isInvalid()) sb.append(" invalid"); |
| 10335 | if (!isBound()) sb.append(" unbound"); |
| 10336 | if (needsUpdate()) sb.append(" update"); |
| 10337 | if (isRemoved()) sb.append(" removed"); |
| 10338 | if (shouldIgnore()) sb.append(" ignored"); |
| 10339 | if (isTmpDetached()) sb.append(" tmpDetached"); |
| 10340 | if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")"); |
| 10341 | if (isAdapterPositionUnknown()) sb.append(" undefined adapter position"); |
| 10342 | |
| 10343 | if (itemView.getParent() == null) sb.append(" no parent"); |
| 10344 | sb.append("}"); |
| 10345 | return sb.toString(); |
| 10346 | } |
| 10347 | |
| 10348 | /** |
| 10349 | * Informs the recycler whether this item can be recycled. Views which are not |
| 10350 | * recyclable will not be reused for other items until setIsRecyclable() is |
| 10351 | * later set to true. Calls to setIsRecyclable() should always be paired (one |
| 10352 | * call to setIsRecyclabe(false) should always be matched with a later call to |
| 10353 | * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally |
| 10354 | * reference-counted. |
| 10355 | * |
| 10356 | * @param recyclable Whether this item is available to be recycled. Default value |
| 10357 | * is true. |
| 10358 | * |
| 10359 | * @see #isRecyclable() |
| 10360 | */ |
| 10361 | public final void setIsRecyclable(boolean recyclable) { |
| 10362 | mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; |
| 10363 | if (mIsRecyclableCount < 0) { |
| 10364 | mIsRecyclableCount = 0; |
| 10365 | if (DEBUG) { |
| 10366 | throw new RuntimeException("isRecyclable decremented below 0: " |
| 10367 | + "unmatched pair of setIsRecyable() calls for " + this); |
| 10368 | } |
| 10369 | Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " |
| 10370 | + "unmatched pair of setIsRecyable() calls for " + this); |
| 10371 | } else if (!recyclable && mIsRecyclableCount == 1) { |
| 10372 | mFlags |= FLAG_NOT_RECYCLABLE; |
| 10373 | } else if (recyclable && mIsRecyclableCount == 0) { |
| 10374 | mFlags &= ~FLAG_NOT_RECYCLABLE; |
| 10375 | } |
| 10376 | if (DEBUG) { |
| 10377 | Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this); |
| 10378 | } |
| 10379 | } |
| 10380 | |
| 10381 | /** |
| 10382 | * @return true if this item is available to be recycled, false otherwise. |
| 10383 | * |
| 10384 | * @see #setIsRecyclable(boolean) |
| 10385 | */ |
| 10386 | public final boolean isRecyclable() { |
| 10387 | return (mFlags & FLAG_NOT_RECYCLABLE) == 0 |
| 10388 | && !itemView.hasTransientState(); |
| 10389 | } |
| 10390 | |
| 10391 | /** |
| 10392 | * Returns whether we have animations referring to this view holder or not. |
| 10393 | * This is similar to isRecyclable flag but does not check transient state. |
| 10394 | */ |
| 10395 | private boolean shouldBeKeptAsChild() { |
| 10396 | return (mFlags & FLAG_NOT_RECYCLABLE) != 0; |
| 10397 | } |
| 10398 | |
| 10399 | /** |
| 10400 | * @return True if ViewHolder is not referenced by RecyclerView animations but has |
| 10401 | * transient state which will prevent it from being recycled. |
| 10402 | */ |
| 10403 | private boolean doesTransientStatePreventRecycling() { |
| 10404 | return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && itemView.hasTransientState(); |
| 10405 | } |
| 10406 | |
| 10407 | boolean isUpdated() { |
| 10408 | return (mFlags & FLAG_UPDATE) != 0; |
| 10409 | } |
| 10410 | } |
| 10411 | |
| 10412 | /** |
| 10413 | * This method is here so that we can control the important for a11y changes and test it. |
| 10414 | */ |
| 10415 | @VisibleForTesting |
| 10416 | boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder, |
| 10417 | int importantForAccessibility) { |
| 10418 | if (isComputingLayout()) { |
| 10419 | viewHolder.mPendingAccessibilityState = importantForAccessibility; |
| 10420 | mPendingAccessibilityImportanceChange.add(viewHolder); |
| 10421 | return false; |
| 10422 | } |
| 10423 | viewHolder.itemView.setImportantForAccessibility(importantForAccessibility); |
| 10424 | return true; |
| 10425 | } |
| 10426 | |
| 10427 | void dispatchPendingImportantForAccessibilityChanges() { |
| 10428 | for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i--) { |
| 10429 | ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i); |
| 10430 | if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) { |
| 10431 | continue; |
| 10432 | } |
| 10433 | int state = viewHolder.mPendingAccessibilityState; |
| 10434 | if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) { |
| 10435 | //noinspection WrongConstant |
| 10436 | viewHolder.itemView.setImportantForAccessibility(state); |
| 10437 | viewHolder.mPendingAccessibilityState = |
| 10438 | ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET; |
| 10439 | } |
| 10440 | } |
| 10441 | mPendingAccessibilityImportanceChange.clear(); |
| 10442 | } |
| 10443 | |
| 10444 | int getAdapterPositionFor(ViewHolder viewHolder) { |
| 10445 | if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID |
| 10446 | | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN) |
| 10447 | || !viewHolder.isBound()) { |
| 10448 | return RecyclerView.NO_POSITION; |
| 10449 | } |
| 10450 | return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition); |
| 10451 | } |
| 10452 | |
| 10453 | /** |
| 10454 | * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of |
| 10455 | * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged |
| 10456 | * to create their own subclass of this <code>LayoutParams</code> class |
| 10457 | * to store any additional required per-child view metadata about the layout. |
| 10458 | */ |
| 10459 | public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams { |
| 10460 | ViewHolder mViewHolder; |
| 10461 | final Rect mDecorInsets = new Rect(); |
| 10462 | boolean mInsetsDirty = true; |
| 10463 | // Flag is set to true if the view is bound while it is detached from RV. |
| 10464 | // In this case, we need to manually call invalidate after view is added to guarantee that |
| 10465 | // invalidation is populated through the View hierarchy |
| 10466 | boolean mPendingInvalidate = false; |
| 10467 | |
| 10468 | public LayoutParams(Context c, AttributeSet attrs) { |
| 10469 | super(c, attrs); |
| 10470 | } |
| 10471 | |
| 10472 | public LayoutParams(int width, int height) { |
| 10473 | super(width, height); |
| 10474 | } |
| 10475 | |
| 10476 | public LayoutParams(MarginLayoutParams source) { |
| 10477 | super(source); |
| 10478 | } |
| 10479 | |
| 10480 | public LayoutParams(ViewGroup.LayoutParams source) { |
| 10481 | super(source); |
| 10482 | } |
| 10483 | |
| 10484 | public LayoutParams(LayoutParams source) { |
| 10485 | super((ViewGroup.LayoutParams) source); |
| 10486 | } |
| 10487 | |
| 10488 | /** |
| 10489 | * Returns true if the view this LayoutParams is attached to needs to have its content |
| 10490 | * updated from the corresponding adapter. |
| 10491 | * |
| 10492 | * @return true if the view should have its content updated |
| 10493 | */ |
| 10494 | public boolean viewNeedsUpdate() { |
| 10495 | return mViewHolder.needsUpdate(); |
| 10496 | } |
| 10497 | |
| 10498 | /** |
| 10499 | * Returns true if the view this LayoutParams is attached to is now representing |
| 10500 | * potentially invalid data. A LayoutManager should scrap/recycle it. |
| 10501 | * |
| 10502 | * @return true if the view is invalid |
| 10503 | */ |
| 10504 | public boolean isViewInvalid() { |
| 10505 | return mViewHolder.isInvalid(); |
| 10506 | } |
| 10507 | |
| 10508 | /** |
| 10509 | * Returns true if the adapter data item corresponding to the view this LayoutParams |
| 10510 | * is attached to has been removed from the data set. A LayoutManager may choose to |
| 10511 | * treat it differently in order to animate its outgoing or disappearing state. |
| 10512 | * |
| 10513 | * @return true if the item the view corresponds to was removed from the data set |
| 10514 | */ |
| 10515 | public boolean isItemRemoved() { |
| 10516 | return mViewHolder.isRemoved(); |
| 10517 | } |
| 10518 | |
| 10519 | /** |
| 10520 | * Returns true if the adapter data item corresponding to the view this LayoutParams |
| 10521 | * is attached to has been changed in the data set. A LayoutManager may choose to |
| 10522 | * treat it differently in order to animate its changing state. |
| 10523 | * |
| 10524 | * @return true if the item the view corresponds to was changed in the data set |
| 10525 | */ |
| 10526 | public boolean isItemChanged() { |
| 10527 | return mViewHolder.isUpdated(); |
| 10528 | } |
| 10529 | |
| 10530 | /** |
| 10531 | * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()} |
| 10532 | */ |
| 10533 | @Deprecated |
| 10534 | public int getViewPosition() { |
| 10535 | return mViewHolder.getPosition(); |
| 10536 | } |
| 10537 | |
| 10538 | /** |
| 10539 | * Returns the adapter position that the view this LayoutParams is attached to corresponds |
| 10540 | * to as of latest layout calculation. |
| 10541 | * |
| 10542 | * @return the adapter position this view as of latest layout pass |
| 10543 | */ |
| 10544 | public int getViewLayoutPosition() { |
| 10545 | return mViewHolder.getLayoutPosition(); |
| 10546 | } |
| 10547 | |
| 10548 | /** |
| 10549 | * Returns the up-to-date adapter position that the view this LayoutParams is attached to |
| 10550 | * corresponds to. |
| 10551 | * |
| 10552 | * @return the up-to-date adapter position this view. It may return |
| 10553 | * {@link RecyclerView#NO_POSITION} if item represented by this View has been removed or |
| 10554 | * its up-to-date position cannot be calculated. |
| 10555 | */ |
| 10556 | public int getViewAdapterPosition() { |
| 10557 | return mViewHolder.getAdapterPosition(); |
| 10558 | } |
| 10559 | } |
| 10560 | |
| 10561 | /** |
| 10562 | * Observer base class for watching changes to an {@link Adapter}. |
| 10563 | * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}. |
| 10564 | */ |
| 10565 | public abstract static class AdapterDataObserver { |
| 10566 | public void onChanged() { |
| 10567 | // Do nothing |
| 10568 | } |
| 10569 | |
| 10570 | public void onItemRangeChanged(int positionStart, int itemCount) { |
| 10571 | // do nothing |
| 10572 | } |
| 10573 | |
| 10574 | public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { |
| 10575 | // fallback to onItemRangeChanged(positionStart, itemCount) if app |
| 10576 | // does not override this method. |
| 10577 | onItemRangeChanged(positionStart, itemCount); |
| 10578 | } |
| 10579 | |
| 10580 | public void onItemRangeInserted(int positionStart, int itemCount) { |
| 10581 | // do nothing |
| 10582 | } |
| 10583 | |
| 10584 | public void onItemRangeRemoved(int positionStart, int itemCount) { |
| 10585 | // do nothing |
| 10586 | } |
| 10587 | |
| 10588 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { |
| 10589 | // do nothing |
| 10590 | } |
| 10591 | } |
| 10592 | |
| 10593 | /** |
| 10594 | * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and |
| 10595 | * provides methods to trigger a programmatic scroll.</p> |
| 10596 | * |
| 10597 | * @see LinearSmoothScroller |
| 10598 | */ |
| 10599 | public abstract static class SmoothScroller { |
| 10600 | |
| 10601 | private int mTargetPosition = RecyclerView.NO_POSITION; |
| 10602 | |
| 10603 | private RecyclerView mRecyclerView; |
| 10604 | |
| 10605 | private LayoutManager mLayoutManager; |
| 10606 | |
| 10607 | private boolean mPendingInitialRun; |
| 10608 | |
| 10609 | private boolean mRunning; |
| 10610 | |
| 10611 | private View mTargetView; |
| 10612 | |
| 10613 | private final Action mRecyclingAction; |
| 10614 | |
| 10615 | public SmoothScroller() { |
| 10616 | mRecyclingAction = new Action(0, 0); |
| 10617 | } |
| 10618 | |
| 10619 | /** |
| 10620 | * Starts a smooth scroll for the given target position. |
| 10621 | * <p>In each animation step, {@link RecyclerView} will check |
| 10622 | * for the target view and call either |
| 10623 | * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or |
| 10624 | * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until |
| 10625 | * SmoothScroller is stopped.</p> |
| 10626 | * |
| 10627 | * <p>Note that if RecyclerView finds the target view, it will automatically stop the |
| 10628 | * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will |
| 10629 | * stop calling SmoothScroller in each animation step.</p> |
| 10630 | */ |
| 10631 | void start(RecyclerView recyclerView, LayoutManager layoutManager) { |
| 10632 | mRecyclerView = recyclerView; |
| 10633 | mLayoutManager = layoutManager; |
| 10634 | if (mTargetPosition == RecyclerView.NO_POSITION) { |
| 10635 | throw new IllegalArgumentException("Invalid target position"); |
| 10636 | } |
| 10637 | mRecyclerView.mState.mTargetPosition = mTargetPosition; |
| 10638 | mRunning = true; |
| 10639 | mPendingInitialRun = true; |
| 10640 | mTargetView = findViewByPosition(getTargetPosition()); |
| 10641 | onStart(); |
| 10642 | mRecyclerView.mViewFlinger.postOnAnimation(); |
| 10643 | } |
| 10644 | |
| 10645 | public void setTargetPosition(int targetPosition) { |
| 10646 | mTargetPosition = targetPosition; |
| 10647 | } |
| 10648 | |
| 10649 | /** |
| 10650 | * @return The LayoutManager to which this SmoothScroller is attached. Will return |
| 10651 | * <code>null</code> after the SmoothScroller is stopped. |
| 10652 | */ |
| 10653 | @Nullable |
| 10654 | public LayoutManager getLayoutManager() { |
| 10655 | return mLayoutManager; |
| 10656 | } |
| 10657 | |
| 10658 | /** |
| 10659 | * Stops running the SmoothScroller in each animation callback. Note that this does not |
| 10660 | * cancel any existing {@link Action} updated by |
| 10661 | * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or |
| 10662 | * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}. |
| 10663 | */ |
| 10664 | protected final void stop() { |
| 10665 | if (!mRunning) { |
| 10666 | return; |
| 10667 | } |
| 10668 | onStop(); |
| 10669 | mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION; |
| 10670 | mTargetView = null; |
| 10671 | mTargetPosition = RecyclerView.NO_POSITION; |
| 10672 | mPendingInitialRun = false; |
| 10673 | mRunning = false; |
| 10674 | // trigger a cleanup |
| 10675 | mLayoutManager.onSmoothScrollerStopped(this); |
| 10676 | // clear references to avoid any potential leak by a custom smooth scroller |
| 10677 | mLayoutManager = null; |
| 10678 | mRecyclerView = null; |
| 10679 | } |
| 10680 | |
| 10681 | /** |
| 10682 | * Returns true if SmoothScroller has been started but has not received the first |
| 10683 | * animation |
| 10684 | * callback yet. |
| 10685 | * |
| 10686 | * @return True if this SmoothScroller is waiting to start |
| 10687 | */ |
| 10688 | public boolean isPendingInitialRun() { |
| 10689 | return mPendingInitialRun; |
| 10690 | } |
| 10691 | |
| 10692 | |
| 10693 | /** |
| 10694 | * @return True if SmoothScroller is currently active |
| 10695 | */ |
| 10696 | public boolean isRunning() { |
| 10697 | return mRunning; |
| 10698 | } |
| 10699 | |
| 10700 | /** |
| 10701 | * Returns the adapter position of the target item |
| 10702 | * |
| 10703 | * @return Adapter position of the target item or |
| 10704 | * {@link RecyclerView#NO_POSITION} if no target view is set. |
| 10705 | */ |
| 10706 | public int getTargetPosition() { |
| 10707 | return mTargetPosition; |
| 10708 | } |
| 10709 | |
| 10710 | private void onAnimation(int dx, int dy) { |
| 10711 | final RecyclerView recyclerView = mRecyclerView; |
| 10712 | if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) { |
| 10713 | stop(); |
| 10714 | } |
| 10715 | mPendingInitialRun = false; |
| 10716 | if (mTargetView != null) { |
| 10717 | // verify target position |
| 10718 | if (getChildPosition(mTargetView) == mTargetPosition) { |
| 10719 | onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction); |
| 10720 | mRecyclingAction.runIfNecessary(recyclerView); |
| 10721 | stop(); |
| 10722 | } else { |
| 10723 | Log.e(TAG, "Passed over target position while smooth scrolling."); |
| 10724 | mTargetView = null; |
| 10725 | } |
| 10726 | } |
| 10727 | if (mRunning) { |
| 10728 | onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction); |
| 10729 | boolean hadJumpTarget = mRecyclingAction.hasJumpTarget(); |
| 10730 | mRecyclingAction.runIfNecessary(recyclerView); |
| 10731 | if (hadJumpTarget) { |
| 10732 | // It is not stopped so needs to be restarted |
| 10733 | if (mRunning) { |
| 10734 | mPendingInitialRun = true; |
| 10735 | recyclerView.mViewFlinger.postOnAnimation(); |
| 10736 | } else { |
| 10737 | stop(); // done |
| 10738 | } |
| 10739 | } |
| 10740 | } |
| 10741 | } |
| 10742 | |
| 10743 | /** |
| 10744 | * @see RecyclerView#getChildLayoutPosition(android.view.View) |
| 10745 | */ |
| 10746 | public int getChildPosition(View view) { |
| 10747 | return mRecyclerView.getChildLayoutPosition(view); |
| 10748 | } |
| 10749 | |
| 10750 | /** |
| 10751 | * @see RecyclerView.LayoutManager#getChildCount() |
| 10752 | */ |
| 10753 | public int getChildCount() { |
| 10754 | return mRecyclerView.mLayout.getChildCount(); |
| 10755 | } |
| 10756 | |
| 10757 | /** |
| 10758 | * @see RecyclerView.LayoutManager#findViewByPosition(int) |
| 10759 | */ |
| 10760 | public View findViewByPosition(int position) { |
| 10761 | return mRecyclerView.mLayout.findViewByPosition(position); |
| 10762 | } |
| 10763 | |
| 10764 | /** |
| 10765 | * @see RecyclerView#scrollToPosition(int) |
| 10766 | * @deprecated Use {@link Action#jumpTo(int)}. |
| 10767 | */ |
| 10768 | @Deprecated |
| 10769 | public void instantScrollToPosition(int position) { |
| 10770 | mRecyclerView.scrollToPosition(position); |
| 10771 | } |
| 10772 | |
| 10773 | protected void onChildAttachedToWindow(View child) { |
| 10774 | if (getChildPosition(child) == getTargetPosition()) { |
| 10775 | mTargetView = child; |
| 10776 | if (DEBUG) { |
| 10777 | Log.d(TAG, "smooth scroll target view has been attached"); |
| 10778 | } |
| 10779 | } |
| 10780 | } |
| 10781 | |
| 10782 | /** |
| 10783 | * Normalizes the vector. |
| 10784 | * @param scrollVector The vector that points to the target scroll position |
| 10785 | */ |
| 10786 | protected void normalize(PointF scrollVector) { |
| 10787 | final double magnitude = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y |
| 10788 | * scrollVector.y); |
| 10789 | scrollVector.x /= magnitude; |
| 10790 | scrollVector.y /= magnitude; |
| 10791 | } |
| 10792 | |
| 10793 | /** |
| 10794 | * Called when smooth scroll is started. This might be a good time to do setup. |
| 10795 | */ |
| 10796 | protected abstract void onStart(); |
| 10797 | |
| 10798 | /** |
| 10799 | * Called when smooth scroller is stopped. This is a good place to cleanup your state etc. |
| 10800 | * @see #stop() |
| 10801 | */ |
| 10802 | protected abstract void onStop(); |
| 10803 | |
| 10804 | /** |
| 10805 | * <p>RecyclerView will call this method each time it scrolls until it can find the target |
| 10806 | * position in the layout.</p> |
| 10807 | * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the |
| 10808 | * provided {@link Action} to define the next scroll.</p> |
| 10809 | * |
| 10810 | * @param dx Last scroll amount horizontally |
| 10811 | * @param dy Last scroll amount vertically |
| 10812 | * @param state Transient state of RecyclerView |
| 10813 | * @param action If you want to trigger a new smooth scroll and cancel the previous one, |
| 10814 | * update this object. |
| 10815 | */ |
| 10816 | protected abstract void onSeekTargetStep(int dx, int dy, State state, Action action); |
| 10817 | |
| 10818 | /** |
| 10819 | * Called when the target position is laid out. This is the last callback SmoothScroller |
| 10820 | * will receive and it should update the provided {@link Action} to define the scroll |
| 10821 | * details towards the target view. |
| 10822 | * @param targetView The view element which render the target position. |
| 10823 | * @param state Transient state of RecyclerView |
| 10824 | * @param action Action instance that you should update to define final scroll action |
| 10825 | * towards the targetView |
| 10826 | */ |
| 10827 | protected abstract void onTargetFound(View targetView, State state, Action action); |
| 10828 | |
| 10829 | /** |
| 10830 | * Holds information about a smooth scroll request by a {@link SmoothScroller}. |
| 10831 | */ |
| 10832 | public static class Action { |
| 10833 | |
| 10834 | public static final int UNDEFINED_DURATION = Integer.MIN_VALUE; |
| 10835 | |
| 10836 | private int mDx; |
| 10837 | |
| 10838 | private int mDy; |
| 10839 | |
| 10840 | private int mDuration; |
| 10841 | |
| 10842 | private int mJumpToPosition = NO_POSITION; |
| 10843 | |
| 10844 | private Interpolator mInterpolator; |
| 10845 | |
| 10846 | private boolean mChanged = false; |
| 10847 | |
| 10848 | // we track this variable to inform custom implementer if they are updating the action |
| 10849 | // in every animation callback |
| 10850 | private int mConsecutiveUpdates = 0; |
| 10851 | |
| 10852 | /** |
| 10853 | * @param dx Pixels to scroll horizontally |
| 10854 | * @param dy Pixels to scroll vertically |
| 10855 | */ |
| 10856 | public Action(int dx, int dy) { |
| 10857 | this(dx, dy, UNDEFINED_DURATION, null); |
| 10858 | } |
| 10859 | |
| 10860 | /** |
| 10861 | * @param dx Pixels to scroll horizontally |
| 10862 | * @param dy Pixels to scroll vertically |
| 10863 | * @param duration Duration of the animation in milliseconds |
| 10864 | */ |
| 10865 | public Action(int dx, int dy, int duration) { |
| 10866 | this(dx, dy, duration, null); |
| 10867 | } |
| 10868 | |
| 10869 | /** |
| 10870 | * @param dx Pixels to scroll horizontally |
| 10871 | * @param dy Pixels to scroll vertically |
| 10872 | * @param duration Duration of the animation in milliseconds |
| 10873 | * @param interpolator Interpolator to be used when calculating scroll position in each |
| 10874 | * animation step |
| 10875 | */ |
| 10876 | public Action(int dx, int dy, int duration, Interpolator interpolator) { |
| 10877 | mDx = dx; |
| 10878 | mDy = dy; |
| 10879 | mDuration = duration; |
| 10880 | mInterpolator = interpolator; |
| 10881 | } |
| 10882 | |
| 10883 | /** |
| 10884 | * Instead of specifying pixels to scroll, use the target position to jump using |
| 10885 | * {@link RecyclerView#scrollToPosition(int)}. |
| 10886 | * <p> |
| 10887 | * You may prefer using this method if scroll target is really far away and you prefer |
| 10888 | * to jump to a location and smooth scroll afterwards. |
| 10889 | * <p> |
| 10890 | * Note that calling this method takes priority over other update methods such as |
| 10891 | * {@link #update(int, int, int, Interpolator)}, {@link #setX(float)}, |
| 10892 | * {@link #setY(float)} and #{@link #setInterpolator(Interpolator)}. If you call |
| 10893 | * {@link #jumpTo(int)}, the other changes will not be considered for this animation |
| 10894 | * frame. |
| 10895 | * |
| 10896 | * @param targetPosition The target item position to scroll to using instant scrolling. |
| 10897 | */ |
| 10898 | public void jumpTo(int targetPosition) { |
| 10899 | mJumpToPosition = targetPosition; |
| 10900 | } |
| 10901 | |
| 10902 | boolean hasJumpTarget() { |
| 10903 | return mJumpToPosition >= 0; |
| 10904 | } |
| 10905 | |
| 10906 | void runIfNecessary(RecyclerView recyclerView) { |
| 10907 | if (mJumpToPosition >= 0) { |
| 10908 | final int position = mJumpToPosition; |
| 10909 | mJumpToPosition = NO_POSITION; |
| 10910 | recyclerView.jumpToPositionForSmoothScroller(position); |
| 10911 | mChanged = false; |
| 10912 | return; |
| 10913 | } |
| 10914 | if (mChanged) { |
| 10915 | validate(); |
| 10916 | if (mInterpolator == null) { |
| 10917 | if (mDuration == UNDEFINED_DURATION) { |
| 10918 | recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy); |
| 10919 | } else { |
| 10920 | recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration); |
| 10921 | } |
| 10922 | } else { |
| 10923 | recyclerView.mViewFlinger.smoothScrollBy( |
| 10924 | mDx, mDy, mDuration, mInterpolator); |
| 10925 | } |
| 10926 | mConsecutiveUpdates++; |
| 10927 | if (mConsecutiveUpdates > 10) { |
| 10928 | // A new action is being set in every animation step. This looks like a bad |
| 10929 | // implementation. Inform developer. |
| 10930 | Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure" |
| 10931 | + " you are not changing it unless necessary"); |
| 10932 | } |
| 10933 | mChanged = false; |
| 10934 | } else { |
| 10935 | mConsecutiveUpdates = 0; |
| 10936 | } |
| 10937 | } |
| 10938 | |
| 10939 | private void validate() { |
| 10940 | if (mInterpolator != null && mDuration < 1) { |
| 10941 | throw new IllegalStateException("If you provide an interpolator, you must" |
| 10942 | + " set a positive duration"); |
| 10943 | } else if (mDuration < 1) { |
| 10944 | throw new IllegalStateException("Scroll duration must be a positive number"); |
| 10945 | } |
| 10946 | } |
| 10947 | |
| 10948 | public int getDx() { |
| 10949 | return mDx; |
| 10950 | } |
| 10951 | |
| 10952 | public void setDx(int dx) { |
| 10953 | mChanged = true; |
| 10954 | mDx = dx; |
| 10955 | } |
| 10956 | |
| 10957 | public int getDy() { |
| 10958 | return mDy; |
| 10959 | } |
| 10960 | |
| 10961 | public void setDy(int dy) { |
| 10962 | mChanged = true; |
| 10963 | mDy = dy; |
| 10964 | } |
| 10965 | |
| 10966 | public int getDuration() { |
| 10967 | return mDuration; |
| 10968 | } |
| 10969 | |
| 10970 | public void setDuration(int duration) { |
| 10971 | mChanged = true; |
| 10972 | mDuration = duration; |
| 10973 | } |
| 10974 | |
| 10975 | public Interpolator getInterpolator() { |
| 10976 | return mInterpolator; |
| 10977 | } |
| 10978 | |
| 10979 | /** |
| 10980 | * Sets the interpolator to calculate scroll steps |
| 10981 | * @param interpolator The interpolator to use. If you specify an interpolator, you must |
| 10982 | * also set the duration. |
| 10983 | * @see #setDuration(int) |
| 10984 | */ |
| 10985 | public void setInterpolator(Interpolator interpolator) { |
| 10986 | mChanged = true; |
| 10987 | mInterpolator = interpolator; |
| 10988 | } |
| 10989 | |
| 10990 | /** |
| 10991 | * Updates the action with given parameters. |
| 10992 | * @param dx Pixels to scroll horizontally |
| 10993 | * @param dy Pixels to scroll vertically |
| 10994 | * @param duration Duration of the animation in milliseconds |
| 10995 | * @param interpolator Interpolator to be used when calculating scroll position in each |
| 10996 | * animation step |
| 10997 | */ |
| 10998 | public void update(int dx, int dy, int duration, Interpolator interpolator) { |
| 10999 | mDx = dx; |
| 11000 | mDy = dy; |
| 11001 | mDuration = duration; |
| 11002 | mInterpolator = interpolator; |
| 11003 | mChanged = true; |
| 11004 | } |
| 11005 | } |
| 11006 | |
| 11007 | /** |
| 11008 | * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager} |
| 11009 | * to provide a hint to a {@link SmoothScroller} about the location of the target position. |
| 11010 | */ |
| 11011 | public interface ScrollVectorProvider { |
| 11012 | /** |
| 11013 | * Should calculate the vector that points to the direction where the target position |
| 11014 | * can be found. |
| 11015 | * <p> |
| 11016 | * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards |
| 11017 | * the target position. |
| 11018 | * <p> |
| 11019 | * The magnitude of the vector is not important. It is always normalized before being |
| 11020 | * used by the {@link LinearSmoothScroller}. |
| 11021 | * <p> |
| 11022 | * LayoutManager should not check whether the position exists in the adapter or not. |
| 11023 | * |
| 11024 | * @param targetPosition the target position to which the returned vector should point |
| 11025 | * |
| 11026 | * @return the scroll vector for a given position. |
| 11027 | */ |
| 11028 | PointF computeScrollVectorForPosition(int targetPosition); |
| 11029 | } |
| 11030 | } |
| 11031 | |
| 11032 | static class AdapterDataObservable extends Observable<AdapterDataObserver> { |
| 11033 | public boolean hasObservers() { |
| 11034 | return !mObservers.isEmpty(); |
| 11035 | } |
| 11036 | |
| 11037 | public void notifyChanged() { |
| 11038 | // since onChanged() is implemented by the app, it could do anything, including |
| 11039 | // removing itself from {@link mObservers} - and that could cause problems if |
| 11040 | // an iterator is used on the ArrayList {@link mObservers}. |
| 11041 | // to avoid such problems, just march thru the list in the reverse order. |
| 11042 | for (int i = mObservers.size() - 1; i >= 0; i--) { |
| 11043 | mObservers.get(i).onChanged(); |
| 11044 | } |
| 11045 | } |
| 11046 | |
| 11047 | public void notifyItemRangeChanged(int positionStart, int itemCount) { |
| 11048 | notifyItemRangeChanged(positionStart, itemCount, null); |
| 11049 | } |
| 11050 | |
| 11051 | public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { |
| 11052 | // since onItemRangeChanged() is implemented by the app, it could do anything, including |
| 11053 | // removing itself from {@link mObservers} - and that could cause problems if |
| 11054 | // an iterator is used on the ArrayList {@link mObservers}. |
| 11055 | // to avoid such problems, just march thru the list in the reverse order. |
| 11056 | for (int i = mObservers.size() - 1; i >= 0; i--) { |
| 11057 | mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); |
| 11058 | } |
| 11059 | } |
| 11060 | |
| 11061 | public void notifyItemRangeInserted(int positionStart, int itemCount) { |
| 11062 | // since onItemRangeInserted() is implemented by the app, it could do anything, |
| 11063 | // including removing itself from {@link mObservers} - and that could cause problems if |
| 11064 | // an iterator is used on the ArrayList {@link mObservers}. |
| 11065 | // to avoid such problems, just march thru the list in the reverse order. |
| 11066 | for (int i = mObservers.size() - 1; i >= 0; i--) { |
| 11067 | mObservers.get(i).onItemRangeInserted(positionStart, itemCount); |
| 11068 | } |
| 11069 | } |
| 11070 | |
| 11071 | public void notifyItemRangeRemoved(int positionStart, int itemCount) { |
| 11072 | // since onItemRangeRemoved() is implemented by the app, it could do anything, including |
| 11073 | // removing itself from {@link mObservers} - and that could cause problems if |
| 11074 | // an iterator is used on the ArrayList {@link mObservers}. |
| 11075 | // to avoid such problems, just march thru the list in the reverse order. |
| 11076 | for (int i = mObservers.size() - 1; i >= 0; i--) { |
| 11077 | mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); |
| 11078 | } |
| 11079 | } |
| 11080 | |
| 11081 | public void notifyItemMoved(int fromPosition, int toPosition) { |
| 11082 | for (int i = mObservers.size() - 1; i >= 0; i--) { |
| 11083 | mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); |
| 11084 | } |
| 11085 | } |
| 11086 | } |
| 11087 | |
| 11088 | /** |
| 11089 | * This is public so that the CREATOR can be access on cold launch. |
| 11090 | * @hide |
| 11091 | */ |
| 11092 | public static class SavedState extends AbsSavedState { |
| 11093 | |
| 11094 | Parcelable mLayoutState; |
| 11095 | |
| 11096 | /** |
| 11097 | * called by CREATOR |
| 11098 | */ |
| 11099 | SavedState(Parcel in) { |
| 11100 | super(in); |
| 11101 | mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader()); |
| 11102 | } |
| 11103 | |
| 11104 | /** |
| 11105 | * Called by onSaveInstanceState |
| 11106 | */ |
| 11107 | SavedState(Parcelable superState) { |
| 11108 | super(superState); |
| 11109 | } |
| 11110 | |
| 11111 | @Override |
| 11112 | public void writeToParcel(Parcel dest, int flags) { |
| 11113 | super.writeToParcel(dest, flags); |
| 11114 | dest.writeParcelable(mLayoutState, 0); |
| 11115 | } |
| 11116 | |
| 11117 | void copyFrom(SavedState other) { |
| 11118 | mLayoutState = other.mLayoutState; |
| 11119 | } |
| 11120 | |
| 11121 | public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { |
| 11122 | @Override |
| 11123 | public SavedState createFromParcel(Parcel in) { |
| 11124 | return new SavedState(in); |
| 11125 | } |
| 11126 | |
| 11127 | @Override |
| 11128 | public SavedState[] newArray(int size) { |
| 11129 | return new SavedState[size]; |
| 11130 | } |
| 11131 | }; |
| 11132 | } |
| 11133 | /** |
| 11134 | * <p>Contains useful information about the current RecyclerView state like target scroll |
| 11135 | * position or view focus. State object can also keep arbitrary data, identified by resource |
| 11136 | * ids.</p> |
| 11137 | * <p>Often times, RecyclerView components will need to pass information between each other. |
| 11138 | * To provide a well defined data bus between components, RecyclerView passes the same State |
| 11139 | * object to component callbacks and these components can use it to exchange data.</p> |
| 11140 | * <p>If you implement custom components, you can use State's put/get/remove methods to pass |
| 11141 | * data between your components without needing to manage their lifecycles.</p> |
| 11142 | */ |
| 11143 | public static class State { |
| 11144 | static final int STEP_START = 1; |
| 11145 | static final int STEP_LAYOUT = 1 << 1; |
| 11146 | static final int STEP_ANIMATIONS = 1 << 2; |
| 11147 | |
| 11148 | void assertLayoutStep(int accepted) { |
| 11149 | if ((accepted & mLayoutStep) == 0) { |
| 11150 | throw new IllegalStateException("Layout state should be one of " |
| 11151 | + Integer.toBinaryString(accepted) + " but it is " |
| 11152 | + Integer.toBinaryString(mLayoutStep)); |
| 11153 | } |
| 11154 | } |
| 11155 | |
| 11156 | |
| 11157 | /** Owned by SmoothScroller */ |
| 11158 | private int mTargetPosition = RecyclerView.NO_POSITION; |
| 11159 | |
| 11160 | private SparseArray<Object> mData; |
| 11161 | |
| 11162 | //////////////////////////////////////////////////////////////////////////////////////////// |
| 11163 | // Fields below are carried from one layout pass to the next |
| 11164 | //////////////////////////////////////////////////////////////////////////////////////////// |
| 11165 | |
| 11166 | /** |
| 11167 | * Number of items adapter had in the previous layout. |
| 11168 | */ |
| 11169 | int mPreviousLayoutItemCount = 0; |
| 11170 | |
| 11171 | /** |
| 11172 | * Number of items that were NOT laid out but has been deleted from the adapter after the |
| 11173 | * previous layout. |
| 11174 | */ |
| 11175 | int mDeletedInvisibleItemCountSincePreviousLayout = 0; |
| 11176 | |
| 11177 | //////////////////////////////////////////////////////////////////////////////////////////// |
| 11178 | // Fields below must be updated or cleared before they are used (generally before a pass) |
| 11179 | //////////////////////////////////////////////////////////////////////////////////////////// |
| 11180 | |
| 11181 | @IntDef(flag = true, value = { |
| 11182 | STEP_START, STEP_LAYOUT, STEP_ANIMATIONS |
| 11183 | }) |
| 11184 | @Retention(RetentionPolicy.SOURCE) |
| 11185 | @interface LayoutState {} |
| 11186 | |
| 11187 | @LayoutState |
| 11188 | int mLayoutStep = STEP_START; |
| 11189 | |
| 11190 | /** |
| 11191 | * Number of items adapter has. |
| 11192 | */ |
| 11193 | int mItemCount = 0; |
| 11194 | |
| 11195 | boolean mStructureChanged = false; |
| 11196 | |
| 11197 | boolean mInPreLayout = false; |
| 11198 | |
| 11199 | boolean mTrackOldChangeHolders = false; |
| 11200 | |
| 11201 | boolean mIsMeasuring = false; |
| 11202 | |
| 11203 | //////////////////////////////////////////////////////////////////////////////////////////// |
| 11204 | // Fields below are always reset outside of the pass (or passes) that use them |
| 11205 | //////////////////////////////////////////////////////////////////////////////////////////// |
| 11206 | |
| 11207 | boolean mRunSimpleAnimations = false; |
| 11208 | |
| 11209 | boolean mRunPredictiveAnimations = false; |
| 11210 | |
| 11211 | /** |
| 11212 | * This data is saved before a layout calculation happens. After the layout is finished, |
| 11213 | * if the previously focused view has been replaced with another view for the same item, we |
| 11214 | * move the focus to the new item automatically. |
| 11215 | */ |
| 11216 | int mFocusedItemPosition; |
| 11217 | long mFocusedItemId; |
| 11218 | // when a sub child has focus, record its id and see if we can directly request focus on |
| 11219 | // that one instead |
| 11220 | int mFocusedSubChildId; |
| 11221 | |
| 11222 | //////////////////////////////////////////////////////////////////////////////////////////// |
| 11223 | |
| 11224 | State reset() { |
| 11225 | mTargetPosition = RecyclerView.NO_POSITION; |
| 11226 | if (mData != null) { |
| 11227 | mData.clear(); |
| 11228 | } |
| 11229 | mItemCount = 0; |
| 11230 | mStructureChanged = false; |
| 11231 | mIsMeasuring = false; |
| 11232 | return this; |
| 11233 | } |
| 11234 | |
| 11235 | /** |
| 11236 | * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially |
| 11237 | * prior to any layout passes. |
| 11238 | * |
| 11239 | * <p>Don't touch any state stored between layout passes, only reset per-layout state, so |
| 11240 | * that Recycler#getViewForPosition() can function safely.</p> |
| 11241 | */ |
| 11242 | void prepareForNestedPrefetch(Adapter adapter) { |
| 11243 | mLayoutStep = STEP_START; |
| 11244 | mItemCount = adapter.getItemCount(); |
| 11245 | mStructureChanged = false; |
| 11246 | mInPreLayout = false; |
| 11247 | mTrackOldChangeHolders = false; |
| 11248 | mIsMeasuring = false; |
| 11249 | } |
| 11250 | |
| 11251 | /** |
| 11252 | * Returns true if the RecyclerView is currently measuring the layout. This value is |
| 11253 | * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView |
| 11254 | * has non-exact measurement specs. |
| 11255 | * <p> |
| 11256 | * Note that if the LayoutManager supports predictive animations and it is calculating the |
| 11257 | * pre-layout step, this value will be {@code false} even if the RecyclerView is in |
| 11258 | * {@code onMeasure} call. This is because pre-layout means the previous state of the |
| 11259 | * RecyclerView and measurements made for that state cannot change the RecyclerView's size. |
| 11260 | * LayoutManager is always guaranteed to receive another call to |
| 11261 | * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens. |
| 11262 | * |
| 11263 | * @return True if the RecyclerView is currently calculating its bounds, false otherwise. |
| 11264 | */ |
| 11265 | public boolean isMeasuring() { |
| 11266 | return mIsMeasuring; |
| 11267 | } |
| 11268 | |
| 11269 | /** |
| 11270 | * Returns true if |
| 11271 | * @return |
| 11272 | */ |
| 11273 | public boolean isPreLayout() { |
| 11274 | return mInPreLayout; |
| 11275 | } |
| 11276 | |
| 11277 | /** |
| 11278 | * Returns whether RecyclerView will run predictive animations in this layout pass |
| 11279 | * or not. |
| 11280 | * |
| 11281 | * @return true if RecyclerView is calculating predictive animations to be run at the end |
| 11282 | * of the layout pass. |
| 11283 | */ |
| 11284 | public boolean willRunPredictiveAnimations() { |
| 11285 | return mRunPredictiveAnimations; |
| 11286 | } |
| 11287 | |
| 11288 | /** |
| 11289 | * Returns whether RecyclerView will run simple animations in this layout pass |
| 11290 | * or not. |
| 11291 | * |
| 11292 | * @return true if RecyclerView is calculating simple animations to be run at the end of |
| 11293 | * the layout pass. |
| 11294 | */ |
| 11295 | public boolean willRunSimpleAnimations() { |
| 11296 | return mRunSimpleAnimations; |
| 11297 | } |
| 11298 | |
| 11299 | /** |
| 11300 | * Removes the mapping from the specified id, if there was any. |
| 11301 | * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to |
| 11302 | * preserve cross functionality and avoid conflicts. |
| 11303 | */ |
| 11304 | public void remove(int resourceId) { |
| 11305 | if (mData == null) { |
| 11306 | return; |
| 11307 | } |
| 11308 | mData.remove(resourceId); |
| 11309 | } |
| 11310 | |
| 11311 | /** |
| 11312 | * Gets the Object mapped from the specified id, or <code>null</code> |
| 11313 | * if no such data exists. |
| 11314 | * |
| 11315 | * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* |
| 11316 | * to |
| 11317 | * preserve cross functionality and avoid conflicts. |
| 11318 | */ |
| 11319 | public <T> T get(int resourceId) { |
| 11320 | if (mData == null) { |
| 11321 | return null; |
| 11322 | } |
| 11323 | return (T) mData.get(resourceId); |
| 11324 | } |
| 11325 | |
| 11326 | /** |
| 11327 | * Adds a mapping from the specified id to the specified value, replacing the previous |
| 11328 | * mapping from the specified key if there was one. |
| 11329 | * |
| 11330 | * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to |
| 11331 | * preserve cross functionality and avoid conflicts. |
| 11332 | * @param data The data you want to associate with the resourceId. |
| 11333 | */ |
| 11334 | public void put(int resourceId, Object data) { |
| 11335 | if (mData == null) { |
| 11336 | mData = new SparseArray<Object>(); |
| 11337 | } |
| 11338 | mData.put(resourceId, data); |
| 11339 | } |
| 11340 | |
| 11341 | /** |
| 11342 | * If scroll is triggered to make a certain item visible, this value will return the |
| 11343 | * adapter index of that item. |
| 11344 | * @return Adapter index of the target item or |
| 11345 | * {@link RecyclerView#NO_POSITION} if there is no target |
| 11346 | * position. |
| 11347 | */ |
| 11348 | public int getTargetScrollPosition() { |
| 11349 | return mTargetPosition; |
| 11350 | } |
| 11351 | |
| 11352 | /** |
| 11353 | * Returns if current scroll has a target position. |
| 11354 | * @return true if scroll is being triggered to make a certain position visible |
| 11355 | * @see #getTargetScrollPosition() |
| 11356 | */ |
| 11357 | public boolean hasTargetScrollPosition() { |
| 11358 | return mTargetPosition != RecyclerView.NO_POSITION; |
| 11359 | } |
| 11360 | |
| 11361 | /** |
| 11362 | * @return true if the structure of the data set has changed since the last call to |
| 11363 | * onLayoutChildren, false otherwise |
| 11364 | */ |
| 11365 | public boolean didStructureChange() { |
| 11366 | return mStructureChanged; |
| 11367 | } |
| 11368 | |
| 11369 | /** |
| 11370 | * Returns the total number of items that can be laid out. Note that this number is not |
| 11371 | * necessarily equal to the number of items in the adapter, so you should always use this |
| 11372 | * number for your position calculations and never access the adapter directly. |
| 11373 | * <p> |
| 11374 | * RecyclerView listens for Adapter's notify events and calculates the effects of adapter |
| 11375 | * data changes on existing Views. These calculations are used to decide which animations |
| 11376 | * should be run. |
| 11377 | * <p> |
| 11378 | * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to |
| 11379 | * present the correct state to LayoutManager in pre-layout pass. |
| 11380 | * <p> |
| 11381 | * For example, a newly added item is not included in pre-layout item count because |
| 11382 | * pre-layout reflects the contents of the adapter before the item is added. Behind the |
| 11383 | * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that |
| 11384 | * LayoutManager does not know about the new item's existence in pre-layout. The item will |
| 11385 | * be available in second layout pass and will be included in the item count. Similar |
| 11386 | * adjustments are made for moved and removed items as well. |
| 11387 | * <p> |
| 11388 | * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method. |
| 11389 | * |
| 11390 | * @return The number of items currently available |
| 11391 | * @see LayoutManager#getItemCount() |
| 11392 | */ |
| 11393 | public int getItemCount() { |
| 11394 | return mInPreLayout |
| 11395 | ? (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) |
| 11396 | : mItemCount; |
| 11397 | } |
| 11398 | |
| 11399 | @Override |
| 11400 | public String toString() { |
| 11401 | return "State{" |
| 11402 | + "mTargetPosition=" + mTargetPosition |
| 11403 | + ", mData=" + mData |
| 11404 | + ", mItemCount=" + mItemCount |
| 11405 | + ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount |
| 11406 | + ", mDeletedInvisibleItemCountSincePreviousLayout=" |
| 11407 | + mDeletedInvisibleItemCountSincePreviousLayout |
| 11408 | + ", mStructureChanged=" + mStructureChanged |
| 11409 | + ", mInPreLayout=" + mInPreLayout |
| 11410 | + ", mRunSimpleAnimations=" + mRunSimpleAnimations |
| 11411 | + ", mRunPredictiveAnimations=" + mRunPredictiveAnimations |
| 11412 | + '}'; |
| 11413 | } |
| 11414 | } |
| 11415 | |
| 11416 | /** |
| 11417 | * This class defines the behavior of fling if the developer wishes to handle it. |
| 11418 | * <p> |
| 11419 | * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior. |
| 11420 | * |
| 11421 | * @see #setOnFlingListener(OnFlingListener) |
| 11422 | */ |
| 11423 | public abstract static class OnFlingListener { |
| 11424 | |
| 11425 | /** |
| 11426 | * Override this to handle a fling given the velocities in both x and y directions. |
| 11427 | * Note that this method will only be called if the associated {@link LayoutManager} |
| 11428 | * supports scrolling and the fling is not handled by nested scrolls first. |
| 11429 | * |
| 11430 | * @param velocityX the fling velocity on the X axis |
| 11431 | * @param velocityY the fling velocity on the Y axis |
| 11432 | * |
| 11433 | * @return true if the fling washandled, false otherwise. |
| 11434 | */ |
| 11435 | public abstract boolean onFling(int velocityX, int velocityY); |
| 11436 | } |
| 11437 | |
| 11438 | /** |
| 11439 | * Internal listener that manages items after animations finish. This is how items are |
| 11440 | * retained (not recycled) during animations, but allowed to be recycled afterwards. |
| 11441 | * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished() |
| 11442 | * method on the animator's listener when it is done animating any item. |
| 11443 | */ |
| 11444 | private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { |
| 11445 | |
| 11446 | ItemAnimatorRestoreListener() { |
| 11447 | } |
| 11448 | |
| 11449 | @Override |
| 11450 | public void onAnimationFinished(ViewHolder item) { |
| 11451 | item.setIsRecyclable(true); |
| 11452 | if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh |
| 11453 | item.mShadowedHolder = null; |
| 11454 | } |
| 11455 | // always null this because an OldViewHolder can never become NewViewHolder w/o being |
| 11456 | // recycled. |
| 11457 | item.mShadowingHolder = null; |
| 11458 | if (!item.shouldBeKeptAsChild()) { |
| 11459 | if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) { |
| 11460 | removeDetachedView(item.itemView, false); |
| 11461 | } |
| 11462 | } |
| 11463 | } |
| 11464 | } |
| 11465 | |
| 11466 | /** |
| 11467 | * This class defines the animations that take place on items as changes are made |
| 11468 | * to the adapter. |
| 11469 | * |
| 11470 | * Subclasses of ItemAnimator can be used to implement custom animations for actions on |
| 11471 | * ViewHolder items. The RecyclerView will manage retaining these items while they |
| 11472 | * are being animated, but implementors must call {@link #dispatchAnimationFinished(ViewHolder)} |
| 11473 | * when a ViewHolder's animation is finished. In other words, there must be a matching |
| 11474 | * {@link #dispatchAnimationFinished(ViewHolder)} call for each |
| 11475 | * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) animateAppearance()}, |
| 11476 | * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11477 | * animateChange()} |
| 11478 | * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) animatePersistence()}, |
| 11479 | * and |
| 11480 | * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11481 | * animateDisappearance()} call. |
| 11482 | * |
| 11483 | * <p>By default, RecyclerView uses {@link DefaultItemAnimator}.</p> |
| 11484 | * |
| 11485 | * @see #setItemAnimator(ItemAnimator) |
| 11486 | */ |
| 11487 | @SuppressWarnings("UnusedParameters") |
| 11488 | public abstract static class ItemAnimator { |
| 11489 | |
| 11490 | /** |
| 11491 | * The Item represented by this ViewHolder is updated. |
| 11492 | * <p> |
| 11493 | * @see #recordPreLayoutInformation(State, ViewHolder, int, List) |
| 11494 | */ |
| 11495 | public static final int FLAG_CHANGED = ViewHolder.FLAG_UPDATE; |
| 11496 | |
| 11497 | /** |
| 11498 | * The Item represented by this ViewHolder is removed from the adapter. |
| 11499 | * <p> |
| 11500 | * @see #recordPreLayoutInformation(State, ViewHolder, int, List) |
| 11501 | */ |
| 11502 | public static final int FLAG_REMOVED = ViewHolder.FLAG_REMOVED; |
| 11503 | |
| 11504 | /** |
| 11505 | * Adapter {@link Adapter#notifyDataSetChanged()} has been called and the content |
| 11506 | * represented by this ViewHolder is invalid. |
| 11507 | * <p> |
| 11508 | * @see #recordPreLayoutInformation(State, ViewHolder, int, List) |
| 11509 | */ |
| 11510 | public static final int FLAG_INVALIDATED = ViewHolder.FLAG_INVALID; |
| 11511 | |
| 11512 | /** |
| 11513 | * The position of the Item represented by this ViewHolder has been changed. This flag is |
| 11514 | * not bound to {@link Adapter#notifyItemMoved(int, int)}. It might be set in response to |
| 11515 | * any adapter change that may have a side effect on this item. (e.g. The item before this |
| 11516 | * one has been removed from the Adapter). |
| 11517 | * <p> |
| 11518 | * @see #recordPreLayoutInformation(State, ViewHolder, int, List) |
| 11519 | */ |
| 11520 | public static final int FLAG_MOVED = ViewHolder.FLAG_MOVED; |
| 11521 | |
| 11522 | /** |
| 11523 | * This ViewHolder was not laid out but has been added to the layout in pre-layout state |
| 11524 | * by the {@link LayoutManager}. This means that the item was already in the Adapter but |
| 11525 | * invisible and it may become visible in the post layout phase. LayoutManagers may prefer |
| 11526 | * to add new items in pre-layout to specify their virtual location when they are invisible |
| 11527 | * (e.g. to specify the item should <i>animate in</i> from below the visible area). |
| 11528 | * <p> |
| 11529 | * @see #recordPreLayoutInformation(State, ViewHolder, int, List) |
| 11530 | */ |
| 11531 | public static final int FLAG_APPEARED_IN_PRE_LAYOUT = |
| 11532 | ViewHolder.FLAG_APPEARED_IN_PRE_LAYOUT; |
| 11533 | |
| 11534 | /** |
| 11535 | * The set of flags that might be passed to |
| 11536 | * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11537 | */ |
| 11538 | @IntDef(flag = true, value = { |
| 11539 | FLAG_CHANGED, FLAG_REMOVED, FLAG_MOVED, FLAG_INVALIDATED, |
| 11540 | FLAG_APPEARED_IN_PRE_LAYOUT |
| 11541 | }) |
| 11542 | @Retention(RetentionPolicy.SOURCE) |
| 11543 | public @interface AdapterChanges {} |
| 11544 | private ItemAnimatorListener mListener = null; |
| 11545 | private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners = |
| 11546 | new ArrayList<ItemAnimatorFinishedListener>(); |
| 11547 | |
| 11548 | private long mAddDuration = 120; |
| 11549 | private long mRemoveDuration = 120; |
| 11550 | private long mMoveDuration = 250; |
| 11551 | private long mChangeDuration = 250; |
| 11552 | |
| 11553 | /** |
| 11554 | * Gets the current duration for which all move animations will run. |
| 11555 | * |
| 11556 | * @return The current move duration |
| 11557 | */ |
| 11558 | public long getMoveDuration() { |
| 11559 | return mMoveDuration; |
| 11560 | } |
| 11561 | |
| 11562 | /** |
| 11563 | * Sets the duration for which all move animations will run. |
| 11564 | * |
| 11565 | * @param moveDuration The move duration |
| 11566 | */ |
| 11567 | public void setMoveDuration(long moveDuration) { |
| 11568 | mMoveDuration = moveDuration; |
| 11569 | } |
| 11570 | |
| 11571 | /** |
| 11572 | * Gets the current duration for which all add animations will run. |
| 11573 | * |
| 11574 | * @return The current add duration |
| 11575 | */ |
| 11576 | public long getAddDuration() { |
| 11577 | return mAddDuration; |
| 11578 | } |
| 11579 | |
| 11580 | /** |
| 11581 | * Sets the duration for which all add animations will run. |
| 11582 | * |
| 11583 | * @param addDuration The add duration |
| 11584 | */ |
| 11585 | public void setAddDuration(long addDuration) { |
| 11586 | mAddDuration = addDuration; |
| 11587 | } |
| 11588 | |
| 11589 | /** |
| 11590 | * Gets the current duration for which all remove animations will run. |
| 11591 | * |
| 11592 | * @return The current remove duration |
| 11593 | */ |
| 11594 | public long getRemoveDuration() { |
| 11595 | return mRemoveDuration; |
| 11596 | } |
| 11597 | |
| 11598 | /** |
| 11599 | * Sets the duration for which all remove animations will run. |
| 11600 | * |
| 11601 | * @param removeDuration The remove duration |
| 11602 | */ |
| 11603 | public void setRemoveDuration(long removeDuration) { |
| 11604 | mRemoveDuration = removeDuration; |
| 11605 | } |
| 11606 | |
| 11607 | /** |
| 11608 | * Gets the current duration for which all change animations will run. |
| 11609 | * |
| 11610 | * @return The current change duration |
| 11611 | */ |
| 11612 | public long getChangeDuration() { |
| 11613 | return mChangeDuration; |
| 11614 | } |
| 11615 | |
| 11616 | /** |
| 11617 | * Sets the duration for which all change animations will run. |
| 11618 | * |
| 11619 | * @param changeDuration The change duration |
| 11620 | */ |
| 11621 | public void setChangeDuration(long changeDuration) { |
| 11622 | mChangeDuration = changeDuration; |
| 11623 | } |
| 11624 | |
| 11625 | /** |
| 11626 | * Internal only: |
| 11627 | * Sets the listener that must be called when the animator is finished |
| 11628 | * animating the item (or immediately if no animation happens). This is set |
| 11629 | * internally and is not intended to be set by external code. |
| 11630 | * |
| 11631 | * @param listener The listener that must be called. |
| 11632 | */ |
| 11633 | void setListener(ItemAnimatorListener listener) { |
| 11634 | mListener = listener; |
| 11635 | } |
| 11636 | |
| 11637 | /** |
| 11638 | * Called by the RecyclerView before the layout begins. Item animator should record |
| 11639 | * necessary information about the View before it is potentially rebound, moved or removed. |
| 11640 | * <p> |
| 11641 | * The data returned from this method will be passed to the related <code>animate**</code> |
| 11642 | * methods. |
| 11643 | * <p> |
| 11644 | * Note that this method may be called after pre-layout phase if LayoutManager adds new |
| 11645 | * Views to the layout in pre-layout pass. |
| 11646 | * <p> |
| 11647 | * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of |
| 11648 | * the View and the adapter change flags. |
| 11649 | * |
| 11650 | * @param state The current State of RecyclerView which includes some useful data |
| 11651 | * about the layout that will be calculated. |
| 11652 | * @param viewHolder The ViewHolder whose information should be recorded. |
| 11653 | * @param changeFlags Additional information about what changes happened in the Adapter |
| 11654 | * about the Item represented by this ViewHolder. For instance, if |
| 11655 | * item is deleted from the adapter, {@link #FLAG_REMOVED} will be set. |
| 11656 | * @param payloads The payload list that was previously passed to |
| 11657 | * {@link Adapter#notifyItemChanged(int, Object)} or |
| 11658 | * {@link Adapter#notifyItemRangeChanged(int, int, Object)}. |
| 11659 | * |
| 11660 | * @return An ItemHolderInfo instance that preserves necessary information about the |
| 11661 | * ViewHolder. This object will be passed back to related <code>animate**</code> methods |
| 11662 | * after layout is complete. |
| 11663 | * |
| 11664 | * @see #recordPostLayoutInformation(State, ViewHolder) |
| 11665 | * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11666 | * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11667 | * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11668 | * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11669 | */ |
| 11670 | public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state, |
| 11671 | @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, |
| 11672 | @NonNull List<Object> payloads) { |
| 11673 | return obtainHolderInfo().setFrom(viewHolder); |
| 11674 | } |
| 11675 | |
| 11676 | /** |
| 11677 | * Called by the RecyclerView after the layout is complete. Item animator should record |
| 11678 | * necessary information about the View's final state. |
| 11679 | * <p> |
| 11680 | * The data returned from this method will be passed to the related <code>animate**</code> |
| 11681 | * methods. |
| 11682 | * <p> |
| 11683 | * The default implementation returns an {@link ItemHolderInfo} which holds the bounds of |
| 11684 | * the View. |
| 11685 | * |
| 11686 | * @param state The current State of RecyclerView which includes some useful data about |
| 11687 | * the layout that will be calculated. |
| 11688 | * @param viewHolder The ViewHolder whose information should be recorded. |
| 11689 | * |
| 11690 | * @return An ItemHolderInfo that preserves necessary information about the ViewHolder. |
| 11691 | * This object will be passed back to related <code>animate**</code> methods when |
| 11692 | * RecyclerView decides how items should be animated. |
| 11693 | * |
| 11694 | * @see #recordPreLayoutInformation(State, ViewHolder, int, List) |
| 11695 | * @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11696 | * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11697 | * @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11698 | * @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11699 | */ |
| 11700 | public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state, |
| 11701 | @NonNull ViewHolder viewHolder) { |
| 11702 | return obtainHolderInfo().setFrom(viewHolder); |
| 11703 | } |
| 11704 | |
| 11705 | /** |
| 11706 | * Called by the RecyclerView when a ViewHolder has disappeared from the layout. |
| 11707 | * <p> |
| 11708 | * This means that the View was a child of the LayoutManager when layout started but has |
| 11709 | * been removed by the LayoutManager. It might have been removed from the adapter or simply |
| 11710 | * become invisible due to other factors. You can distinguish these two cases by checking |
| 11711 | * the change flags that were passed to |
| 11712 | * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11713 | * <p> |
| 11714 | * Note that when a ViewHolder both changes and disappears in the same layout pass, the |
| 11715 | * animation callback method which will be called by the RecyclerView depends on the |
| 11716 | * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the |
| 11717 | * LayoutManager's decision whether to layout the changed version of a disappearing |
| 11718 | * ViewHolder or not. RecyclerView will call |
| 11719 | * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11720 | * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator |
| 11721 | * returns {@code false} from |
| 11722 | * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the |
| 11723 | * LayoutManager lays out a new disappearing view that holds the updated information. |
| 11724 | * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. |
| 11725 | * <p> |
| 11726 | * If LayoutManager supports predictive animations, it might provide a target disappear |
| 11727 | * location for the View by laying it out in that location. When that happens, |
| 11728 | * RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the |
| 11729 | * response of that call will be passed to this method as the <code>postLayoutInfo</code>. |
| 11730 | * <p> |
| 11731 | * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation |
| 11732 | * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it |
| 11733 | * decides not to animate the view). |
| 11734 | * |
| 11735 | * @param viewHolder The ViewHolder which should be animated |
| 11736 | * @param preLayoutInfo The information that was returned from |
| 11737 | * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11738 | * @param postLayoutInfo The information that was returned from |
| 11739 | * {@link #recordPostLayoutInformation(State, ViewHolder)}. Might be |
| 11740 | * null if the LayoutManager did not layout the item. |
| 11741 | * |
| 11742 | * @return true if a later call to {@link #runPendingAnimations()} is requested, |
| 11743 | * false otherwise. |
| 11744 | */ |
| 11745 | public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, |
| 11746 | @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo); |
| 11747 | |
| 11748 | /** |
| 11749 | * Called by the RecyclerView when a ViewHolder is added to the layout. |
| 11750 | * <p> |
| 11751 | * In detail, this means that the ViewHolder was <b>not</b> a child when the layout started |
| 11752 | * but has been added by the LayoutManager. It might be newly added to the adapter or |
| 11753 | * simply become visible due to other factors. |
| 11754 | * <p> |
| 11755 | * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation |
| 11756 | * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it |
| 11757 | * decides not to animate the view). |
| 11758 | * |
| 11759 | * @param viewHolder The ViewHolder which should be animated |
| 11760 | * @param preLayoutInfo The information that was returned from |
| 11761 | * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11762 | * Might be null if Item was just added to the adapter or |
| 11763 | * LayoutManager does not support predictive animations or it could |
| 11764 | * not predict that this ViewHolder will become visible. |
| 11765 | * @param postLayoutInfo The information that was returned from {@link |
| 11766 | * #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11767 | * |
| 11768 | * @return true if a later call to {@link #runPendingAnimations()} is requested, |
| 11769 | * false otherwise. |
| 11770 | */ |
| 11771 | public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, |
| 11772 | @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); |
| 11773 | |
| 11774 | /** |
| 11775 | * Called by the RecyclerView when a ViewHolder is present in both before and after the |
| 11776 | * layout and RecyclerView has not received a {@link Adapter#notifyItemChanged(int)} call |
| 11777 | * for it or a {@link Adapter#notifyDataSetChanged()} call. |
| 11778 | * <p> |
| 11779 | * This ViewHolder still represents the same data that it was representing when the layout |
| 11780 | * started but its position / size may be changed by the LayoutManager. |
| 11781 | * <p> |
| 11782 | * If the Item's layout position didn't change, RecyclerView still calls this method because |
| 11783 | * it does not track this information (or does not necessarily know that an animation is |
| 11784 | * not required). Your ItemAnimator should handle this case and if there is nothing to |
| 11785 | * animate, it should call {@link #dispatchAnimationFinished(ViewHolder)} and return |
| 11786 | * <code>false</code>. |
| 11787 | * <p> |
| 11788 | * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} when the animation |
| 11789 | * is complete (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it |
| 11790 | * decides not to animate the view). |
| 11791 | * |
| 11792 | * @param viewHolder The ViewHolder which should be animated |
| 11793 | * @param preLayoutInfo The information that was returned from |
| 11794 | * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11795 | * @param postLayoutInfo The information that was returned from {@link |
| 11796 | * #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11797 | * |
| 11798 | * @return true if a later call to {@link #runPendingAnimations()} is requested, |
| 11799 | * false otherwise. |
| 11800 | */ |
| 11801 | public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder, |
| 11802 | @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); |
| 11803 | |
| 11804 | /** |
| 11805 | * Called by the RecyclerView when an adapter item is present both before and after the |
| 11806 | * layout and RecyclerView has received a {@link Adapter#notifyItemChanged(int)} call |
| 11807 | * for it. This method may also be called when |
| 11808 | * {@link Adapter#notifyDataSetChanged()} is called and adapter has stable ids so that |
| 11809 | * RecyclerView could still rebind views to the same ViewHolders. If viewType changes when |
| 11810 | * {@link Adapter#notifyDataSetChanged()} is called, this method <b>will not</b> be called, |
| 11811 | * instead, {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} will be |
| 11812 | * called for the new ViewHolder and the old one will be recycled. |
| 11813 | * <p> |
| 11814 | * If this method is called due to a {@link Adapter#notifyDataSetChanged()} call, there is |
| 11815 | * a good possibility that item contents didn't really change but it is rebound from the |
| 11816 | * adapter. {@link DefaultItemAnimator} will skip animating the View if its location on the |
| 11817 | * screen didn't change and your animator should handle this case as well and avoid creating |
| 11818 | * unnecessary animations. |
| 11819 | * <p> |
| 11820 | * When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the |
| 11821 | * previous presentation of the item as-is and supply a new ViewHolder for the updated |
| 11822 | * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}. |
| 11823 | * This is useful if you don't know the contents of the Item and would like |
| 11824 | * to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique). |
| 11825 | * <p> |
| 11826 | * When you are writing a custom item animator for your layout, it might be more performant |
| 11827 | * and elegant to re-use the same ViewHolder and animate the content changes manually. |
| 11828 | * <p> |
| 11829 | * When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change. |
| 11830 | * If the Item's view type has changed or ItemAnimator returned <code>false</code> for |
| 11831 | * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the |
| 11832 | * <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances |
| 11833 | * which represent the same Item. In that case, only the new ViewHolder is visible |
| 11834 | * to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations. |
| 11835 | * <p> |
| 11836 | * ItemAnimator must call {@link #dispatchAnimationFinished(ViewHolder)} for each distinct |
| 11837 | * ViewHolder when their animation is complete |
| 11838 | * (or instantly call {@link #dispatchAnimationFinished(ViewHolder)} if it decides not to |
| 11839 | * animate the view). |
| 11840 | * <p> |
| 11841 | * If oldHolder and newHolder are the same instance, you should call |
| 11842 | * {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>. |
| 11843 | * <p> |
| 11844 | * Note that when a ViewHolder both changes and disappears in the same layout pass, the |
| 11845 | * animation callback method which will be called by the RecyclerView depends on the |
| 11846 | * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the |
| 11847 | * LayoutManager's decision whether to layout the changed version of a disappearing |
| 11848 | * ViewHolder or not. RecyclerView will call |
| 11849 | * {@code animateChange} instead of |
| 11850 | * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11851 | * animateDisappearance} if and only if the ItemAnimator returns {@code false} from |
| 11852 | * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the |
| 11853 | * LayoutManager lays out a new disappearing view that holds the updated information. |
| 11854 | * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views. |
| 11855 | * |
| 11856 | * @param oldHolder The ViewHolder before the layout is started, might be the same |
| 11857 | * instance with newHolder. |
| 11858 | * @param newHolder The ViewHolder after the layout is finished, might be the same |
| 11859 | * instance with oldHolder. |
| 11860 | * @param preLayoutInfo The information that was returned from |
| 11861 | * {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11862 | * @param postLayoutInfo The information that was returned from {@link |
| 11863 | * #recordPreLayoutInformation(State, ViewHolder, int, List)}. |
| 11864 | * |
| 11865 | * @return true if a later call to {@link #runPendingAnimations()} is requested, |
| 11866 | * false otherwise. |
| 11867 | */ |
| 11868 | public abstract boolean animateChange(@NonNull ViewHolder oldHolder, |
| 11869 | @NonNull ViewHolder newHolder, |
| 11870 | @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo); |
| 11871 | |
| 11872 | @AdapterChanges static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) { |
| 11873 | int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED); |
| 11874 | if (viewHolder.isInvalid()) { |
| 11875 | return FLAG_INVALIDATED; |
| 11876 | } |
| 11877 | if ((flags & FLAG_INVALIDATED) == 0) { |
| 11878 | final int oldPos = viewHolder.getOldPosition(); |
| 11879 | final int pos = viewHolder.getAdapterPosition(); |
| 11880 | if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) { |
| 11881 | flags |= FLAG_MOVED; |
| 11882 | } |
| 11883 | } |
| 11884 | return flags; |
| 11885 | } |
| 11886 | |
| 11887 | /** |
| 11888 | * Called when there are pending animations waiting to be started. This state |
| 11889 | * is governed by the return values from |
| 11890 | * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11891 | * animateAppearance()}, |
| 11892 | * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11893 | * animateChange()} |
| 11894 | * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11895 | * animatePersistence()}, and |
| 11896 | * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11897 | * animateDisappearance()}, which inform the RecyclerView that the ItemAnimator wants to be |
| 11898 | * called later to start the associated animations. runPendingAnimations() will be scheduled |
| 11899 | * to be run on the next frame. |
| 11900 | */ |
| 11901 | public abstract void runPendingAnimations(); |
| 11902 | |
| 11903 | /** |
| 11904 | * Method called when an animation on a view should be ended immediately. |
| 11905 | * This could happen when other events, like scrolling, occur, so that |
| 11906 | * animating views can be quickly put into their proper end locations. |
| 11907 | * Implementations should ensure that any animations running on the item |
| 11908 | * are canceled and affected properties are set to their end values. |
| 11909 | * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished |
| 11910 | * animation since the animations are effectively done when this method is called. |
| 11911 | * |
| 11912 | * @param item The item for which an animation should be stopped. |
| 11913 | */ |
| 11914 | public abstract void endAnimation(ViewHolder item); |
| 11915 | |
| 11916 | /** |
| 11917 | * Method called when all item animations should be ended immediately. |
| 11918 | * This could happen when other events, like scrolling, occur, so that |
| 11919 | * animating views can be quickly put into their proper end locations. |
| 11920 | * Implementations should ensure that any animations running on any items |
| 11921 | * are canceled and affected properties are set to their end values. |
| 11922 | * Also, {@link #dispatchAnimationFinished(ViewHolder)} should be called for each finished |
| 11923 | * animation since the animations are effectively done when this method is called. |
| 11924 | */ |
| 11925 | public abstract void endAnimations(); |
| 11926 | |
| 11927 | /** |
| 11928 | * Method which returns whether there are any item animations currently running. |
| 11929 | * This method can be used to determine whether to delay other actions until |
| 11930 | * animations end. |
| 11931 | * |
| 11932 | * @return true if there are any item animations currently running, false otherwise. |
| 11933 | */ |
| 11934 | public abstract boolean isRunning(); |
| 11935 | |
| 11936 | /** |
| 11937 | * Method to be called by subclasses when an animation is finished. |
| 11938 | * <p> |
| 11939 | * For each call RecyclerView makes to |
| 11940 | * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11941 | * animateAppearance()}, |
| 11942 | * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11943 | * animatePersistence()}, or |
| 11944 | * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11945 | * animateDisappearance()}, there |
| 11946 | * should |
| 11947 | * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass. |
| 11948 | * <p> |
| 11949 | * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11950 | * animateChange()}, subclass should call this method for both the <code>oldHolder</code> |
| 11951 | * and <code>newHolder</code> (if they are not the same instance). |
| 11952 | * |
| 11953 | * @param viewHolder The ViewHolder whose animation is finished. |
| 11954 | * @see #onAnimationFinished(ViewHolder) |
| 11955 | */ |
| 11956 | public final void dispatchAnimationFinished(ViewHolder viewHolder) { |
| 11957 | onAnimationFinished(viewHolder); |
| 11958 | if (mListener != null) { |
| 11959 | mListener.onAnimationFinished(viewHolder); |
| 11960 | } |
| 11961 | } |
| 11962 | |
| 11963 | /** |
| 11964 | * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the |
| 11965 | * ItemAnimator. |
| 11966 | * |
| 11967 | * @param viewHolder The ViewHolder whose animation is finished. There might still be other |
| 11968 | * animations running on this ViewHolder. |
| 11969 | * @see #dispatchAnimationFinished(ViewHolder) |
| 11970 | */ |
| 11971 | public void onAnimationFinished(ViewHolder viewHolder) { |
| 11972 | } |
| 11973 | |
| 11974 | /** |
| 11975 | * Method to be called by subclasses when an animation is started. |
| 11976 | * <p> |
| 11977 | * For each call RecyclerView makes to |
| 11978 | * {@link #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11979 | * animateAppearance()}, |
| 11980 | * {@link #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11981 | * animatePersistence()}, or |
| 11982 | * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11983 | * animateDisappearance()}, there should be a matching |
| 11984 | * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass. |
| 11985 | * <p> |
| 11986 | * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) |
| 11987 | * animateChange()}, subclass should call this method for both the <code>oldHolder</code> |
| 11988 | * and <code>newHolder</code> (if they are not the same instance). |
| 11989 | * <p> |
| 11990 | * If your ItemAnimator decides not to animate a ViewHolder, it should call |
| 11991 | * {@link #dispatchAnimationFinished(ViewHolder)} <b>without</b> calling |
| 11992 | * {@link #dispatchAnimationStarted(ViewHolder)}. |
| 11993 | * |
| 11994 | * @param viewHolder The ViewHolder whose animation is starting. |
| 11995 | * @see #onAnimationStarted(ViewHolder) |
| 11996 | */ |
| 11997 | public final void dispatchAnimationStarted(ViewHolder viewHolder) { |
| 11998 | onAnimationStarted(viewHolder); |
| 11999 | } |
| 12000 | |
| 12001 | /** |
| 12002 | * Called when a new animation is started on the given ViewHolder. |
| 12003 | * |
| 12004 | * @param viewHolder The ViewHolder which started animating. Note that the ViewHolder |
| 12005 | * might already be animating and this might be another animation. |
| 12006 | * @see #dispatchAnimationStarted(ViewHolder) |
| 12007 | */ |
| 12008 | public void onAnimationStarted(ViewHolder viewHolder) { |
| 12009 | |
| 12010 | } |
| 12011 | |
| 12012 | /** |
| 12013 | * Like {@link #isRunning()}, this method returns whether there are any item |
| 12014 | * animations currently running. Additionally, the listener passed in will be called |
| 12015 | * when there are no item animations running, either immediately (before the method |
| 12016 | * returns) if no animations are currently running, or when the currently running |
| 12017 | * animations are {@link #dispatchAnimationsFinished() finished}. |
| 12018 | * |
| 12019 | * <p>Note that the listener is transient - it is either called immediately and not |
| 12020 | * stored at all, or stored only until it is called when running animations |
| 12021 | * are finished sometime later.</p> |
| 12022 | * |
| 12023 | * @param listener A listener to be called immediately if no animations are running |
| 12024 | * or later when currently-running animations have finished. A null listener is |
| 12025 | * equivalent to calling {@link #isRunning()}. |
| 12026 | * @return true if there are any item animations currently running, false otherwise. |
| 12027 | */ |
| 12028 | public final boolean isRunning(ItemAnimatorFinishedListener listener) { |
| 12029 | boolean running = isRunning(); |
| 12030 | if (listener != null) { |
| 12031 | if (!running) { |
| 12032 | listener.onAnimationsFinished(); |
| 12033 | } else { |
| 12034 | mFinishedListeners.add(listener); |
| 12035 | } |
| 12036 | } |
| 12037 | return running; |
| 12038 | } |
| 12039 | |
| 12040 | /** |
| 12041 | * When an item is changed, ItemAnimator can decide whether it wants to re-use |
| 12042 | * the same ViewHolder for animations or RecyclerView should create a copy of the |
| 12043 | * item and ItemAnimator will use both to run the animation (e.g. cross-fade). |
| 12044 | * <p> |
| 12045 | * Note that this method will only be called if the {@link ViewHolder} still has the same |
| 12046 | * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive |
| 12047 | * both {@link ViewHolder}s in the |
| 12048 | * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. |
| 12049 | * <p> |
| 12050 | * If your application is using change payloads, you can override |
| 12051 | * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads. |
| 12052 | * |
| 12053 | * @param viewHolder The ViewHolder which represents the changed item's old content. |
| 12054 | * |
| 12055 | * @return True if RecyclerView should just rebind to the same ViewHolder or false if |
| 12056 | * RecyclerView should create a new ViewHolder and pass this ViewHolder to the |
| 12057 | * ItemAnimator to animate. Default implementation returns <code>true</code>. |
| 12058 | * |
| 12059 | * @see #canReuseUpdatedViewHolder(ViewHolder, List) |
| 12060 | */ |
| 12061 | public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) { |
| 12062 | return true; |
| 12063 | } |
| 12064 | |
| 12065 | /** |
| 12066 | * When an item is changed, ItemAnimator can decide whether it wants to re-use |
| 12067 | * the same ViewHolder for animations or RecyclerView should create a copy of the |
| 12068 | * item and ItemAnimator will use both to run the animation (e.g. cross-fade). |
| 12069 | * <p> |
| 12070 | * Note that this method will only be called if the {@link ViewHolder} still has the same |
| 12071 | * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive |
| 12072 | * both {@link ViewHolder}s in the |
| 12073 | * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method. |
| 12074 | * |
| 12075 | * @param viewHolder The ViewHolder which represents the changed item's old content. |
| 12076 | * @param payloads A non-null list of merged payloads that were sent with change |
| 12077 | * notifications. Can be empty if the adapter is invalidated via |
| 12078 | * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of |
| 12079 | * payloads will be passed into |
| 12080 | * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)} |
| 12081 | * method <b>if</b> this method returns <code>true</code>. |
| 12082 | * |
| 12083 | * @return True if RecyclerView should just rebind to the same ViewHolder or false if |
| 12084 | * RecyclerView should create a new ViewHolder and pass this ViewHolder to the |
| 12085 | * ItemAnimator to animate. Default implementation calls |
| 12086 | * {@link #canReuseUpdatedViewHolder(ViewHolder)}. |
| 12087 | * |
| 12088 | * @see #canReuseUpdatedViewHolder(ViewHolder) |
| 12089 | */ |
| 12090 | public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, |
| 12091 | @NonNull List<Object> payloads) { |
| 12092 | return canReuseUpdatedViewHolder(viewHolder); |
| 12093 | } |
| 12094 | |
| 12095 | /** |
| 12096 | * This method should be called by ItemAnimator implementations to notify |
| 12097 | * any listeners that all pending and active item animations are finished. |
| 12098 | */ |
| 12099 | public final void dispatchAnimationsFinished() { |
| 12100 | final int count = mFinishedListeners.size(); |
| 12101 | for (int i = 0; i < count; ++i) { |
| 12102 | mFinishedListeners.get(i).onAnimationsFinished(); |
| 12103 | } |
| 12104 | mFinishedListeners.clear(); |
| 12105 | } |
| 12106 | |
| 12107 | /** |
| 12108 | * Returns a new {@link ItemHolderInfo} which will be used to store information about the |
| 12109 | * ViewHolder. This information will later be passed into <code>animate**</code> methods. |
| 12110 | * <p> |
| 12111 | * You can override this method if you want to extend {@link ItemHolderInfo} and provide |
| 12112 | * your own instances. |
| 12113 | * |
| 12114 | * @return A new {@link ItemHolderInfo}. |
| 12115 | */ |
| 12116 | public ItemHolderInfo obtainHolderInfo() { |
| 12117 | return new ItemHolderInfo(); |
| 12118 | } |
| 12119 | |
| 12120 | /** |
| 12121 | * The interface to be implemented by listeners to animation events from this |
| 12122 | * ItemAnimator. This is used internally and is not intended for developers to |
| 12123 | * create directly. |
| 12124 | */ |
| 12125 | interface ItemAnimatorListener { |
| 12126 | void onAnimationFinished(ViewHolder item); |
| 12127 | } |
| 12128 | |
| 12129 | /** |
| 12130 | * This interface is used to inform listeners when all pending or running animations |
| 12131 | * in an ItemAnimator are finished. This can be used, for example, to delay an action |
| 12132 | * in a data set until currently-running animations are complete. |
| 12133 | * |
| 12134 | * @see #isRunning(ItemAnimatorFinishedListener) |
| 12135 | */ |
| 12136 | public interface ItemAnimatorFinishedListener { |
| 12137 | /** |
| 12138 | * Notifies when all pending or running animations in an ItemAnimator are finished. |
| 12139 | */ |
| 12140 | void onAnimationsFinished(); |
| 12141 | } |
| 12142 | |
| 12143 | /** |
| 12144 | * A simple data structure that holds information about an item's bounds. |
| 12145 | * This information is used in calculating item animations. Default implementation of |
| 12146 | * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)} and |
| 12147 | * {@link #recordPostLayoutInformation(RecyclerView.State, ViewHolder)} returns this data |
| 12148 | * structure. You can extend this class if you would like to keep more information about |
| 12149 | * the Views. |
| 12150 | * <p> |
| 12151 | * If you want to provide your own implementation but still use `super` methods to record |
| 12152 | * basic information, you can override {@link #obtainHolderInfo()} to provide your own |
| 12153 | * instances. |
| 12154 | */ |
| 12155 | public static class ItemHolderInfo { |
| 12156 | |
| 12157 | /** |
| 12158 | * The left edge of the View (excluding decorations) |
| 12159 | */ |
| 12160 | public int left; |
| 12161 | |
| 12162 | /** |
| 12163 | * The top edge of the View (excluding decorations) |
| 12164 | */ |
| 12165 | public int top; |
| 12166 | |
| 12167 | /** |
| 12168 | * The right edge of the View (excluding decorations) |
| 12169 | */ |
| 12170 | public int right; |
| 12171 | |
| 12172 | /** |
| 12173 | * The bottom edge of the View (excluding decorations) |
| 12174 | */ |
| 12175 | public int bottom; |
| 12176 | |
| 12177 | /** |
| 12178 | * The change flags that were passed to |
| 12179 | * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, List)}. |
| 12180 | */ |
| 12181 | @AdapterChanges |
| 12182 | public int changeFlags; |
| 12183 | |
| 12184 | public ItemHolderInfo() { |
| 12185 | } |
| 12186 | |
| 12187 | /** |
| 12188 | * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from |
| 12189 | * the given ViewHolder. Clears all {@link #changeFlags}. |
| 12190 | * |
| 12191 | * @param holder The ViewHolder whose bounds should be copied. |
| 12192 | * @return This {@link ItemHolderInfo} |
| 12193 | */ |
| 12194 | public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder) { |
| 12195 | return setFrom(holder, 0); |
| 12196 | } |
| 12197 | |
| 12198 | /** |
| 12199 | * Sets the {@link #left}, {@link #top}, {@link #right} and {@link #bottom} values from |
| 12200 | * the given ViewHolder and sets the {@link #changeFlags} to the given flags parameter. |
| 12201 | * |
| 12202 | * @param holder The ViewHolder whose bounds should be copied. |
| 12203 | * @param flags The adapter change flags that were passed into |
| 12204 | * {@link #recordPreLayoutInformation(RecyclerView.State, ViewHolder, int, |
| 12205 | * List)}. |
| 12206 | * @return This {@link ItemHolderInfo} |
| 12207 | */ |
| 12208 | public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder, |
| 12209 | @AdapterChanges int flags) { |
| 12210 | final View view = holder.itemView; |
| 12211 | this.left = view.getLeft(); |
| 12212 | this.top = view.getTop(); |
| 12213 | this.right = view.getRight(); |
| 12214 | this.bottom = view.getBottom(); |
| 12215 | return this; |
| 12216 | } |
| 12217 | } |
| 12218 | } |
| 12219 | |
| 12220 | @Override |
| 12221 | protected int getChildDrawingOrder(int childCount, int i) { |
| 12222 | if (mChildDrawingOrderCallback == null) { |
| 12223 | return super.getChildDrawingOrder(childCount, i); |
| 12224 | } else { |
| 12225 | return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i); |
| 12226 | } |
| 12227 | } |
| 12228 | |
| 12229 | /** |
| 12230 | * A callback interface that can be used to alter the drawing order of RecyclerView children. |
| 12231 | * <p> |
| 12232 | * It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case |
| 12233 | * that applies to that method also applies to this callback. For example, changing the drawing |
| 12234 | * order of two views will not have any effect if their elevation values are different since |
| 12235 | * elevation overrides the result of this callback. |
| 12236 | */ |
| 12237 | public interface ChildDrawingOrderCallback { |
| 12238 | /** |
| 12239 | * Returns the index of the child to draw for this iteration. Override this |
| 12240 | * if you want to change the drawing order of children. By default, it |
| 12241 | * returns i. |
| 12242 | * |
| 12243 | * @param i The current iteration. |
| 12244 | * @return The index of the child to draw this iteration. |
| 12245 | * |
| 12246 | * @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback) |
| 12247 | */ |
| 12248 | int onGetChildDrawingOrder(int childCount, int i); |
| 12249 | } |
| 12250 | } |