diff --git a/app/src/main/java/android/support/v7/widget/LayoutState.java b/app/src/main/java/android/support/v7/widget/LayoutState.java
index e62a80a28..f58a4a76e 100644
--- a/app/src/main/java/android/support/v7/widget/LayoutState.java
+++ b/app/src/main/java/android/support/v7/widget/LayoutState.java
@@ -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 +
+ '}';
+ }
}
diff --git a/app/src/main/java/android/support/v7/widget/LinearLayoutManager.java b/app/src/main/java/android/support/v7/widget/LinearLayoutManager.java
index 04d5c2d21..f6a12eb3d 100644
--- a/app/src/main/java/android/support/v7/widget/LinearLayoutManager.java
+++ b/app/src/main/java/android/support/v7/widget/LinearLayoutManager.java
@@ -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 {
* item[10]'s bottom is 20 pixels above the RecyclerView's bottom.
*
* Note that scroll position change will not be reflected until the next layout call. - * *
* 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. diff --git a/app/src/main/java/android/support/v7/widget/OpReorderer.java b/app/src/main/java/android/support/v7/widget/OpReorderer.java index 1a34cba3e..e123ce8dc 100644 --- a/app/src/main/java/android/support/v7/widget/OpReorderer.java +++ b/app/src/main/java/android/support/v7/widget/OpReorderer.java @@ -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; diff --git a/app/src/main/java/android/support/v7/widget/RecyclerView.java b/app/src/main/java/android/support/v7/widget/RecyclerView.java index c610c480b..d856323b6 100644 --- a/app/src/main/java/android/support/v7/widget/RecyclerView.java +++ b/app/src/main/java/android/support/v7/widget/RecyclerView.java @@ -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; *
* 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 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.
+ * 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.
*
@@ -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 is not.
@@ -1744,6 +1947,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView {
* for each incoming MotionEvent until the end of the gesture.
+ * 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 dy 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 RecyclerView 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.
+ *
+ * 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. + *
+ * 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);
+ }
}
diff --git a/app/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java b/app/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
index f98b7f2cd..dcadb5fcc 100644
--- a/app/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/app/src/main/java/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -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) {
diff --git a/app/src/main/res/values/recyclerview_values.xml b/app/src/main/res/values/recyclerview_values.xml
new file mode 100644
index 000000000..79b78b7d3
--- /dev/null
+++ b/app/src/main/res/values/recyclerview_values.xml
@@ -0,0 +1,10 @@
+
+