RecyclerView version bump

This commit is contained in:
seven332 committed 2015-06-03 17:02:36 +08:00
1 parent 2130e7a8dd
commit ce75c6a67c
6 files changed
+748 -150

No files matched your search

@@ -60,11 +60,14 @@ class LayoutState {
int mLayoutDirection;
/**
* Used if you want to pre-layout items that are not yet visible.
* The difference with {@link #mAvailable} is that, when recycling, distance rendered for
* {@link #mExtra} is not considered not to recycle visible children.
* This is the target pixel closest to the start of the layout that we are trying to fill
*/
int mExtra = 0;
int mStartLine = 0;
/**
* This is the target pixel closest to the end of the layout that we are trying to fill
*/
int mEndLine = 0;
/**
* @return true if there are more items in the data adapter
@@ -84,4 +87,16 @@ class LayoutState {
mCurrentPosition += mItemDirection;
return view;
}
@Override
public String toString() {
return "LayoutState{" +
"mAvailable=" + mAvailable +
", mCurrentPosition=" + mCurrentPosition +
", mItemDirection=" + mItemDirection +
", mLayoutDirection=" + mLayoutDirection +
", mStartLine=" + mStartLine +
", mEndLine=" + mEndLine +
'}';
}
}
@@ -23,6 +23,8 @@ import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.util.AttributeSet;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -36,7 +38,8 @@ import static android.support.v7.widget.RecyclerView.NO_POSITION;
* A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides
* similar functionality to {@link android.widget.ListView}.
*/
public class LinearLayoutManager extends RecyclerView.LayoutManager {
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler {
private static final String TAG = "LinearLayoutManager";
@@ -130,7 +133,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
* Re-used variable to keep anchor information on re-layout.
* Anchor position and coordinate defines the reference point for LLM while doing a layout.
* */
final AnchorInfo mAnchorInfo;
final AnchorInfo mAnchorInfo = new AnchorInfo();
/**
* Creates a vertical LinearLayoutManager
@@ -148,11 +151,26 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
* @param reverseLayout When set to true, layouts from end to start.
*/
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
mAnchorInfo = new AnchorInfo();
setOrientation(orientation);
setReverseLayout(reverseLayout);
}
/**
* Constructor used when layout manager is set in XML by RecyclerView attribute
* "layoutManager". Defaults to vertical orientation.
*
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
*/
public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
setOrientation(properties.orientation);
setReverseLayout(properties.reverseLayout);
setStackFromEnd(properties.stackFromEnd);
}
/**
* {@inheritDoc}
*/
@@ -371,9 +389,13 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
final int firstChild = getPosition(getChildAt(0));
final int viewPosition = position - firstChild;
if (viewPosition >= 0 && viewPosition < childCount) {
return getChildAt(viewPosition);
final View child = getChildAt(viewPosition);
if (getPosition(child) == position) {
return child; // in pre-layout, this may not match
}
}
return null;
// fallback to traversal. This might be necessary in pre-layout.
return super.findViewByPosition(position);
}
/**
@@ -796,6 +818,7 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
}
// override layout from end values for consistency
anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
// if this changes, we should update prepareForDrop as well
if (mShouldReverseLayout) {
anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() -
mPendingScrollPositionOffset;
@@ -941,7 +964,6 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
* <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
* <p>
* Note that scroll position change will not be reflected until the next layout call.
*
* <p>
* If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
*
@@ -1177,9 +1199,8 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
*
* @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
* @param dt This can be used to add additional padding to the visible area. This is used
* to
* detect children that will go out of bounds after scrolling, without actually
* moving them.
* to detect children that will go out of bounds after scrolling, without
* actually moving them.
*/
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
if (dt < 0) {
@@ -1788,6 +1809,40 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager {
return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
}
/**
* @hide This method should be called by ItemTouchHelper only.
*/
@Override
public void prepareForDrop(View view, View target, int x, int y) {
assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation");
ensureLayoutState();
resolveShouldLayoutReverse();
final int myPos = getPosition(view);
final int targetPos = getPosition(target);
final int dropDirection = myPos < targetPos ? LayoutState.ITEM_DIRECTION_TAIL :
LayoutState.ITEM_DIRECTION_HEAD;
if (mShouldReverseLayout) {
if (dropDirection == LayoutState.ITEM_DIRECTION_TAIL) {
scrollToPositionWithOffset(targetPos,
mOrientationHelper.getEndAfterPadding() -
(mOrientationHelper.getDecoratedStart(target) +
mOrientationHelper.getDecoratedMeasurement(view)));
} else {
scrollToPositionWithOffset(targetPos,
mOrientationHelper.getEndAfterPadding() -
mOrientationHelper.getDecoratedEnd(target));
}
} else {
if (dropDirection == LayoutState.ITEM_DIRECTION_HEAD) {
scrollToPositionWithOffset(targetPos, mOrientationHelper.getDecoratedStart(target));
} else {
scrollToPositionWithOffset(targetPos,
mOrientationHelper.getDecoratedEnd(target) -
mOrientationHelper.getDecoratedMeasurement(view));
}
}
}
/**
* Helper class that keeps temporary state while {LayoutManager} is filling out the empty
* space.
@@ -16,10 +16,9 @@
package android.support.v7.widget;
import android.support.v7.widget.AdapterHelper.UpdateOp;
import java.util.List;
import android.support.v7.widget.AdapterHelper.UpdateOp;
import static android.support.v7.widget.AdapterHelper.UpdateOp.ADD;
import static android.support.v7.widget.AdapterHelper.UpdateOp.MOVE;
import static android.support.v7.widget.AdapterHelper.UpdateOp.REMOVE;
@@ -18,6 +18,7 @@
package android.support.v7.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Observable;
import android.graphics.Canvas;
import android.graphics.PointF;
@@ -31,6 +32,8 @@ import android.support.v4.os.TraceCompat;
import android.support.v4.util.ArrayMap;
import android.support.v4.view.InputDeviceCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
@@ -40,9 +43,6 @@ import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.support.v4.widget.ScrollerCompat;
import static android.support.v7.widget.AdapterHelper.UpdateOp;
import static android.support.v7.widget.AdapterHelper.Callback;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -59,10 +59,17 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import com.hippo.ehviewer.R;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static android.support.v7.widget.AdapterHelper.Callback;
import static android.support.v7.widget.AdapterHelper.UpdateOp;
/**
* A flexible view for providing a limited window into a large data set.
*
@@ -127,8 +134,10 @@ import java.util.List;
* <p>
* 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.
*
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_layoutManager
*/
public class RecyclerView extends ViewGroup implements ScrollingView {
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
private static final String TAG = "RecyclerView";
@@ -220,6 +229,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* >Try increasing your pool size and item cache size.
*/
private 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();
@@ -286,6 +297,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
private boolean mAdapterUpdateDuringMeasure;
private final boolean mPostUpdatesOnAnimation;
private final AccessibilityManager mAccessibilityManager;
private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
/**
* Set to true when an adapter data set changed notification is received.
@@ -357,11 +369,17 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
new ItemAnimatorRestoreListener();
private boolean mPostedAnimatorRunner = false;
private RecyclerViewAccessibilityDelegate mAccessibilityDelegate;
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 NestedScrollingChildHelper mScrollingChildHelper;
private final int[] mScrollOffset = new int[2];
private final int[] mScrollConsumed = new int[2];
private final int[] mNestedOffsets = new int[2];
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
@@ -411,6 +429,18 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
mAccessibilityManager = (AccessibilityManager) getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE);
setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
// Create the layoutManager if specified.
if (attrs != null) {
int defStyleRes = 0;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
defStyle, defStyleRes);
String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
a.recycle();
createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
}
mScrollingChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
/**
@@ -431,6 +461,72 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
ViewCompat.setAccessibilityDelegate(this, mAccessibilityDelegate);
}
/**
* 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.length() != 0) { // Can't use isEmpty since it was added in API 9.
className = getFullClassName(context, className);
try {
ClassLoader classLoader;
if (isInEditMode()) {
// Stupid layoutlib cannot handle simple class loaders.
classLoader = this.getClass().getClassLoader();
} else {
classLoader = context.getClassLoader();
}
Class<? extends LayoutManager> layoutManagerClass =
classLoader.loadClass(className).asSubclass(LayoutManager.class);
Constructor<? extends LayoutManager> constructor;
Object[] constructorArgs = null;
try {
constructor = layoutManagerClass
.getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
} catch (NoSuchMethodException e) {
try {
constructor = layoutManagerClass.getConstructor();
} catch (NoSuchMethodException e1) {
e1.initCause(e);
throw new IllegalStateException(attrs.getPositionDescription() +
": Error creating LayoutManager " + className, e1);
}
}
constructor.setAccessible(true);
setLayoutManager(constructor.newInstance(constructorArgs));
} catch (ClassNotFoundException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Unable to find LayoutManager " + className, e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Could not instantiate the LayoutManager: " + className, e);
} catch (InstantiationException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Could not instantiate the LayoutManager: " + className, e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Cannot access non-public constructor " + className, e);
} catch (ClassCastException e) {
throw new IllegalStateException(attrs.getPositionDescription()
+ ": Class is not a LayoutManager " + className, e);
}
}
}
}
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() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
@Override
@@ -772,6 +868,46 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
}
}
/**
* Register a listener that will be notified whenever a child view is attached to or detached
* from RecyclerView.
*
* <p>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.</p>
*
* @param listener Listener to register
*/
public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
if (mOnChildAttachStateListeners == null) {
mOnChildAttachStateListeners = new ArrayList<OnChildAttachStateChangeListener>();
}
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 the {@link LayoutManager} that this RecyclerView will use.
*
@@ -1048,6 +1184,26 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
requestLayout();
}
/**
* Sets the {@link ChildDrawingOrderCallback} to be used for drawing children.
* <p>
* 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.
* <p>
* 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.
*
@@ -1156,7 +1312,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically();
if (canScrollHorizontal || canScrollVertical) {
scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, false, 0, 0);
scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null);
}
}
@@ -1179,30 +1335,26 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
*
* @param x The amount of horizontal scroll request
* @param y The amount of vertical scroll request
* @param fromMotionEvent If request is originated from a MotionEvent, this should be set to
* true and motionX/motionY should be provided, false otherwise.
* @param motionX The x coordinate of the MotionEvent which triggered this scroll. Unused if
* fromMotionEvent is false.
* @param motionY The y coordinate of the MotionEvent which triggered this scroll. Unused if
* fromMotionEvent is false.
* @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, boolean fromMotionEvent, int motionX, int motionY) {
int overscrollX = 0, overscrollY = 0;
int hresult = 0, vresult = 0;
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
eatRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
if (x != 0) {
hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
overscrollX = x - hresult;
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState);
overscrollY = y - vresult;
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
TraceCompat.endSection();
if (supportsChangeAnimations()) {
@@ -1232,19 +1384,27 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
if (!mItemDecorations.isEmpty()) {
invalidate();
}
if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
if (fromMotionEvent) {
pullGlows(motionX, overscrollX, motionY, overscrollY);
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
} else if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
if (ev != null) {
pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
}
considerReleasingGlowsOnScroll(x, y);
}
if (hresult != 0 || vresult != 0) {
dispatchOnScrolled(hresult, vresult);
if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(consumedX, consumedY);
}
if (!awakenScrollBars()) {
invalidate();
}
return hresult != 0 || vresult != 0;
return consumedX != 0 || consumedY != 0;
}
/**
@@ -1437,19 +1597,31 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
"Call setLayoutManager with a non-null argument.");
return false;
}
final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
final boolean canScrollVertical = mLayout.canScrollVertically();
if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) {
velocityX = 0;
}
if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) {
velocityY = 0;
}
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
if (velocityX != 0 || velocityY != 0) {
mViewFlinger.fling(velocityX, velocityY);
return true;
if (velocityX == 0 && velocityY == 0) {
// If we don't have any velocity, return false
return false;
}
if (!dispatchNestedPreFling(velocityX, velocityY)) {
final boolean canScroll = canScrollHorizontal || canScrollVertical;
dispatchNestedFling(velocityX, velocityY, canScroll);
if (canScroll) {
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
mViewFlinger.fling(velocityX, velocityY);
return true;
}
}
return false;
}
@@ -1473,29 +1645,52 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
}
}
/**
* 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(int x, int overscrollX, int y, int overscrollY) {
private void pullGlows(float x, float overscrollX, float y, float overscrollY) {
boolean invalidate = false;
if (overscrollX < 0) {
ensureLeftGlow();
invalidate = mLeftGlow.onPull(-overscrollX / (float) getWidth(),
1f - y / (float) getHeight()) || invalidate;
if (mLeftGlow.onPull(-overscrollX / getWidth(), 1f - y / getHeight())) {
invalidate = true;
}
} else if (overscrollX > 0) {
ensureRightGlow();
invalidate = mRightGlow.onPull(overscrollX / (float) getWidth(),
y / (float) getHeight()) || invalidate;
if (mRightGlow.onPull(overscrollX / getWidth(), y / getHeight())) {
invalidate = true;
}
}
if (overscrollY < 0) {
ensureTopGlow();
invalidate = mTopGlow.onPull(-overscrollY / (float) getHeight(),
x / (float) getWidth()) || invalidate;
if (mTopGlow.onPull(-overscrollY / getHeight(), x / getWidth())) {
invalidate = true;
}
} else if (overscrollY > 0) {
ensureBottomGlow();
invalidate = mBottomGlow.onPull(overscrollY / (float) getHeight(),
1f - x / (float) getWidth()) || invalidate;
if (mBottomGlow.onPull(overscrollY / getHeight(), 1f - x / getWidth())) {
invalidate = true;
}
}
if (invalidate || overscrollX != 0 || overscrollY != 0) {
@@ -1698,6 +1893,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
removeCallbacks(mItemAnimatorRunner);
}
/**
* 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 <b>is not</b>.
@@ -1744,6 +1947,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* for each incoming MotionEvent until the end of the gesture.</p>
*
* @param listener Listener to add
* @see SimpleOnItemTouchListener
*/
public void addOnItemTouchListener(OnItemTouchListener listener) {
mOnItemTouchListeners.add(listener);
@@ -1837,6 +2041,15 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
break;
case MotionEventCompat.ACTION_POINTER_DOWN:
@@ -1879,6 +2092,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
case MotionEvent.ACTION_UP: {
mVelocityTracker.clear();
stopNestedScroll();
} break;
case MotionEvent.ACTION_CANCEL: {
@@ -1888,6 +2102,16 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return mScrollState == SCROLL_STATE_DRAGGING;
}
@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) {
if (dispatchOnItemTouch(e)) {
@@ -1903,14 +2127,29 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
}
mVelocityTracker.addMovement(e);
final MotionEvent vtev = MotionEvent.obtain(e);
final int action = MotionEventCompat.getActionMasked(e);
final int actionIndex = MotionEventCompat.getActionIndex(e);
if (action == MotionEvent.ACTION_DOWN) {
mNestedOffsets[0] = mNestedOffsets[1] = 0;
}
vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);
switch (action) {
case MotionEvent.ACTION_DOWN: {
mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
if (canScrollHorizontally) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
}
if (canScrollVertically) {
nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
}
startNestedScroll(nestedScrollAxis);
} break;
case MotionEventCompat.ACTION_POINTER_DOWN: {
@@ -1929,32 +2168,52 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
final int dx = x - mLastTouchX;
final int dy = y - mLastTouchY;
if (scrollByInternal(canScrollHorizontally ? -dx : 0,
canScrollVertically ? -dy : 0, true, x, y)) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
mLastTouchX = x;
mLastTouchY = y;
} break;
case MotionEventCompat.ACTION_POINTER_UP: {
@@ -1970,6 +2229,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
mVelocityTracker.clear();
releaseGlows();
} break;
@@ -1979,6 +2239,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
} break;
}
vtev.recycle();
return true;
}
@@ -1986,6 +2248,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
stopNestedScroll();
releaseGlows();
setScrollState(SCROLL_STATE_IDLE);
}
@@ -2830,6 +3093,18 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return mLayout.generateLayoutParams(p);
}
/**
* Returns true if RecyclerView is currently running some animations.
* <p>
* 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 mItemAnimator != null && mItemAnimator.isRunning();
}
void saveOldPositions() {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
@@ -3239,6 +3514,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return null;
}
@Override
public boolean drawChild(Canvas canvas, View child, long drawingTime) {
return super.drawChild(canvas, child, drawingTime);
}
/**
* Offset the bounds of all child views by <code>dy</code> pixels.
* Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
@@ -5166,17 +5446,32 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
}
private void dispatchChildDetached(View child) {
if (mAdapter != null) {
mAdapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
}
final ViewHolder viewHolder = getChildViewHolderInt(child);
onChildDetachedFromWindow(child);
if (mAdapter != null && viewHolder != null) {
mAdapter.onViewDetachedFromWindow(viewHolder);
}
if (mOnChildAttachStateListeners != null) {
final int cnt = mOnChildAttachStateListeners.size();
for (int i = cnt - 1; i >= 0; i--) {
mOnChildAttachStateListeners.get(i).onChildViewDetachedFromWindow(child);
}
}
}
private void dispatchChildAttached(View child) {
if (mAdapter != null) {
mAdapter.onViewAttachedToWindow(getChildViewHolderInt(child));
}
final ViewHolder viewHolder = getChildViewHolderInt(child);
onChildAttachedToWindow(child);
if (mAdapter != null && viewHolder != null) {
mAdapter.onViewAttachedToWindow(viewHolder);
}
if (mOnChildAttachStateListeners != null) {
final int cnt = mOnChildAttachStateListeners.size();
for (int i = cnt - 1; i >= 0; i--) {
mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
}
}
}
/**
@@ -5186,6 +5481,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
* a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
* layout managers are provided for general use.
* <p/>
* If the LayoutManager specifies a default constructor or one with the signature
* ({@link Context}, {@link AttributeSet}, {@code int}, {@code int}), RecyclerView will
* instantiate and set the LayoutManager when being inflated. Most used properties can
* be then obtained from {@link #getProperties(Context, AttributeSet, int, int)}. In case
* a LayoutManager specifies both constructors, the non-default constructor will take
* precedence.
*
*/
public static abstract class LayoutManager {
ChildHelper mChildHelper;
@@ -7272,6 +7575,43 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
int action, Bundle args) {
return false;
}
/**
* Parse the xml attributes to get the most common properties used by layout managers.
*
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
* @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
*
* @return an object containing the properties as specified in the attrs.
*/
public static Properties getProperties(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
Properties properties = new Properties();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
defStyleAttr, defStyleRes);
properties.orientation = a.getInt(R.styleable.RecyclerView_android_orientation, VERTICAL);
properties.spanCount = a.getInt(R.styleable.RecyclerView_spanCount, 1);
properties.reverseLayout = a.getBoolean(R.styleable.RecyclerView_reverseLayout, false);
properties.stackFromEnd = a.getBoolean(R.styleable.RecyclerView_stackFromEnd, false);
a.recycle();
return properties;
}
/**
* Some general properties that a LayoutManager may want to use.
*/
public static class Properties {
/** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation */
public int orientation;
/** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount */
public int spanCount;
/** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout */
public boolean reverseLayout;
/** @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd */
public boolean stackFromEnd;
}
}
/**
@@ -7372,8 +7712,10 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
* a touch interaction already in progress even if the RecyclerView is already handling that
* gesture stream itself for the purposes of scrolling.</p>
*
* @see SimpleOnItemTouchListener
*/
public interface OnItemTouchListener {
public static interface OnItemTouchListener {
/**
* Silently observe and/or take over touch events sent to the RecyclerView
* before they are handled by either the RecyclerView itself or its child views.
@@ -7398,8 +7740,44 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* the RecyclerView's coordinate system.
*/
public void onTouchEvent(RecyclerView rv, MotionEvent e);
/**
* Called when a child of RecyclerView does not want RecyclerView and its ancestors to
* intercept touch events with
* {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
*
* @param disallowIntercept True if the child does not want the parent to
* intercept touch events.
* @see ViewParent#requestDisallowInterceptTouchEvent(boolean)
*/
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
/**
* An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies and
* default return values.
* <p>
* You may prefer to extend this class if you don't need to override all methods. Another
* benefit of using this class is future compatibility. As the interface may change, we'll
* always provide a default implementation on this class so that your code won't break when
* you update to a new version of the support library.
*/
public class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
/**
* An OnScrollListener can be set on a RecyclerView to receive messages
* when a scrolling event has occurred on that RecyclerView.
@@ -7412,7 +7790,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* want your components to be able to easily replace the listener use
* RecyclerView#setOnScrollListener.
*/
abstract static public class OnScrollListener {
public abstract static class OnScrollListener {
/**
* Callback method to be invoked when RecyclerView's scroll state changes.
*
@@ -7457,6 +7835,27 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
public void onViewRecycled(ViewHolder holder);
}
/**
* A Listener interface that can be attached to a RecylcerView to get notified
* whenever a ViewHolder is attached to or detached from RecyclerView.
*/
public interface OnChildAttachStateChangeListener {
/**
* Called when a view is attached to the RecyclerView.
*
* @param view The View which is attached to the RecyclerView
*/
public void onChildViewAttachedToWindow(View view);
/**
* Called when a view is detached from RecyclerView.
*
* @param view The View which is being detached from the RecyclerView
*/
public void onChildViewDetachedFromWindow(View view);
}
/**
* A ViewHolder describes an item view and metadata about its place within the RecyclerView.
*
@@ -7875,6 +8274,55 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
}
// NestedScrollingChild
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mScrollingChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return mScrollingChildHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return mScrollingChildHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
mScrollingChildHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return mScrollingChildHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
/**
* {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
* {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
@@ -9331,4 +9779,35 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
this.bottom = bottom;
}
}
@Override
protected int getChildDrawingOrder(int childCount, int i) {
if (mChildDrawingOrderCallback == null) {
return super.getChildDrawingOrder(childCount, i);
} else {
return mChildDrawingOrderCallback.onGetChildDrawingOrder(childCount, i);
}
}
/**
* A callback interface that can be used to alter the drawing order of RecyclerView children.
* <p>
* It works using the {@link ViewGroup#getChildDrawingOrder(int, int)} method, so any case
* that applies to that method also applies to this callback. For example, changing the drawing
* order of two views will not have any effect if their elevation values are different since
* elevation overrides the result of this callback.
*/
public static interface ChildDrawingOrderCallback {
/**
* Returns the index of the child to draw for this iteration. Override this
* if you want to change the drawing order of children. By default, it
* returns i.
*
* @param i The current iteration.
* @return The index of the child to draw this iteration.
*
* @see RecyclerView#setChildDrawingOrderCallback(RecyclerView.ChildDrawingOrderCallback)
*/
public int onGetChildDrawingOrder(int childCount, int i);
}
}
@@ -201,6 +201,18 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
}
};
/**
* Constructor used when layout manager is set in XML by RecyclerView attribute
* "layoutManager". Defaults to single column and vertical.
*/
public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
setOrientation(properties.orientation);
setSpanCount(properties.spanCount);
setReverseLayout(properties.reverseLayout);
}
/**
* Creates a StaggeredGridLayoutManager with given parameters.
*
@@ -537,7 +549,6 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
ensureOrientationHelper();
final AnchorInfo anchorInfo = mAnchorInfo;
anchorInfo.reset();
@@ -577,21 +588,22 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
detachAndScrapAttachedViews(recycler);
mLaidOutInvalidFullSpan = false;
updateMeasureSpecs();
updateLayoutState(anchorInfo.mPosition, state);
if (anchorInfo.mLayoutFromEnd) {
// Layout start.
updateLayoutStateToFillStart(anchorInfo.mPosition, state);
setLayoutStateDirection(LAYOUT_START);
fill(recycler, mLayoutState, state);
// Layout end.
updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
setLayoutStateDirection(LAYOUT_END);
mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state);
} else {
// Layout end.
updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
setLayoutStateDirection(LAYOUT_END);
fill(recycler, mLayoutState, state);
// Layout start.
updateLayoutStateToFillStart(anchorInfo.mPosition, state);
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
setLayoutStateDirection(LAYOUT_START);
mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state);
}
@@ -1254,40 +1266,37 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
}
}
private void updateLayoutStateToFillStart(int anchorPosition, RecyclerView.State state) {
private void updateLayoutState(int anchorPosition, RecyclerView.State state) {
mLayoutState.mAvailable = 0;
mLayoutState.mCurrentPosition = anchorPosition;
int startExtra = 0;
int endExtra = 0;
if (isSmoothScrolling()) {
final int targetPos = state.getTargetScrollPosition();
if (mShouldReverseLayout == targetPos < anchorPosition) {
mLayoutState.mExtra = 0;
} else {
mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
if (targetPos != NO_POSITION) {
if (mShouldReverseLayout == targetPos < anchorPosition) {
endExtra = mPrimaryOrientation.getTotalSpace();
} else {
startExtra = mPrimaryOrientation.getTotalSpace();
}
}
} else {
mLayoutState.mExtra = 0;
}
mLayoutState.mLayoutDirection = LAYOUT_START;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
: ITEM_DIRECTION_HEAD;
// Line of the furthest row.
final boolean clipToPadding = getClipToPadding();
if (clipToPadding) {
mLayoutState.mStartLine = mPrimaryOrientation.getStartAfterPadding() - startExtra;
mLayoutState.mEndLine = mPrimaryOrientation.getEndAfterPadding() + endExtra;
} else {
mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
mLayoutState.mStartLine = -startExtra;
}
}
private void updateLayoutStateToFillEnd(int anchorPosition, RecyclerView.State state) {
mLayoutState.mAvailable = 0;
mLayoutState.mCurrentPosition = anchorPosition;
if (isSmoothScrolling()) {
final int targetPos = state.getTargetScrollPosition();
if (mShouldReverseLayout == targetPos > anchorPosition) {
mLayoutState.mExtra = 0;
} else {
mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
}
} else {
mLayoutState.mExtra = 0;
}
mLayoutState.mLayoutDirection = LAYOUT_END;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
: ITEM_DIRECTION_TAIL;
private void setLayoutStateDirection(int direction) {
mLayoutState.mLayoutDirection = direction;
mLayoutState.mItemDirection = (mShouldReverseLayout == (direction == LAYOUT_START)) ?
ITEM_DIRECTION_TAIL : ITEM_DIRECTION_HEAD;
}
@Override
@@ -1383,31 +1392,25 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
mRemainingSpans.set(0, mSpanCount, true);
// The target position we are trying to reach.
final int targetLine;
/*
* The line until which we can recycle, as long as we add views.
* Keep in mind, it is still the line in layout direction which means; to calculate the
* actual recycle line, we should subtract/add the size in orientation.
*/
final int recycleLine;
// Line of the furthest row.
if (layoutState.mLayoutDirection == LAYOUT_END) {
// ignore padding for recycler
recycleLine = mPrimaryOrientation.getEndAfterPadding() + mLayoutState.mAvailable;
targetLine = recycleLine + mLayoutState.mExtra + mPrimaryOrientation.getEndPadding();
targetLine = layoutState.mEndLine + layoutState.mAvailable;
} else { // LAYOUT_START
// ignore padding for recycler
recycleLine = mPrimaryOrientation.getStartAfterPadding() - mLayoutState.mAvailable;
targetLine = recycleLine - mLayoutState.mExtra -
mPrimaryOrientation.getStartAfterPadding();
targetLine = layoutState.mStartLine - layoutState.mAvailable;
}
updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
if (DEBUG) {
Log.d(TAG, "FILLING targetLine: " + targetLine + "," +
"remaining spans:" + mRemainingSpans + ", state: " + layoutState);
}
// the default coordinate to add new view.
final int defaultNewViewLine = mShouldReverseLayout
? mPrimaryOrientation.getEndAfterPadding()
: mPrimaryOrientation.getStartAfterPadding();
boolean added = false;
while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
View view = layoutState.next(recycler);
LayoutParams lp = ((LayoutParams) view.getLayoutParams());
@@ -1500,18 +1503,21 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
} else {
updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
}
recycle(recycler, mLayoutState, currentSpan, recycleLine);
recycle(recycler, mLayoutState);
added = true;
}
if (DEBUG) {
Log.d(TAG, "fill, " + getChildCount());
if (!added) {
recycle(recycler, mLayoutState);
}
final int diff;
if (mLayoutState.mLayoutDirection == LAYOUT_START) {
final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
return Math.max(0, mLayoutState.mAvailable + (recycleLine - minStart));
diff = mPrimaryOrientation.getStartAfterPadding() - minStart;
} else {
final int max = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
return Math.max(0, mLayoutState.mAvailable + (max - recycleLine));
final int maxEnd = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
diff = maxEnd - mPrimaryOrientation.getEndAfterPadding();
}
return diff > 0 ? Math.min(layoutState.mAvailable, diff) : 0;
}
private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
@@ -1548,19 +1554,40 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
}
}
private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState,
Span updatedSpan, int recycleLine) {
if (layoutState.mLayoutDirection == LAYOUT_START) {
// calculate recycle line
int maxStart = getMaxStart(updatedSpan.getStartLine());
recycleFromEnd(recycler, Math.max(recycleLine, maxStart) +
(mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (layoutState.mAvailable == 0) {
// easy, recycle line is still valid
if (layoutState.mLayoutDirection == LAYOUT_START) {
recycleFromEnd(recycler, layoutState.mEndLine);
} else {
recycleFromStart(recycler, layoutState.mStartLine);
}
} else {
// calculate recycle line
int minEnd = getMinEnd(updatedSpan.getEndLine());
recycleFromStart(recycler, Math.min(recycleLine, minEnd) -
(mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
// scrolling case, recycle line can be shifted by how much space we could cover
// by adding new views
if (layoutState.mLayoutDirection == LAYOUT_START) {
// calculate recycle line
int scrolled = layoutState.mStartLine - getMaxStart(layoutState.mStartLine);
final int line;
if (scrolled < 0) {
line = layoutState.mEndLine;
} else {
line = layoutState.mEndLine - Math.min(scrolled, layoutState.mAvailable);
}
recycleFromEnd(recycler, line);
} else {
// calculate recycle line
int scrolled = getMinEnd(layoutState.mEndLine) - layoutState.mEndLine;
final int line;
if (scrolled < 0) {
line = layoutState.mStartLine;
} else {
line = layoutState.mStartLine + Math.min(scrolled, layoutState.mAvailable);
}
recycleFromStart(recycler, line);
}
}
}
private void appendViewToAllSpans(View view) {
@@ -1602,12 +1629,12 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
final int deletedSize = span.getDeletedSize();
if (layoutDir == LAYOUT_START) {
final int line = span.getStartLine();
if (line + deletedSize < targetLine) {
if (line + deletedSize <= targetLine) {
mRemainingSpans.set(span.mIndex, false);
}
} else {
final int line = span.getEndLine();
if (line - deletedSize > targetLine) {
if (line - deletedSize >= targetLine) {
mRemainingSpans.set(span.mIndex, false);
}
}
@@ -1678,18 +1705,24 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
}
private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
if (DEBUG) {
Log.d(TAG, "recycling from start for line " + line);
}
while (getChildCount() > 0) {
View child = getChildAt(0);
if (mPrimaryOrientation.getDecoratedEnd(child) < line) {
if (mPrimaryOrientation.getDecoratedEnd(child) <= line) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't recycle the last View in a span not to lose span's start/end lines
if (lp.mFullSpan) {
for (int j = 0; j < mSpanCount; j++) {
if (mSpans[j].mViews.size() == 1) {
return;
}
}
for (int j = 0; j < mSpanCount; j++) {
mSpans[j].popStart();
}
} else {
if (lp.mSpan.mViews.size() == 1) {
return;
}
lp.mSpan.popStart();
}
removeAndRecycleView(child, recycler);
@@ -1704,13 +1737,22 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
int i;
for (i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mPrimaryOrientation.getDecoratedStart(child) > line) {
if (mPrimaryOrientation.getDecoratedStart(child) >= line) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't recycle the last View in a span not to lose span's start/end lines
if (lp.mFullSpan) {
for (int j = 0; j < mSpanCount; j++) {
if (mSpans[j].mViews.size() == 1) {
return;
}
}
for (int j = 0; j < mSpanCount; j++) {
mSpans[j].popEnd();
}
} else {
if (lp.mSpan.mViews.size() == 1) {
return;
}
lp.mSpan.popEnd();
}
removeAndRecycleView(child, recycler);
@@ -1860,21 +1902,19 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
ensureOrientationHelper();
final int referenceChildPosition;
final int layoutDir;
if (dt > 0) { // layout towards end
mLayoutState.mLayoutDirection = LAYOUT_END;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
: ITEM_DIRECTION_TAIL;
layoutDir = LAYOUT_END;
referenceChildPosition = getLastChildPosition();
} else {
mLayoutState.mLayoutDirection = LAYOUT_START;
mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
: ITEM_DIRECTION_HEAD;
layoutDir = LAYOUT_START;
referenceChildPosition = getFirstChildPosition();
}
updateLayoutState(referenceChildPosition, state);
setLayoutStateDirection(layoutDir);
mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
final int absDt = Math.abs(dt);
mLayoutState.mAvailable = absDt;
mLayoutState.mExtra = isSmoothScrolling() ? mPrimaryOrientation.getTotalSpace() : 0;
int consumed = fill(recycler, mLayoutState, state);
final int totalScroll;
if (absDt < consumed) {