androidx.recyclerview.widget.RecyclerView Maven / Gradle / Ivy
package androidx.recyclerview.widget;
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.animation.LayoutTransition;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.Observable;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
import android.view.FocusFinder;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.widget.EdgeEffect;
import android.widget.LinearLayout;
import android.widget.OverScroller;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A flexible view for providing a limited window into a large data set.
*
* Glossary of terms:
*
*
* - Adapter: A subclass of {@link Adapter} responsible for providing views
* that represent items in a data set.
* - Position: The position of a data item within an Adapter.
* - Index: The index of an attached child view as used in a call to
* {@link ViewGroup#getChildAt}. Contrast with Position.
* - Binding: The process of preparing a child view to display data corresponding
* to a position within the adapter.
* - Recycle (view): A view previously used to display data for a specific adapter
* position may be placed in a cache for later reuse to display the same type of data again
* later. This can drastically improve performance by skipping initial layout inflation
* or construction.
* - Scrap (view): A child view that has entered into a temporarily detached
* state during layout. Scrap views may be reused without becoming fully detached
* from the parent RecyclerView, either unmodified if no rebinding is required or modified
* by the adapter if the view was considered dirty.
* - Dirty (view): A child view that must be rebound by the adapter before
* being displayed.
*
*
* Positions in RecyclerView:
*
* RecyclerView introduces an additional level of abstraction between the {@link Adapter} and
* {@link LayoutManager} to be able to detect data set changes in batches during a layout
* calculation. This saves LayoutManager from tracking adapter changes to calculate animations.
* It also helps with performance because all view bindings happen at the same time and unnecessary
* bindings are avoided.
*
* For this reason, there are two types of position
related methods in RecyclerView:
*
* - layout position: Position of an item in the latest layout calculation. This is the
* position from the LayoutManager's perspective.
* - adapter position: Position of an item in the adapter. This is the position from
* the Adapter's perspective.
*
*
* These two positions are the same except the time between dispatching adapter.notify*
*
events and calculating the updated layout.
*
* Methods that return or receive *LayoutPosition*
use position as of the latest
* layout calculation (e.g. {@link ViewHolder#getLayoutPosition()},
* {@link #findViewHolderForLayoutPosition(int)}). These positions include all changes until the
* last layout calculation. You can rely on these positions to be consistent with what user is
* currently seeing on the screen. For example, if you have a list of items on the screen and user
* asks for the 5th element, you should use these methods as they'll match what user
* is seeing.
*
* The other set of position related methods are in the form of
* *AdapterPosition*
. (e.g. {@link ViewHolder#getAdapterPosition()},
* {@link #findViewHolderForAdapterPosition(int)}) You should use these methods when you need to
* work with up-to-date adapter positions even if they may not have been reflected to layout yet.
* For example, if you want to access the item in the adapter on a ViewHolder click, you should use
* {@link ViewHolder#getAdapterPosition()}. Beware that these methods may not be able to calculate
* adapter positions if {@link Adapter#notifyDataSetChanged()} has been called and new layout has
* not yet been calculated. For this reasons, you should carefully handle {@link #NO_POSITION} or
* null
results from these methods.
*
* When writing a {@link LayoutManager} you almost always want to use layout positions whereas when
* writing an {@link Adapter}, you probably want to use adapter positions.
*
*
Presenting Dynamic Data
* To display updatable data in a RecyclerView, your adapter needs to signal inserts, moves, and
* deletions to RecyclerView. You can build this yourself by manually calling
* {@code adapter.notify*} methods when content changes, or you can use one of the easier solutions
* RecyclerView provides:
*
*
List diffing with DiffUtil
* If your RecyclerView is displaying a list that is re-fetched from scratch for each update (e.g.
* from the network, or from a database), {@link DiffUtil} can calculate the difference between
* versions of the list. {@code DiffUtil} takes both lists as input and computes the difference,
* which can be passed to RecyclerView to trigger minimal animations and updates to keep your UI
* performant, and animations meaningful. This approach requires that each list is represented in
* memory with immutable content, and relies on receiving updates as new instances of lists. This
* approach is also ideal if your UI layer doesn't implement sorting, it just presents the data in
* the order it's given.
*
* The best part of this approach is that it extends to any arbitrary changes - item updates,
* moves, addition and removal can all be computed and handled the same way. Though you do have
* to keep two copies of the list in memory while diffing, and must avoid mutating them, it's
* possible to share unmodified elements between list versions.
*
* There are three primary ways to do this for RecyclerView. We recommend you start with
* {@link ListAdapter}, the higher-level API that builds in {@link List} diffing on a background
* thread, with minimal code. {@link AsyncListDiffer} also provides this behavior, but without
* defining an Adapter to subclass. If you want more control, {@link DiffUtil} is the lower-level
* API you can use to compute the diffs yourself. Each approach allows you to specify how diffs
* should be computed based on item data.
*
*
List mutation with SortedList
* If your RecyclerView receives updates incrementally, e.g. item X is inserted, or item Y is
* removed, you can use {@link SortedList} to manage your list. You define how to order items,
* and it will automatically trigger update signals that RecyclerView can use. SortedList works
* if you only need to handle insert and remove events, and has the benefit that you only ever
* need to have a single copy of the list in memory. It can also compute differences with
* {@link SortedList#replaceAll(Object[])}, but this method is more limited than the list diffing
* behavior above.
*
*
Paging Library
* The Paging
* library extends the diff-based approach to additionally support paged loading. It provides
* the {@link androidx.paging.PagedList} class that operates as a self-loading list, provided a
* source of data like a database, or paginated network API. It provides convenient list diffing
* support out of the box, similar to {@code ListAdapter} and {@code AsyncListDiffer}. For more
* information about the Paging library, see the
* library
* documentation.
*
* {@link androidx.recyclerview.R.attr#layoutManager}
*/
public class RecyclerView extends ViewGroup {
static final String TAG = "RecyclerView";
static final boolean DEBUG = false;
static final boolean VERBOSE_TRACING = false;
private static final int[] NESTED_SCROLLING_ATTRS =
{16843830 /* android.R.attr.nestedScrollingEnabled */};
private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding};
/**
* On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if
* a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by
* setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler
* recursively traverses itemView and invalidates display list for each ViewGroup that matches
* this criteria.
*/
static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18
|| Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20;
/**
* On M+, an unspecified measure spec may include a hint which we can use. On older platforms,
* this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to
* 0 when mode is unspecified.
*/
static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23;
static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16;
/**
* On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to
* RenderThread but before the next frame begins. We schedule prefetch work in this window.
*/
static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;
/**
* FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction.
* We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT.
*/
private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15;
/**
* on API 15-, a focused child can still be considered a focused child of RV even after
* it's being removed or its focusable flag is set to false. This is because when this focused
* child is detached, the reference to this child is not removed in clearFocus. API 16 and above
* properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus
* to request focus on a new child, which will clear the focus on the old (detached) child as a
* side-effect.
*/
private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15;
public @interface Orientation {}
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
public static final int VERTICAL = LinearLayout.VERTICAL;
static final int DEFAULT_ORIENTATION = VERTICAL;
public static final int NO_POSITION = -1;
public static final long NO_ID = -1;
public static final int INVALID_TYPE = -1;
/**
* Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
* that the RecyclerView should use the standard touch slop for smooth,
* continuous scrolling.
*/
public static final int TOUCH_SLOP_DEFAULT = 0;
/**
* Constant for use with {@link #setScrollingTouchSlop(int)}. Indicates
* that the RecyclerView should use the standard touch slop for scrolling
* widgets that snap to a page or other coarse-grained barrier.
*/
public static final int TOUCH_SLOP_PAGING = 1;
static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
static final int MAX_SCROLL_DURATION = 2000;
/**
* RecyclerView is calculating a scroll.
* If there are too many of these in Systrace, some Views inside RecyclerView might be causing
* it. Try to avoid using EditText, focusable views or handle them with care.
*/
static final String TRACE_SCROLL_TAG = "RV Scroll";
/**
* OnLayout has been called by the View system.
* If this shows up too many times in Systrace, make sure the children of RecyclerView do not
* update themselves directly. This will cause a full re-layout but when it happens via the
* Adapter notifyItemChanged, RecyclerView can avoid full layout calculation.
*/
private static final String TRACE_ON_LAYOUT_TAG = "RV OnLayout";
/**
* NotifyDataSetChanged or equal has been called.
* If this is taking a long time, try sending granular notify adapter changes instead of just
* calling notifyDataSetChanged or setAdapter / swapAdapter. Adding stable ids to your adapter
* might help.
*/
private static final String TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG = "RV FullInvalidate";
/**
* RecyclerView is doing a layout for partial adapter updates (we know what has changed)
* If this is taking a long time, you may have dispatched too many Adapter updates causing too
* many Views being rebind. Make sure all are necessary and also prefer using notify*Range
* methods.
*/
private static final String TRACE_HANDLE_ADAPTER_UPDATES_TAG = "RV PartialInvalidate";
/**
* RecyclerView is rebinding a View.
* If this is taking a lot of time, consider optimizing your layout or make sure you are not
* doing extra operations in onBindViewHolder call.
*/
static final String TRACE_BIND_VIEW_TAG = "RV OnBindView";
/**
* RecyclerView is attempting to pre-populate off screen views.
*/
static final String TRACE_PREFETCH_TAG = "RV Prefetch";
/**
* RecyclerView is attempting to pre-populate off screen itemviews within an off screen
* RecyclerView.
*/
static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch";
/**
* RecyclerView is creating a new View.
* If too many of these present in Systrace:
* - There might be a problem in Recycling (e.g. custom Animations that set transient state and
* prevent recycling or ItemAnimator not implementing the contract properly. ({@link
* > Adapter#onFailedToRecycleView(ViewHolder)})
*
* - There might be too many item view types.
* > Try merging them
*
* - There might be too many itemChange animations and not enough space in RecyclerPool.
* >Try increasing your pool size and item cache size.
*/
static final String TRACE_CREATE_VIEW_TAG = "RV CreateView";
private static final Class>[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE =
new Class[]{Context.class, AttributeSet.class, int.class, int.class};
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
final Recycler mRecycler = new Recycler();
/**
* Prior to L, there is no way to query this variable which is why we override the setter and
* track it here.
*/
boolean mClipToPadding;
/**
* Note: this Runnable is only ever posted if:
* 1) We've been through first layout
* 2) We know we have a fixed size (mHasFixedSize)
* 3) We're attached
*/
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (!mIsAttached) {
requestLayout();
// if we are not attached yet, mark us as requiring layout and skip
return;
}
if (mLayoutSuppressed) {
mLayoutWasDefered = true;
return; //we'll process updates when ice age ends.
}
consumePendingUpdateOperations();
}
};
final Rect mTempRect = new Rect();
private final Rect mTempRect2 = new Rect();
final RectF mTempRectF = new RectF();
Adapter mAdapter;
RecyclerListener mRecyclerListener;
final ArrayList mItemDecorations = new ArrayList<>();
private final ArrayList mOnItemTouchListeners =
new ArrayList<>();
private OnItemTouchListener mInterceptingOnItemTouchListener;
boolean mIsAttached;
boolean mHasFixedSize;
boolean mEnableFastScroller;
boolean mFirstLayoutComplete;
/**
* The current depth of nested calls to {@link #startInterceptRequestLayout()} (number of
* calls to {@link #startInterceptRequestLayout()} - number of calls to
* {@link #stopInterceptRequestLayout(boolean)} . This is used to signal whether we
* should defer layout operations caused by layout requests from children of
* {@link RecyclerView}.
*/
private int mInterceptRequestLayoutDepth = 0;
/**
* True if a call to requestLayout was intercepted and prevented from executing like normal and
* we plan on continuing with normal execution later.
*/
boolean mLayoutWasDefered;
boolean mLayoutSuppressed;
private boolean mIgnoreMotionEventTillDown;
// binary OR of change events that were eaten during a layout or scroll.
private int mEatenAccessibilityChangeFlags;
boolean mAdapterUpdateDuringMeasure;
private final AccessibilityManager mAccessibilityManager;
private List mOnChildAttachStateListeners;
/**
* True after an event occurs that signals that the entire data set has changed. In that case,
* we cannot run any animations since we don't know what happened until layout.
*
* Attached items are invalid until next layout, at which point layout will animate/replace
* items as necessary, building up content from the (effectively) new adapter from scratch.
*
* Cached items must be discarded when setting this to true, so that the cache may be freely
* used by prefetching until the next layout occurs.
*
* @see #processDataSetCompletelyChanged(boolean)
*/
boolean mDataSetHasChangedAfterLayout = false;
/**
* True after the data set has completely changed and
* {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent
* measure/layout.
*
* @see #processDataSetCompletelyChanged(boolean)
*/
boolean mDispatchItemsChangedEvent = false;
/**
* This variable is incremented during a dispatchLayout and/or scroll.
* Some methods should not be called during these periods (e.g. adapter data change).
* Doing so will create hard to find bugs so we better check it and throw an exception.
*
* @see #assertInLayoutOrScroll(String)
* @see #assertNotInLayoutOrScroll(String)
*/
private int mLayoutOrScrollCounter = 0;
/**
* Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception
* (for API compatibility).
*
* It is a bad practice for a developer to update the data in a scroll callback since it is
* potentially called during a layout.
*/
private int mDispatchScrollCounter = 0;
private EdgeEffect mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
private static final int INVALID_POINTER = -1;
/**
* The RecyclerView is not currently scrolling.
* @see #getScrollState()
*/
public static final int SCROLL_STATE_IDLE = 0;
/**
* The RecyclerView is currently being dragged by outside input such as user touch input.
* @see #getScrollState()
*/
public static final int SCROLL_STATE_DRAGGING = 1;
/**
* The RecyclerView is currently animating to a final position while not under
* outside control.
* @see #getScrollState()
*/
public static final int SCROLL_STATE_SETTLING = 2;
static final long FOREVER_NS = Long.MAX_VALUE;
// Touch/scrolling handling
private int mScrollState = SCROLL_STATE_IDLE;
private int mScrollPointerId = INVALID_POINTER;
private VelocityTracker mVelocityTracker;
private int mInitialTouchX;
private int mInitialTouchY;
private int mLastTouchX;
private int mLastTouchY;
private int mTouchSlop;
private OnFlingListener mOnFlingListener;
private final int mMinFlingVelocity;
private final int mMaxFlingVelocity;
// This value is used when handling rotary encoder generic motion events.
private float mScaledHorizontalScrollFactor = Float.MIN_VALUE;
private float mScaledVerticalScrollFactor = Float.MIN_VALUE;
private boolean mPreserveFocusAfterLayout = true;
final State mState = new State();
private OnScrollListener mScrollListener;
private List mScrollListeners;
// For use in item animations
boolean mItemsAddedOrRemoved = false;
boolean mItemsChanged = false;
boolean mPostedAnimatorRunner = false;
private ChildDrawingOrderCallback mChildDrawingOrderCallback;
// simple array to keep min and max child position during a layout calculation
// preserved not to create a new one in each layout pass
private final int[] mMinMaxLayoutPositions = new int[2];
private final int[] mScrollOffset = new int[2];
private final int[] mNestedOffsets = new int[2];
// Reusable int array to be passed to method calls that mutate it in order to "return" two ints.
final int[] mReusableIntPair = new int[2];
static final Interpolator sQuinticInterpolator = new Interpolator() {
@Override
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
public RecyclerView( Context context) {
this(context, null);
}
public RecyclerView( Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
mClipToPadding = a.getBoolean(0, true);
a.recycle();
} else {
mClipToPadding = true;
}
setScrollContainer(true);
setFocusableInTouchMode(true);
final ViewConfiguration vc = ViewConfiguration.get(context);
mTouchSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
initAdapterManager();
initChildrenHelper();
initAutofill();
// If not explicitly specified this view is important for accessibility.
mAccessibilityManager = (AccessibilityManager) getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE);
// Create the layoutManager if specified.
boolean nestedScrollingEnabled = true;
// Re-set whether nested scrolling is enabled so that it is set on all API levels
setNestedScrollingEnabled(nestedScrollingEnabled);
}
/**
* Label appended to all public exception strings, used to help find which RV in an app is
* hitting an exception.
*/
String exceptionLabel() {
return " " + super.toString()
+ ", adapter:" + mAdapter
+ ", context:" + getContext();
}
/**
* If not explicitly specified, this view and its children don't support autofill.
*
* This is done because autofill's means of uniquely identifying views doesn't work out of the
* box with View recycling.
*/
@SuppressLint("InlinedApi")
private void initAutofill() {
}
/**
* Instantiate and set a LayoutManager, if specified in the attributes.
*/
private void createLayoutManager(Context context, String className, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
if (className != null) {
className = className.trim();
if (!className.isEmpty()) {
className = getFullClassName(context, className);
}
}
}
private String getFullClassName(Context context, String className) {
if (className.charAt(0) == '.') {
return context.getPackageName() + className;
}
if (className.contains(".")) {
return className;
}
return RecyclerView.class.getPackage().getName() + '.' + className;
}
private void initChildrenHelper() {
}
void initAdapterManager() {
}
/**
* RecyclerView can perform several optimizations if it can know in advance that RecyclerView's
* size is not affected by the adapter contents. RecyclerView can still change its size based
* on other factors (e.g. its parent's size) but this size calculation cannot depend on the
* size of its children or contents of its adapter (except the number of items in the adapter).
*
* If your use of RecyclerView falls into this category, set this to {@code true}. It will allow
* RecyclerView to avoid invalidating the whole layout when its adapter contents change.
*
* @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
*/
public void setHasFixedSize(boolean hasFixedSize) {
mHasFixedSize = hasFixedSize;
}
/**
* @return true if the app has specified that changes in adapter content cannot change
* the size of the RecyclerView itself.
*/
public boolean hasFixedSize() {
return mHasFixedSize;
}
@Override
public void setClipToPadding(boolean clipToPadding) {
if (clipToPadding != mClipToPadding) {
invalidateGlows();
}
mClipToPadding = clipToPadding;
super.setClipToPadding(clipToPadding);
if (mFirstLayoutComplete) {
requestLayout();
}
}
/**
* Returns whether this RecyclerView will clip its children to its padding, and resize (but
* not clip) any EdgeEffect to the padded region, if padding is present.
*
* By default, children are clipped to the padding of their parent
* RecyclerView. This clipping behavior is only enabled if padding is non-zero.
*
* @return true if this RecyclerView clips children to its padding and resizes (but doesn't
* clip) any EdgeEffect to the padded region, false otherwise.
*
* @attr name android:clipToPadding
*/
@Override
public boolean getClipToPadding() {
return mClipToPadding;
}
/**
* Configure the scrolling touch slop for a specific use case.
*
* Set up the RecyclerView's scrolling motion threshold based on common usages.
* Valid arguments are {@link #TOUCH_SLOP_DEFAULT} and {@link #TOUCH_SLOP_PAGING}.
*
* @param slopConstant One of the TOUCH_SLOP_
constants representing
* the intended usage of this RecyclerView
*/
public void setScrollingTouchSlop(int slopConstant) {
final ViewConfiguration vc = ViewConfiguration.get(getContext());
switch (slopConstant) {
default:
Log.w(TAG, "setScrollingTouchSlop(): bad argument constant "
+ slopConstant + "; using default value");
// fall-through
case TOUCH_SLOP_DEFAULT:
mTouchSlop = vc.getScaledTouchSlop();
break;
case TOUCH_SLOP_PAGING:
mTouchSlop = vc.getScaledPagingTouchSlop();
break;
}
}
/**
* Swaps the current adapter with the provided one. It is similar to
* {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same
* {@link ViewHolder} and does not clear the RecycledViewPool.
*
* Note that it still calls onAdapterChanged callbacks.
*
* @param adapter The new adapter to set, or null to set no adapter.
* @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing
* Views. If adapters have stable ids and/or you want to
* animate the disappearing views, you may prefer to set
* this to false.
* @see #setAdapter(Adapter)
*/
public void swapAdapter( Adapter adapter, boolean removeAndRecycleExistingViews) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
processDataSetCompletelyChanged(true);
requestLayout();
}
/**
* Set a new adapter to provide child views on demand.
*
* When adapter is changed, all existing views are recycled back to the pool. If the pool has
* only one adapter, it will be cleared.
*
* @param adapter The new adapter to set, or null to set no adapter.
* @see #swapAdapter(Adapter, boolean)
*/
public void setAdapter( Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
requestLayout();
}
/**
* Removes and recycles all views - both those currently attached, and those in the Recycler.
*/
void removeAndRecycleViews() {
// end all running animations
// Since animations are ended, mLayout.children should be equal to
// recyclerView.children. This may not be true if item animator's end does not work as
// expected. (e.g. not release children instantly). It is safer to use mLayout's child
// count.
// we should clear it here before adapters are swapped to ensure correct callbacks.
mRecycler.clear();
}
/**
* Replaces the current adapter with the new one and triggers listeners.
* @param adapter The new adapter
* @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
* item types with the current adapter (helps us avoid cache
* invalidation).
* @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If
* compatibleWithPrevious is false, this parameter is ignored.
*/
private void setAdapterInternal( Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
}
/**
* Retrieves the previously set adapter or null if no adapter is set.
*
* @return The previously set adapter
* @see #setAdapter(Adapter)
*/
public Adapter getAdapter() {
return mAdapter;
}
/**
* Register a listener that will be notified whenever a child view is recycled.
*
*
This listener will be called when a LayoutManager or the RecyclerView decides
* that a child view is no longer needed. If an application associates expensive
* or heavyweight data with item views, this may be a good place to release
* or free those resources.
*
* @param listener Listener to register, or null to clear
*/
public void setRecyclerListener( RecyclerListener listener) {
mRecyclerListener = listener;
}
/**
* Return the offset of the RecyclerView's text baseline from the its top
* boundary. If the LayoutManager of this RecyclerView does not support baseline alignment,
* this method returns -1.
*
* @return the offset of the baseline within the RecyclerView's bounds or -1
* if baseline alignment is not supported
*/
@Override
public int getBaseline() {
return 0;
}
/**
* Register a listener that will be notified whenever a child view is attached to or detached
* from RecyclerView.
*
* This listener will be called when a LayoutManager or the RecyclerView decides
* that a child view is no longer needed. If an application associates expensive
* or heavyweight data with item views, this may be a good place to release
* or free those resources.
*
* @param listener Listener to register
*/
public void addOnChildAttachStateChangeListener(
OnChildAttachStateChangeListener listener) {
if (mOnChildAttachStateListeners == null) {
mOnChildAttachStateListeners = new ArrayList<>();
}
mOnChildAttachStateListeners.add(listener);
}
/**
* Removes the provided listener from child attached state listeners list.
*
* @param listener Listener to unregister
*/
public void removeOnChildAttachStateChangeListener(
OnChildAttachStateChangeListener listener) {
if (mOnChildAttachStateListeners == null) {
return;
}
mOnChildAttachStateListeners.remove(listener);
}
/**
* Removes all listeners that were added via
* {@link #addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener)}.
*/
public void clearOnChildAttachStateChangeListeners() {
if (mOnChildAttachStateListeners != null) {
mOnChildAttachStateListeners.clear();
}
}
/**
* Set a {@link OnFlingListener} for this {@link RecyclerView}.
*
* If the {@link OnFlingListener} is set then it will receive
* calls to {@link #fling(int,int)} and will be able to intercept them.
*
* @param onFlingListener The {@link OnFlingListener} instance.
*/
public void setOnFlingListener(OnFlingListener onFlingListener) {
mOnFlingListener = onFlingListener;
}
/**
* Get the current {@link OnFlingListener} from this {@link RecyclerView}.
*
* @return The {@link OnFlingListener} instance currently set (can be null).
*/
public OnFlingListener getOnFlingListener() {
return mOnFlingListener;
}
@Override
protected Parcelable onSaveInstanceState() {
return null;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
}
/**
* Override to prevent freezing of any views created by the adapter.
*/
@Override
protected void dispatchSaveInstanceState(SparseArray container) {
dispatchFreezeSelfOnly(container);
}
/**
* Override to prevent thawing of any views created by the adapter.
*/
@Override
protected void dispatchRestoreInstanceState(SparseArray container) {
dispatchThawSelfOnly(container);
}
/**
* Adds a view to the animatingViews list.
* mAnimatingViews holds the child views that are currently being kept around
* purely for the purpose of being animated out of view. They are drawn as a regular
* part of the child list of the RecyclerView, but they are invisible to the LayoutManager
* as they are managed separately from the regular child views.
* @param viewHolder The ViewHolder to be removed
*/
private void addAnimatingView(ViewHolder viewHolder) {
}
/**
* Removes a view from the animatingViews list.
* @param view The view to be removed
* @see #addAnimatingView(RecyclerView.ViewHolder)
* @return true if an animating view is removed
*/
boolean removeAnimatingView(View view) {
return false;
}
/**
* Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
* if no pool is set for this view a new one will be created. See
* {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
*
* @return The pool used to store recycled item views for reuse.
* @see #setRecycledViewPool(RecycledViewPool)
*/
public RecycledViewPool getRecycledViewPool() {
return null;
}
/**
* Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
* This can be useful if you have multiple RecyclerViews with adapters that use the same
* view types, for example if you have several data sets with the same kinds of item views
* displayed by a {@link androidx.viewpager.widget.ViewPager}.
*
* @param pool Pool to set. If this parameter is null a new pool will be created and used.
*/
public void setRecycledViewPool( RecycledViewPool pool) {
}
/**
* Sets a new {@link ViewCacheExtension} to be used by the Recycler.
*
* @param extension ViewCacheExtension to be used or null if you want to clear the existing one.
*
* @see ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)
*/
public void setViewCacheExtension( ViewCacheExtension extension) {
}
/**
* Set the number of offscreen views to retain before adding them to the potentially shared
* {@link #getRecycledViewPool() recycled view pool}.
*
* The offscreen view cache stays aware of changes in the attached adapter, allowing
* a LayoutManager to reuse those views unmodified without needing to return to the adapter
* to rebind them.
*
* @param size Number of views to cache offscreen before returning them to the general
* recycled view pool
*/
public void setItemViewCacheSize(int size) {
}
/**
* Return the current scrolling state of the RecyclerView.
*
* @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or
* {@link #SCROLL_STATE_SETTLING}
*/
public int getScrollState() {
return mScrollState;
}
/**
* Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
* affect both measurement and drawing of individual item views.
*
* Item decorations are ordered. Decorations placed earlier in the list will
* be run/queried/drawn first for their effects on item views. Padding added to views
* will be nested; a padding added by an earlier decoration will mean further
* item decorations in the list will be asked to draw/pad within the previous decoration's
* given area.
*
* @param decor Decoration to add
* @param index Position in the decoration chain to insert this decoration at. If this value
* is negative the decoration will be added at the end.
*/
public void addItemDecoration( ItemDecoration decor, int index) {
}
/**
* Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
* affect both measurement and drawing of individual item views.
*
* Item decorations are ordered. Decorations placed earlier in the list will
* be run/queried/drawn first for their effects on item views. Padding added to views
* will be nested; a padding added by an earlier decoration will mean further
* item decorations in the list will be asked to draw/pad within the previous decoration's
* given area.
*
* @param decor Decoration to add
*/
public void addItemDecoration( ItemDecoration decor) {
addItemDecoration(decor, -1);
}
/**
* Returns an {@link ItemDecoration} previously added to this RecyclerView.
*
* @param index The index position of the desired ItemDecoration.
* @return the ItemDecoration at index position
* @throws IndexOutOfBoundsException on invalid index
*/
public ItemDecoration getItemDecorationAt(int index) {
final int size = getItemDecorationCount();
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size);
}
return mItemDecorations.get(index);
}
/**
* Returns the number of {@link ItemDecoration} currently added to this RecyclerView.
*
* @return number of ItemDecorations currently added added to this RecyclerView.
*/
public int getItemDecorationCount() {
return mItemDecorations.size();
}
/**
* Removes the {@link ItemDecoration} associated with the supplied index position.
*
* @param index The index position of the ItemDecoration to be removed.
*/
public void removeItemDecorationAt(int index) {
final int size = getItemDecorationCount();
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException(index + " is an invalid index for size " + size);
}
removeItemDecoration(getItemDecorationAt(index));
}
/**
* Remove an {@link ItemDecoration} from this RecyclerView.
*
* The given decoration will no longer impact the measurement and drawing of
* item views.
*
* @param decor Decoration to remove
* @see #addItemDecoration(ItemDecoration)
*/
public void removeItemDecoration( ItemDecoration decor) {
}
/**
* Sets the {@link ChildDrawingOrderCallback} to be used for drawing children.
*
* See {@link ViewGroup#getChildDrawingOrder(int, int)} for details. Calling this method will
* always call {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)}. The parameter will be
* true if childDrawingOrderCallback is not null, false otherwise.
*
* Note that child drawing order may be overridden by View's elevation.
*
* @param childDrawingOrderCallback The ChildDrawingOrderCallback to be used by the drawing
* system.
*/
public void setChildDrawingOrderCallback(
ChildDrawingOrderCallback childDrawingOrderCallback) {
if (childDrawingOrderCallback == mChildDrawingOrderCallback) {
return;
}
mChildDrawingOrderCallback = childDrawingOrderCallback;
setChildrenDrawingOrderEnabled(mChildDrawingOrderCallback != null);
}
/**
* Set a listener that will be notified of any changes in scroll state or position.
*
* @param listener Listener to set or null to clear
*
* @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and
* {@link #removeOnScrollListener(OnScrollListener)}
*/
@Deprecated
public void setOnScrollListener( OnScrollListener listener) {
mScrollListener = listener;
}
/**
* Add a listener that will be notified of any changes in scroll state or position.
*
*
Components that add a listener should take care to remove it when finished.
* Other components that take ownership of a view may call {@link #clearOnScrollListeners()}
* to remove all attached listeners.
*
* @param listener listener to set
*/
public void addOnScrollListener( OnScrollListener listener) {
if (mScrollListeners == null) {
mScrollListeners = new ArrayList<>();
}
mScrollListeners.add(listener);
}
/**
* Remove a listener that was notified of any changes in scroll state or position.
*
* @param listener listener to set or null to clear
*/
public void removeOnScrollListener( OnScrollListener listener) {
if (mScrollListeners != null) {
mScrollListeners.remove(listener);
}
}
/**
* Remove all secondary listener that were notified of any changes in scroll state or position.
*/
public void clearOnScrollListeners() {
if (mScrollListeners != null) {
mScrollListeners.clear();
}
}
/**
* Convenience method to scroll to a certain position.
*
* RecyclerView does not implement scrolling logic, rather forwards the call to
* {@link RecyclerView.LayoutManager#scrollToPosition(int)}
* @param position Scroll to this adapter position
* @see RecyclerView.LayoutManager#scrollToPosition(int)
*/
public void scrollToPosition(int position) {
}
void jumpToPositionForSmoothScroller(int position) {
}
/**
* Starts a smooth scroll to an adapter position.
*
* To support smooth scrolling, you must override
* {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
* {@link SmoothScroller}.
*
* {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
* provide a custom smooth scroll logic, override
* {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
* LayoutManager.
*
* @param position The adapter position to scroll to
* @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
*/
public void smoothScrollToPosition(int position) {
}
@Override
public void scrollTo(int x, int y) {
Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
+ "Use scrollToPosition instead");
}
@Override
public void scrollBy(int x, int y) {
}
/**
* Scrolls the RV by 'dx' and 'dy' via calls to
* {@link LayoutManager#scrollHorizontallyBy(int, Recycler, State)} and
* {@link LayoutManager#scrollVerticallyBy(int, Recycler, State)}.
*
* Also sets how much of the scroll was actually consumed in 'consumed' parameter (indexes 0 and
* 1 for the x axis and y axis, respectively).
*
* This method should only be called in the context of an existing scroll operation such that
* any other necessary operations (such as a call to {@link #consumePendingUpdateOperations()})
* is already handled.
*/
void scrollStep(int dx, int dy, int[] consumed) {
}
/**
* Helper method reflect data changes to the state.
*
* Adapter changes during a scroll may trigger a crash because scroll assumes no data change
* but data actually changed.
*
* This method consumes all deferred changes to avoid that case.
*/
void consumePendingUpdateOperations() {
}
/**
* @return True if an existing view holder needs to be updated
*/
private boolean hasUpdatedView() {
return false;
}
/**
* Does not perform bounds checking. Used by internal methods that have already validated input.
*
* It also reports any unused scroll request to the related EdgeEffect.
*
* @param x The amount of horizontal scroll request
* @param y The amount of vertical scroll request
* @param ev The originating MotionEvent, or null if not from a touch event.
*
* @return Whether any scroll was consumed in either direction.
*/
boolean scrollByInternal(int x, int y, MotionEvent ev) {
return false;
}
/**
*
Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
* range. This value is used to compute the length of the thumb within the scrollbar's track.
*
*
* The range is expressed in arbitrary units that must be the same as the units used by
* {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.
*
* Default implementation returns 0.
*
* If you want to support scroll bars, override
* {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your
* LayoutManager.
*
* @return The horizontal offset of the scrollbar's thumb
* @see RecyclerView.LayoutManager#computeHorizontalScrollOffset
* (RecyclerView.State)
*/
@Override
public int computeHorizontalScrollOffset() {
return 0;
}
/**
* Compute the horizontal extent of the horizontal scrollbar's thumb within the
* horizontal range. This value is used to compute the length of the thumb within the
* scrollbar's track.
*
* The range is expressed in arbitrary units that must be the same as the units used by
* {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.
*
* Default implementation returns 0.
*
* If you want to support scroll bars, override
* {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your
* LayoutManager.
*
* @return The horizontal extent of the scrollbar's thumb
* @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)
*/
@Override
public int computeHorizontalScrollExtent() {
return 0;
}
/**
* Compute the horizontal range that the horizontal scrollbar represents.
*
* The range is expressed in arbitrary units that must be the same as the units used by
* {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.
*
* Default implementation returns 0.
*
* If you want to support scroll bars, override
* {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
* LayoutManager.
*
* @return The total horizontal range represented by the vertical scrollbar
* @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
*/
@Override
public int computeHorizontalScrollRange() {
return 0;
}
/**
* Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
* This value is used to compute the length of the thumb within the scrollbar's track.
*
* The range is expressed in arbitrary units that must be the same as the units used by
* {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.
*
* Default implementation returns 0.
*
* If you want to support scroll bars, override
* {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your
* LayoutManager.
*
* @return The vertical offset of the scrollbar's thumb
* @see RecyclerView.LayoutManager#computeVerticalScrollOffset
* (RecyclerView.State)
*/
@Override
public int computeVerticalScrollOffset() {
return 0;
}
/**
* Compute the vertical extent of the vertical scrollbar's thumb within the vertical range.
* This value is used to compute the length of the thumb within the scrollbar's track.
*
* The range is expressed in arbitrary units that must be the same as the units used by
* {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.
*
* Default implementation returns 0.
*
* If you want to support scroll bars, override
* {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your
* LayoutManager.
*
* @return The vertical extent of the scrollbar's thumb
* @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)
*/
@Override
public int computeVerticalScrollExtent() {
return 0;
}
/**
* Compute the vertical range that the vertical scrollbar represents.
*
* The range is expressed in arbitrary units that must be the same as the units used by
* {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.
*
* Default implementation returns 0.
*
* If you want to support scroll bars, override
* {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your
* LayoutManager.
*
* @return The total vertical range represented by the vertical scrollbar
* @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)
*/
@Override
public int computeVerticalScrollRange() {
return 0;
}
/**
* This method should be called before any code that may trigger a child view to cause a call to
* {@link RecyclerView#requestLayout()}. Doing so enables {@link RecyclerView} to avoid
* reacting to additional redundant calls to {@link #requestLayout()}.
*
* A call to this method must always be accompanied by a call to
* {@link #stopInterceptRequestLayout(boolean)} that follows the code that may trigger a
* child View to cause a call to {@link RecyclerView#requestLayout()}.
*
* @see #stopInterceptRequestLayout(boolean)
*/
void startInterceptRequestLayout() {
mInterceptRequestLayoutDepth++;
if (mInterceptRequestLayoutDepth == 1 && !mLayoutSuppressed) {
mLayoutWasDefered = false;
}
}
/**
* This method should be called after any code that may trigger a child view to cause a call to
* {@link RecyclerView#requestLayout()}.
*
* A call to this method must always be accompanied by a call to
* {@link #startInterceptRequestLayout()} that precedes the code that may trigger a child
* View to cause a call to {@link RecyclerView#requestLayout()}.
*
* @see #startInterceptRequestLayout()
*/
void stopInterceptRequestLayout(boolean performLayoutChildren) {
}
/**
* Tells this RecyclerView to suppress all layout and scroll calls until layout
* suppression is disabled with a later call to suppressLayout(false).
* When layout suppression is disabled, a requestLayout() call is sent
* if requestLayout() was attempted while layout was being suppressed.
*
* In addition to the layout suppression {@link #smoothScrollBy(int, int)},
* {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and
* {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are
* dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be
* called.
*
*
* suppressLayout(true)
does not prevent app from directly calling {@link
* LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition(
* RecyclerView, State, int)}.
*
* {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically
* stop suppressing.
*
* Note: Running ItemAnimator is not stopped automatically, it's caller's
* responsibility to call ItemAnimator.end().
*
* @param suppress true to suppress layout and scroll, false to re-enable.
*/
public final void suppressLayout(boolean suppress) {
}
/**
* Returns whether layout and scroll calls on this container are currently being
* suppressed, due to an earlier call to {@link #suppressLayout(boolean)}.
*
* @return true if layout and scroll are currently suppressed, false otherwise.
*/
public final boolean isLayoutSuppressed() {
return mLayoutSuppressed;
}
/**
* Enable or disable layout and scroll. After setLayoutFrozen(true)
is called,
* Layout requests will be postponed until setLayoutFrozen(false)
is called;
* child views are not updated when RecyclerView is frozen, {@link #smoothScrollBy(int, int)},
* {@link #scrollBy(int, int)}, {@link #scrollToPosition(int)} and
* {@link #smoothScrollToPosition(int)} are dropped; TouchEvents and GenericMotionEvents are
* dropped; {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} will not be
* called.
*
*
* setLayoutFrozen(true)
does not prevent app from directly calling {@link
* LayoutManager#scrollToPosition(int)}, {@link LayoutManager#smoothScrollToPosition(
* RecyclerView, State, int)}.
*
* {@link #setAdapter(Adapter)} and {@link #swapAdapter(Adapter, boolean)} will automatically
* stop frozen.
*
* Note: Running ItemAnimator is not stopped automatically, it's caller's
* responsibility to call ItemAnimator.end().
*
* @param frozen true to freeze layout and scroll, false to re-enable.
*
* @deprecated Use {@link #suppressLayout(boolean)}.
*/
@Deprecated
public void setLayoutFrozen(boolean frozen) {
suppressLayout(frozen);
}
/**
* @return true if layout and scroll are frozen
*
* @deprecated Use {@link #isLayoutSuppressed()}.
*/
@Deprecated
public boolean isLayoutFrozen() {
return isLayoutSuppressed();
}
/**
* @deprecated Use {@link #setItemAnimator(ItemAnimator)} ()}.
*/
@Deprecated
@Override
public void setLayoutTransition(LayoutTransition transition) {
if (transition == null) {
super.setLayoutTransition(null);
} else {
throw new IllegalArgumentException("Providing a LayoutTransition into RecyclerView is "
+ "not supported. Please use setItemAnimator() instead for animating changes "
+ "to the items in this RecyclerView");
}
}
/**
* Animate a scroll by the given amount of pixels along either axis.
*
* @param dx Pixels to scroll horizontally
* @param dy Pixels to scroll vertically
*/
public void smoothScrollBy( int dx, int dy) {
smoothScrollBy(dx, dy, null);
}
/**
* Animate a scroll by the given amount of pixels along either axis.
*
* @param dx Pixels to scroll horizontally
* @param dy Pixels to scroll vertically
* @param interpolator {@link Interpolator} to be used for scrolling. If it is
* {@code null}, RecyclerView is going to use the default interpolator.
*/
public void smoothScrollBy( int dx, int dy, Interpolator interpolator) {
}
/**
* Begin a standard fling with an initial velocity along each axis in pixels per second.
* If the velocity given is below the system-defined minimum this method will return false
* and no fling will occur.
*
* @param velocityX Initial horizontal velocity in pixels per second
* @param velocityY Initial vertical velocity in pixels per second
* @return true if the fling was started, false if the velocity was too low to fling or
* LayoutManager does not support scrolling in the axis fling is issued.
*
* @see LayoutManager#canScrollVertically()
* @see LayoutManager#canScrollHorizontally()
*/
public boolean fling(int velocityX, int velocityY) {
return false;
}
/**
* Similar to {@link #stopScroll()} but does not set the state.
*/
private void stopScrollersInternal() {
}
/**
* Returns the minimum velocity to start a fling.
*
* @return The minimum velocity to start a fling
*/
public int getMinFlingVelocity() {
return mMinFlingVelocity;
}
/**
* Returns the maximum fling velocity used by this RecyclerView.
*
* @return The maximum fling velocity used by this RecyclerView.
*/
public int getMaxFlingVelocity() {
return mMaxFlingVelocity;
}
/**
* Apply a pull to relevant overscroll glow effects
*/
private void pullGlows(float x, float overscrollX, float y, float overscrollY) {
}
private void releaseGlows() {
boolean needsInvalidate = false;
if (mLeftGlow != null) {
mLeftGlow.onRelease();
needsInvalidate = mLeftGlow.isFinished();
}
if (mTopGlow != null) {
mTopGlow.onRelease();
needsInvalidate |= mTopGlow.isFinished();
}
if (mRightGlow != null) {
mRightGlow.onRelease();
needsInvalidate |= mRightGlow.isFinished();
}
if (mBottomGlow != null) {
mBottomGlow.onRelease();
needsInvalidate |= mBottomGlow.isFinished();
}
if (needsInvalidate) {
}
}
void considerReleasingGlowsOnScroll(int dx, int dy) {
boolean needsInvalidate = false;
if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) {
mLeftGlow.onRelease();
needsInvalidate = mLeftGlow.isFinished();
}
if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) {
mRightGlow.onRelease();
needsInvalidate |= mRightGlow.isFinished();
}
if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) {
mTopGlow.onRelease();
needsInvalidate |= mTopGlow.isFinished();
}
if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) {
mBottomGlow.onRelease();
needsInvalidate |= mBottomGlow.isFinished();
}
if (needsInvalidate) {
}
}
void absorbGlows(int velocityX, int velocityY) {
if (velocityX < 0) {
ensureLeftGlow();
if (mLeftGlow.isFinished()) {
mLeftGlow.onAbsorb(-velocityX);
}
} else if (velocityX > 0) {
ensureRightGlow();
if (mRightGlow.isFinished()) {
mRightGlow.onAbsorb(velocityX);
}
}
if (velocityY < 0) {
ensureTopGlow();
if (mTopGlow.isFinished()) {
mTopGlow.onAbsorb(-velocityY);
}
} else if (velocityY > 0) {
ensureBottomGlow();
if (mBottomGlow.isFinished()) {
mBottomGlow.onAbsorb(velocityY);
}
}
if (velocityX != 0 || velocityY != 0) {
}
}
void ensureLeftGlow() {
if (mLeftGlow != null) {
return;
}
if (mClipToPadding) {
mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
} else {
mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
}
}
void ensureRightGlow() {
}
void ensureTopGlow() {
}
void ensureBottomGlow() {
}
void invalidateGlows() {
mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null;
}
/**
* Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
* in the Adapter but not visible in the UI), it employs a more involved focus search strategy
* that differs from other ViewGroups.
*
* It first does a focus search within the RecyclerView. If this search finds a View that is in
* the focus direction with respect to the currently focused View, RecyclerView returns that
* child as the next focus target. When it cannot find such child, it calls
* {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views
* in the focus search direction. If LayoutManager adds a View that matches the
* focus search criteria, it will be returned as the focus search result. Otherwise,
* RecyclerView will call parent to handle the focus search like a regular ViewGroup.
*
* When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that
* is not in the focus direction is still valid focus target which may not be the desired
* behavior if the Adapter has more children in the focus direction. To handle this case,
* RecyclerView converts the focus direction to an absolute direction and makes a preliminary
* focus search in that direction. If there are no Views to gain focus, it will call
* {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a
* focus search with the original (relative) direction. This allows RecyclerView to provide
* better candidates to the focus search while still allowing the view system to take focus from
* the RecyclerView and give it to a more suitable child if such child exists.
*
* @param focused The view that currently has focus
* @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
* {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD},
* {@link View#FOCUS_BACKWARD} or 0 for not applicable.
*
* @return A new View that can be the next focus after the focused View
*/
@Override
public View focusSearch(View focused, int direction) {
return null;
}
/**
* Checks if the new focus candidate is a good enough candidate such that RecyclerView will
* assign it as the next focus View instead of letting view hierarchy decide.
* A good candidate means a View that is aligned in the focus direction wrt the focused View
* and is not the RecyclerView itself.
* When this method returns false, RecyclerView will let the parent make the decision so the
* same View may still get the focus as a result of that search.
*/
private boolean isPreferredNextFocus(View focused, View next, int direction) {
return false;
}
@Override
public void requestChildFocus(View child, View focused) {
}
/**
* Requests that the given child of the RecyclerView be positioned onto the screen. This method
* can be called for both unfocusable and focusable child views. For unfocusable child views,
* the {@param focused} parameter passed is null, whereas for a focusable child, this parameter
* indicates the actual descendant view within this child view that holds the focus.
* @param child The child view of this RecyclerView that wants to come onto the screen.
* @param focused The descendant view that actually has the focus if child is focusable, null
* otherwise.
*/
private void requestChildOnScreen( View child, View focused) {
}
@Override
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
return false;
}
@Override
public void addFocusables(ArrayList views, int direction, int focusableMode) {
}
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
if (isComputingLayout()) {
// if we are in the middle of a layout calculation, don't let any child take focus.
// RV will handle it after layout calculation is finished.
return false;
}
return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
}
@Override
protected void onAttachedToWindow() {
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
/**
* Returns true if RecyclerView is attached to window.
*/
@Override
public boolean isAttachedToWindow() {
return mIsAttached;
}
/**
* Checks if RecyclerView is in the middle of a layout or scroll and throws an
* {@link IllegalStateException} if it is not.
*
* @param message The message for the exception. Can be null.
* @see #assertNotInLayoutOrScroll(String)
*/
void assertInLayoutOrScroll(String message) {
if (!isComputingLayout()) {
if (message == null) {
throw new IllegalStateException("Cannot call this method unless RecyclerView is "
+ "computing a layout or scrolling" + exceptionLabel());
}
throw new IllegalStateException(message + exceptionLabel());
}
}
/**
* Checks if RecyclerView is in the middle of a layout or scroll and throws an
* {@link IllegalStateException} if it is.
*
* @param message The message for the exception. Can be null.
* @see #assertInLayoutOrScroll(String)
*/
void assertNotInLayoutOrScroll(String message) {
if (isComputingLayout()) {
if (message == null) {
throw new IllegalStateException("Cannot call this method while RecyclerView is "
+ "computing a layout or scrolling" + exceptionLabel());
}
throw new IllegalStateException(message);
}
if (mDispatchScrollCounter > 0) {
Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might"
+ "be run during a measure & layout pass where you cannot change the"
+ "RecyclerView data. Any method call that might change the structure"
+ "of the RecyclerView or the adapter contents should be postponed to"
+ "the next frame.",
new IllegalStateException("" + exceptionLabel()));
}
}
/**
* Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
* to child views or this view's standard scrolling behavior.
*
* Client code may use listeners to implement item manipulation behavior. Once a listener
* returns true from
* {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
* {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
* for each incoming MotionEvent until the end of the gesture.
*
* @param listener Listener to add
* @see SimpleOnItemTouchListener
*/
public void addOnItemTouchListener( OnItemTouchListener listener) {
mOnItemTouchListeners.add(listener);
}
/**
* Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events.
*
* @param listener Listener to remove
*/
public void removeOnItemTouchListener( OnItemTouchListener listener) {
mOnItemTouchListeners.remove(listener);
if (mInterceptingOnItemTouchListener == listener) {
mInterceptingOnItemTouchListener = null;
}
}
/**
* Dispatches the motion event to the intercepting OnItemTouchListener or provides opportunity
* for OnItemTouchListeners to intercept.
* @param e The MotionEvent
* @return True if handled by an intercepting OnItemTouchListener.
*/
private boolean dispatchToOnItemTouchListeners(MotionEvent e) {
// OnItemTouchListeners should receive calls to their methods in the same pattern that
// ViewGroups do. That pattern is a bit confusing, which in turn makes the below code a
// bit confusing. Here are rules for the pattern:
//
// 1. A single MotionEvent should not be passed to either OnInterceptTouchEvent or
// OnTouchEvent twice.
// 2. ACTION_DOWN MotionEvents may be passed to both onInterceptTouchEvent and
// onTouchEvent.
// 3. All other MotionEvents should be passed to either onInterceptTouchEvent or
// onTouchEvent, not both.
// Side Note: If we are to truly mimic how MotionEvents work in the view system, for every
// MotionEvent, any OnItemTouchListener that is before the intercepting OnItemTouchEvent
// should still have a chance to intercept, and if it does, the previously intercepting
// OnItemTouchEvent should get an ACTION_CANCEL event.
if (mInterceptingOnItemTouchListener == null) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
return false;
}
return findInterceptingOnItemTouchListener(e);
} else {
mInterceptingOnItemTouchListener.onTouchEvent(this, e);
final int action = e.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mInterceptingOnItemTouchListener = null;
}
return true;
}
}
/**
* Looks for an OnItemTouchListener that wants to intercept.
*
* Passes the MotionEvent to all registered OnItemTouchListeners one at a time. If one wants
* to intercept and the action is not ACTION_UP or ACTION_CANCEL, saves the intercepting
* OnItemTouchListener and immediately returns true. If none want to intercept
* or the action is ACTION_UP or ACTION_CANCEL, returns false.
*
* @param e The MotionEvent
* @return true if an OnItemTouchListener is saved as intercepting.
*/
private boolean findInterceptingOnItemTouchListener(MotionEvent e) {
int action = e.getAction();
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
if (listener.onInterceptTouchEvent(this, e)
&& action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL) {
mInterceptingOnItemTouchListener = listener;
return true;
}
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
return false;
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
listener.onRequestDisallowInterceptTouchEvent(disallowIntercept);
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
return false;
}
private void resetScroll() {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
releaseGlows();
}
private void onPointerUp(MotionEvent e) {
final int actionIndex = e.getActionIndex();
if (e.getPointerId(actionIndex) == mScrollPointerId) {
// Pick a new pointer to pick up the slack.
final int newIndex = actionIndex == 0 ? 1 : 0;
mScrollPointerId = e.getPointerId(newIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f);
}
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
return false;
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
}
/**
* An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios
* where this RecyclerView is otherwise lacking better information.
*/
void defaultOnMeasure(int widthSpec, int heightSpec) {
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw || h != oldh) {
invalidateGlows();
// layout's w/h are updated during measure/layout steps.
}
}
/**
* Sets the {@link ItemAnimator} that will handle animations involving changes
* to the items in this RecyclerView. By default, RecyclerView instantiates and
* uses an instance of {@link DefaultItemAnimator}. Whether item animations are
* enabled for the RecyclerView depends on the ItemAnimator and whether
* the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations()
* supports item animations}.
*
* @param animator The ItemAnimator being set. If null, no animations will occur
* when changes occur to the items in this RecyclerView.
*/
void onEnterLayoutOrScroll() {
mLayoutOrScrollCounter++;
}
void onExitLayoutOrScroll() {
onExitLayoutOrScroll(true);
}
void onExitLayoutOrScroll(boolean enableChangeEvents) {
}
boolean isAccessibilityEnabled() {
return mAccessibilityManager != null && mAccessibilityManager.isEnabled();
}
private void dispatchContentChangedIfNecessary() {
final int flags = mEatenAccessibilityChangeFlags;
mEatenAccessibilityChangeFlags = 0;
if (flags != 0 && isAccessibilityEnabled()) {
final AccessibilityEvent event = AccessibilityEvent.obtain();
event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
sendAccessibilityEventUnchecked(event);
}
}
/**
* Returns whether RecyclerView is currently computing a layout.
*
* If this method returns true, it means that RecyclerView is in a lockdown state and any
* attempt to update adapter contents will result in an exception because adapter contents
* cannot be changed while RecyclerView is trying to compute the layout.
*
* It is very unlikely that your code will be running during this state as it is
* called by the framework when a layout traversal happens or RecyclerView starts to scroll
* in response to system events (touch, accessibility etc).
*
* This case may happen if you have some custom logic to change adapter contents in
* response to a View callback (e.g. focus change callback) which might be triggered during a
* layout calculation. In these cases, you should just postpone the change using a Handler or a
* similar mechanism.
*
* @return true
if RecyclerView is currently computing a layout, false
* otherwise
*/
public boolean isComputingLayout() {
return mLayoutOrScrollCounter > 0;
}
/**
* Returns true if an accessibility event should not be dispatched now. This happens when an
* accessibility request arrives while RecyclerView does not have a stable state which is very
* hard to handle for a LayoutManager. Instead, this method records necessary information about
* the event and dispatches a window change event after the critical section is finished.
*
* @return True if the accessibility event should be postponed.
*/
boolean shouldDeferAccessibilityEvent(AccessibilityEvent event) {
return false;
}
@Override
public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
if (shouldDeferAccessibilityEvent(event)) {
return;
}
super.sendAccessibilityEventUnchecked(event);
}
/**
* Consumes adapter updates and calculates which type of animations we want to run.
* Called in onMeasure and dispatchLayout.
*
* This method may process only the pre-layout state of updates or all of them.
*/
private void processAdapterUpdatesAndSetAnimationFlags() {
}
/**
* Wrapper around layoutChildren() that handles animating changes caused by layout.
* Animations work on the assumption that there are five different kinds of items
* in play:
* PERSISTENT: items are visible before and after layout
* REMOVED: items were visible before layout and were removed by the app
* ADDED: items did not exist before layout and were added by the app
* DISAPPEARING: items exist in the data set before/after, but changed from
* visible to non-visible in the process of layout (they were moved off
* screen as a side-effect of other changes)
* APPEARING: items exist in the data set before/after, but changed from
* non-visible to visible in the process of layout (they were moved on
* screen as a side-effect of other changes)
* The overall approach figures out what items exist before/after layout and
* infers one of the five above states for each of the items. Then the animations
* are set up accordingly:
* PERSISTENT views are animated via
* {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
* DISAPPEARING views are animated via
* {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
* APPEARING views are animated via
* {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
* and changed views are animated via
* {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}.
*/
void dispatchLayout() {
}
private void saveFocusInfo() {
View child = null;
if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) {
child = getFocusedChild();
}
final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child);
if (focusedVh == null) {
resetFocusInfo();
} else {
mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID;
// mFocusedItemPosition should hold the current adapter position of the previously
// focused item. If the item is removed, we store the previous adapter position of the
// removed item.
mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION
: (focusedVh.isRemoved() ? focusedVh.mOldPosition
: focusedVh.getAdapterPosition());
mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView);
}
}
private void resetFocusInfo() {
mState.mFocusedItemId = NO_ID;
mState.mFocusedItemPosition = NO_POSITION;
mState.mFocusedSubChildId = View.NO_ID;
}
private int getDeepestFocusedViewWithId(View view) {
int lastKnownId = view.getId();
while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) {
view = ((ViewGroup) view).getFocusedChild();
final int id = view.getId();
if (id != View.NO_ID) {
lastKnownId = view.getId();
}
}
return lastKnownId;
}
private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) {
return false;
}
@Override
protected void removeDetachedView(View child, boolean animate) {
ViewHolder vh = getChildViewHolderInt(child);
if (vh != null) {
if (vh.isTmpDetached()) {
vh.clearTmpDetachFlag();
} else if (!vh.shouldIgnore()) {
throw new IllegalArgumentException("Called removeDetachedView with a view which"
+ " is not flagged as tmp detached." + vh + exceptionLabel());
}
}
// Clear any android.view.animation.Animation that may prevent the item from
// detaching when being removed. If a child is re-added before the
// lazy detach occurs, it will receive invalid attach/detach sequencing.
child.clearAnimation();
dispatchChildDetached(child);
super.removeDetachedView(child, animate);
}
/**
* Returns a unique key to be used while handling change animations.
* It might be child's position or stable id depending on the adapter type.
*/
long getChangedHolderKey(ViewHolder holder) {
return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
mFirstLayoutComplete = true;
}
@Override
public void requestLayout() {
if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) {
super.requestLayout();
} else {
mLayoutWasDefered = true;
}
}
void markItemDecorInsetsDirty() {
}
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return false;
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return null;
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return null;
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return null;
}
/**
* Returns true if RecyclerView is currently running some animations.
*
* If you want to be notified when animations are finished, use
* {@link ItemAnimator#isRunning(ItemAnimator.ItemAnimatorFinishedListener)}.
*
* @return True if there are some item animations currently running or waiting to be started.
*/
public boolean isAnimating() {
return false;
}
void saveOldPositions() {
}
void clearOldPositions() {
}
void offsetPositionRecordsForMove(int from, int to) {
}
void offsetPositionRecordsForRemove(int positionStart, int itemCount,
boolean applyToPreLayout) {
}
/**
* Rebind existing views for the given range, or create as needed.
*
* @param positionStart Adapter position to start at
* @param itemCount Number of views that must explicitly be rebound
*/
void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
}
boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
return false;
}
/**
* Processes the fact that, as far as we can tell, the data set has completely changed.
*
*
* - Once layout occurs, all attached items should be discarded or animated.
*
- Attached items are labeled as invalid.
*
- Because items may still be prefetched between a "data set completely changed"
* event and a layout event, all cached items are discarded.
*
*
* @param dispatchItemsChanged Whether to call
* {@link LayoutManager#onItemsChanged(RecyclerView)} during measure/layout.
*/
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
mDispatchItemsChangedEvent |= dispatchItemsChanged;
mDataSetHasChangedAfterLayout = true;
markKnownViewsInvalid();
}
/**
* Mark all known views as invalid. Used in response to a, "the whole world might have changed"
* data change event.
*/
void markKnownViewsInvalid() {
}
/**
* Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method
* will trigger a {@link #requestLayout()} call.
*/
public void invalidateItemDecorations() {
}
/**
* Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's
* focus even if the View representing the Item is replaced during a layout calculation.
*
* By default, this value is {@code true}.
*
* @return True if the RecyclerView will try to preserve focused Item after a layout if it loses
* focus.
*
* @see #setPreserveFocusAfterLayout(boolean)
*/
public boolean getPreserveFocusAfterLayout() {
return mPreserveFocusAfterLayout;
}
/**
* Set whether the RecyclerView should try to keep the same Item focused after a layout
* calculation or not.
*
* Usually, LayoutManagers keep focused views visible before and after layout but sometimes,
* views may lose focus during a layout calculation as their state changes or they are replaced
* with another view due to type change or animation. In these cases, RecyclerView can request
* focus on the new view automatically.
*
* @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a
* layout calculations. Defaults to true.
*
* @see #getPreserveFocusAfterLayout()
*/
public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) {
mPreserveFocusAfterLayout = preserveFocusAfterLayout;
}
/**
* Retrieve the {@link ViewHolder} for the given child view.
*
* @param child Child of this RecyclerView to query for its ViewHolder
* @return The child view's ViewHolder
*/
public ViewHolder getChildViewHolder( View child) {
final ViewParent parent = child.getParent();
if (parent != null && parent != this) {
throw new IllegalArgumentException("View " + child + " is not a direct child of "
+ this);
}
return getChildViewHolderInt(child);
}
/**
* Traverses the ancestors of the given view and returns the item view that contains it and
* also a direct child of the RecyclerView. This returned view can be used to get the
* ViewHolder by calling {@link #getChildViewHolder(View)}.
*
* @param view The view that is a descendant of the RecyclerView.
*
* @return The direct child of the RecyclerView which contains the given view or null if the
* provided view is not a descendant of this RecyclerView.
*
* @see #getChildViewHolder(View)
* @see #findContainingViewHolder(View)
*/
public View findContainingItemView( View view) {
ViewParent parent = view.getParent();
while (parent != null && parent != this && parent instanceof View) {
view = (View) parent;
parent = view.getParent();
}
return parent == this ? view : null;
}
/**
* Returns the ViewHolder that contains the given view.
*
* @param view The view that is a descendant of the RecyclerView.
*
* @return The ViewHolder that contains the given view or null if the provided view is not a
* descendant of this RecyclerView.
*/
public ViewHolder findContainingViewHolder( View view) {
View itemView = findContainingItemView(view);
return itemView == null ? null : getChildViewHolder(itemView);
}
static ViewHolder getChildViewHolderInt(View child) {
if (child == null) {
return null;
}
return ((LayoutParams) child.getLayoutParams()).mViewHolder;
}
/**
* @deprecated use {@link #getChildAdapterPosition(View)} or
* {@link #getChildLayoutPosition(View)}.
*/
@Deprecated
public int getChildPosition( View child) {
return getChildAdapterPosition(child);
}
/**
* Return the adapter position that the given child view corresponds to.
*
* @param child Child View to query
* @return Adapter position corresponding to the given view or {@link #NO_POSITION}
*/
public int getChildAdapterPosition( View child) {
final ViewHolder holder = getChildViewHolderInt(child);
return holder != null ? holder.getAdapterPosition() : NO_POSITION;
}
/**
* Return the adapter position of the given child view as of the latest completed layout pass.
*
* This position may not be equal to Item's adapter position if there are pending changes
* in the adapter which have not been reflected to the layout yet.
*
* @param child Child View to query
* @return Adapter position of the given View as of last layout pass or {@link #NO_POSITION} if
* the View is representing a removed item.
*/
public int getChildLayoutPosition( View child) {
final ViewHolder holder = getChildViewHolderInt(child);
return holder != null ? holder.getLayoutPosition() : NO_POSITION;
}
/**
* Return the stable item id that the given child view corresponds to.
*
* @param child Child View to query
* @return Item id corresponding to the given view or {@link #NO_ID}
*/
public long getChildItemId( View child) {
if (mAdapter == null || !mAdapter.hasStableIds()) {
return NO_ID;
}
final ViewHolder holder = getChildViewHolderInt(child);
return holder != null ? holder.getItemId() : NO_ID;
}
@Override
public boolean drawChild(Canvas canvas, View child, long drawingTime) {
return super.drawChild(canvas, child, drawingTime);
}
/**
* Called when an item view is attached to this RecyclerView.
*
*
Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
* of child views as they become attached. This will be called before a
* {@link LayoutManager} measures or lays out the view and is a good time to perform these
* changes.
*
* @param child Child view that is now attached to this RecyclerView and its associated window
*/
public void onChildAttachedToWindow( View child) {
}
/**
* Called when an item view is detached from this RecyclerView.
*
* Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
* of child views as they become detached. This will be called as a
* {@link LayoutManager} fully detaches the child view from the parent and its window.
*
* @param child Child view that is now detached from this RecyclerView and its associated window
*/
public void onChildDetachedFromWindow( View child) {
}
/**
* Returns the bounds of the view including its decoration and margins.
*
* @param view The view element to check
* @param outBounds A rect that will receive the bounds of the element including its
* decoration and margins.
*/
public void getDecoratedBoundsWithMargins( View view, Rect outBounds) {
getDecoratedBoundsWithMarginsInt(view, outBounds);
}
static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) {
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Rect insets = lp.mDecorInsets;
outBounds.set(view.getLeft() - insets.left - lp.leftMargin,
view.getTop() - insets.top - lp.topMargin,
view.getRight() + insets.right + lp.rightMargin,
view.getBottom() + insets.bottom + lp.bottomMargin);
}
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
/**
* Called when the scroll position of this RecyclerView changes. Subclasses should use
* this method to respond to scrolling within the adapter's data set instead of an explicit
* listener.
*
* This method will always be invoked before listeners. If a subclass needs to perform
* any additional upkeep or bookkeeping after scrolling but before listeners run,
* this is a good place to do so.
*
* This differs from {@link View#onScrollChanged(int, int, int, int)} in that it receives
* the distance scrolled in either direction within the adapter's data set instead of absolute
* scroll coordinates. Since RecyclerView cannot compute the absolute scroll position from
* any arbitrary point in the data set, onScrollChanged
will always receive
* the current {@link View#getScrollX()} and {@link View#getScrollY()} values which
* do not correspond to the data set scroll position. However, some subclasses may choose
* to use these fields as special offsets.
*
* @param dx horizontal distance scrolled in pixels
* @param dy vertical distance scrolled in pixels
*/
public void onScrolled( int dx, int dy) {
// Do nothing
}
void dispatchOnScrolled(int hresult, int vresult) {
mDispatchScrollCounter++;
// Pass the current scrollX/scrollY values; no actual change in these properties occurred
// but some general-purpose code may choose to respond to changes this way.
final int scrollX = getScrollX();
final int scrollY = getScrollY();
onScrollChanged(scrollX, scrollY, scrollX, scrollY);
// Pass the real deltas to onScrolled, the RecyclerView-specific method.
onScrolled(hresult, vresult);
// Invoke listeners last. Subclassed view methods always handle the event first.
// All internal state is consistent by the time listeners are invoked.
if (mScrollListener != null) {
mScrollListener.onScrolled(this, hresult, vresult);
}
if (mScrollListeners != null) {
for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
mScrollListeners.get(i).onScrolled(this, hresult, vresult);
}
}
mDispatchScrollCounter--;
}
/**
* Called when the scroll state of this RecyclerView changes. Subclasses should use this
* method to respond to state changes instead of an explicit listener.
*
* This method will always be invoked before listeners, but after the LayoutManager
* responds to the scroll state change.
*
* @param state the new scroll state, one of {@link #SCROLL_STATE_IDLE},
* {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}
*/
public void onScrollStateChanged(int state) {
// Do nothing
}
void repositionShadowingViews() {
// Fix up shadow views used by change animations
}
private class RecyclerViewDataObserver extends AdapterDataObserver {
RecyclerViewDataObserver() {
}
@Override
public void onChanged() {
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
}
void triggerUpdateProcessor() {
}
}
/**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
*
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
* and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
*
* RecyclerView automatically creates a pool for itself if you don't provide one.
*/
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
/**
* Tracks both pooled holders, as well as create/bind timing metadata for the given type.
*
* Note that this tracks running averages of create/bind time across all RecyclerViews
* (and, indirectly, Adapters) that use this pool.
*
* 1) This enables us to track average create and bind times across multiple adapters. Even
* though create (and especially bind) may behave differently for different Adapter
* subclasses, sharing the pool is a strong signal that they'll perform similarly, per type.
*
* 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return
* false for all other views of its type for the same deadline. This prevents items
* constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch.
*/
static class ScrapData {
final ArrayList mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray mScrap = new SparseArray<>();
private int mAttachCount = 0;
/**
* Discard all ViewHolders.
*/
public void clear() {
for (int i = 0; i < mScrap.size(); i++) {
ScrapData data = mScrap.valueAt(i);
data.mScrapHeap.clear();
}
}
/**
* Sets the maximum number of ViewHolders to hold in the pool before discarding.
*
* @param viewType ViewHolder Type
* @param max Maximum number
*/
public void setMaxRecycledViews(int viewType, int max) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mMaxScrap = max;
final ArrayList scrapHeap = scrapData.mScrapHeap;
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
/**
* Returns the current number of Views held by the RecycledViewPool of the given view type.
*/
public int getRecycledViewCount(int viewType) {
return getScrapDataForType(viewType).mScrapHeap.size();
}
/**
* Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
* present.
*
* @param viewType ViewHolder type.
* @return ViewHolder of the specified type acquired from the pool, or {@code null} if none
* are present.
*/
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList scrapHeap = scrapData.mScrapHeap;
for (int i = scrapHeap.size() - 1; i >= 0; i--) {
if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
return scrapHeap.remove(i);
}
}
}
return null;
}
/**
* Total number of ViewHolders held by the pool.
*
* @return Number of ViewHolders held by the pool.
*/
int size() {
int count = 0;
for (int i = 0; i < mScrap.size(); i++) {
ArrayList viewHolders = mScrap.valueAt(i).mScrapHeap;
if (viewHolders != null) {
count += viewHolders.size();
}
}
return count;
}
/**
* Add a scrap ViewHolder to the pool.
*
* If the pool is already full for that ViewHolder's type, it will be immediately discarded.
*
* @param scrap ViewHolder to be added to the pool.
*/
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
long runningAverage(long oldAverage, long newValue) {
if (oldAverage == 0) {
return newValue;
}
return (oldAverage / 4 * 3) + (newValue / 4);
}
void factorInCreateTime(int viewType, long createTimeNs) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mCreateRunningAverageNs = runningAverage(
scrapData.mCreateRunningAverageNs, createTimeNs);
}
void factorInBindTime(int viewType, long bindTimeNs) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mBindRunningAverageNs = runningAverage(
scrapData.mBindRunningAverageNs, bindTimeNs);
}
boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;
return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
}
boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) {
long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs;
return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
}
void attach() {
mAttachCount++;
}
void detach() {
mAttachCount--;
}
/**
* Detaches the old adapter and attaches the new one.
*
* RecycledViewPool will clear its cache if it has only one adapter attached and the new
* adapter uses a different ViewHolder than the oldAdapter.
*
* @param oldAdapter The previous adapter instance. Will be detached.
* @param newAdapter The new adapter instance. Will be attached.
* @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
* ViewHolder and view types.
*/
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
boolean compatibleWithPrevious) {
if (oldAdapter != null) {
detach();
}
if (!compatibleWithPrevious && mAttachCount == 0) {
clear();
}
if (newAdapter != null) {
attach();
}
}
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
}
/**
* Utility method for finding an internal RecyclerView, if present
*/
static RecyclerView findNestedRecyclerView(View view) {
if (!(view instanceof ViewGroup)) {
return null;
}
if (view instanceof RecyclerView) {
return (RecyclerView) view;
}
final ViewGroup parent = (ViewGroup) view;
final int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
final View child = parent.getChildAt(i);
final RecyclerView descendant = findNestedRecyclerView(child);
if (descendant != null) {
return descendant;
}
}
return null;
}
/**
* Utility method for clearing holder's internal RecyclerView, if present
*/
static void clearNestedRecyclerViewIfNotNested(ViewHolder holder) {
if (holder.mNestedRecyclerView != null) {
View item = holder.mNestedRecyclerView.get();
while (item != null) {
if (item == holder.itemView) {
return; // match found, don't need to clear
}
ViewParent parent = item.getParent();
if (parent instanceof View) {
item = (View) parent;
} else {
item = null;
}
}
holder.mNestedRecyclerView = null; // not nested
}
}
/**
* Time base for deadline-aware work scheduling. Overridable for testing.
*
* Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling
* isn't relevant.
*/
long getNanoTime() {
if (ALLOW_THREAD_GAP_WORK) {
return System.nanoTime();
} else {
return 0;
}
}
/**
* A Recycler is responsible for managing scrapped or detached item views for reuse.
*
*
A "scrapped" view is a view that is still attached to its parent RecyclerView but
* that has been marked for removal or reuse.
*
* Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
* an adapter's data set representing the data at a given position or item ID.
* If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
* If not, the view can be quickly reused by the LayoutManager with no further work.
* Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
* may be repositioned by a LayoutManager without remeasurement.
*/
public final class Recycler {
public void clear() {
// TODO Auto-generated method stub
}
}
/**
* ViewCacheExtension is a helper class to provide an additional layer of view caching that can
* be controlled by the developer.
*
* When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
* first level cache to find a matching View. If it cannot find a suitable View, Recycler will
* call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
* {@link RecycledViewPool}.
*
* Note that, Recycler never sends Views to this method to be cached. It is developers
* responsibility to decide whether they want to keep their Views in this custom cache or let
* the default recycling policy handle it.
*/
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
*
* This method should not create a new View. Instead, it is expected to return
* an already created View that can be re-used for the given type and position.
* If the View is marked as ignored, it should first call
* {@link LayoutManager#stopIgnoringView(View)} before returning the View.
*
* RecyclerView will re-bind the returned View to the position if necessary.
*
* @param recycler The Recycler that can be used to bind the View
* @param position The adapter position
* @param type The type of the View, defined by adapter
* @return A View that is bound to the given position or NULL if there is no View to re-use
* @see LayoutManager#ignoreView(View)
*/
public abstract View getViewForPositionAndType( Recycler recycler, int position,
int type);
}
/**
* Base class for an Adapter
*
*
Adapters provide a binding from an app-specific data set to views that are displayed
* within a {@link RecyclerView}.
*
* @param A class that extends ViewHolder that will be used by the adapter.
*/
public abstract static class Adapter {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
private boolean mHasStableIds = false;
/**
* Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
* an item.
*
* This new ViewHolder should be constructed with a new View that can represent the items
* of the given type. You can either create a new View manually or inflate it from an XML
* layout file.
*
* The new ViewHolder will be used to display items of the adapter using
* {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
* different items in the data set, it is a good idea to cache references to sub views of
* the View to avoid unnecessary {@link View#findViewById(int)} calls.
*
* @param parent The ViewGroup into which the new View will be added after it is bound to
* an adapter position.
* @param viewType The view type of the new View.
*
* @return A new ViewHolder that holds a View of the given view type.
* @see #getItemViewType(int)
* @see #onBindViewHolder(ViewHolder, int)
*/
public abstract VH onCreateViewHolder( ViewGroup parent, int viewType);
/**
* Called by RecyclerView to display the data at the specified position. This method should
* update the contents of the {@link ViewHolder#itemView} to reflect the item at the given
* position.
*
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
* again if the position of the item changes in the data set unless the item itself is
* invalidated or the new position cannot be determined. For this reason, you should only
* use the position
parameter while acquiring the related data item inside
* this method and should not keep a copy of it. If you need the position of an item later
* on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
* have the updated adapter position.
*
* Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can
* handle efficient partial bind.
*
* @param holder The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
*/
public abstract void onBindViewHolder( VH holder, int position);
/**
* Called by RecyclerView to display the data at the specified position. This method
* should update the contents of the {@link ViewHolder#itemView} to reflect the item at
* the given position.
*
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
* again if the position of the item changes in the data set unless the item itself is
* invalidated or the new position cannot be determined. For this reason, you should only
* use the position
parameter while acquiring the related data item inside
* this method and should not keep a copy of it. If you need the position of an item later
* on (e.g. in a click listener), use {@link ViewHolder#getAdapterPosition()} which will
* have the updated adapter position.
*
* Partial bind vs full bind:
*
* The payloads parameter is a merge list from {@link #notifyItemChanged(int, Object)} or
* {@link #notifyItemRangeChanged(int, int, Object)}. If the payloads list is not empty,
* the ViewHolder is currently bound to old data and Adapter may run an efficient partial
* update using the payload info. If the payload is empty, Adapter must run a full bind.
* Adapter should not assume that the payload passed in notify methods will be received by
* onBindViewHolder(). For example when the view is not attached to the screen, the
* payload in notifyItemChange() will be simply dropped.
*
* @param holder The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
* @param payloads A non-null list of merged payloads. Can be empty list if requires full
* update.
*/
public void onBindViewHolder( VH holder, int position,
List