Update LinearLayoutManager from upstream androidx

From https://android.googlesource.com/platform/frameworks/support

Current changes after 1.2.0:
09993fadf2b02004c3fb907266f20519fee154b7..1c6478826f160c1bb67cf5c219083fbc79d2da36

Particularly interesting from the commit history:
"Fixed anchor bug when stack from end."

Change-Id: I8dc69d8e9ac74a5c5e1ba5f59e0b6223b275f1eb
This commit is contained in:
SpiritCroc 2022-09-29 13:53:40 +02:00
parent 3e8b320fef
commit 4b32e30cff

View file

@ -99,6 +99,30 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
*/ */
private boolean mLastStackFromEnd; private boolean mLastStackFromEnd;
/**
* Whether the last layout filled the entire viewport
*
* If the last layout did not fill the viewport, we should not attempt to calculate an
* anchoring based on the current children (other than if one is focused), because there
* isn't any scrolling that could have occurred that would indicate a position in the list
* that needs to be preserved - and in fact, trying to do so could produce the wrong result,
* such as the case of anchoring to a loading spinner at the end of the list.
*/
private boolean mLastLayoutFilledViewport = false;
/**
* Whether the *current* layout filled the entire viewport
*
* This is used to populate mLastLayoutFilledViewport. It exists as a separate variable
* because we need to populate it at the correct moment, which is tricky due to the
* LayoutManager layout being called multiple times. We want to not set it in prelayout
* (because that's not the real layout), but we want to set it the *first* time that the
* actual layout is run, because for certain non-exact layout cases, there are two passes,
* with the second pass being provided an EXACTLY spec (when the actual spec was non-exact).
* This would otherwise incorrectly believe the viewport was filled, because it was provided
* just enough space to contain the content, and thus it would always fill the viewport.
*/
private Boolean mThisLayoutFilledViewport = null;
/** /**
* Defines if layout should be calculated from end to start. * Defines if layout should be calculated from end to start.
@ -185,7 +209,11 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
* *
* @param context Current context, will be used to access resources. * @param context Current context, will be used to access resources.
*/ */
public BetterLinearLayoutManager(Context context) { public BetterLinearLayoutManager(
// Suppressed because fixing it requires a source-incompatible change to a very
// commonly used constructor, for no benefit: the context parameter is unused
@SuppressLint("UnknownNullness") Context context
) {
this(context, DEFAULT_ORIENTATION, false); this(context, DEFAULT_ORIENTATION, false);
} }
@ -195,8 +223,13 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
* #VERTICAL}. * #VERTICAL}.
* @param reverseLayout When set to true, layouts from end to start. * @param reverseLayout When set to true, layouts from end to start.
*/ */
public BetterLinearLayoutManager(Context context, @RecyclerView.Orientation int orientation, public BetterLinearLayoutManager(
boolean reverseLayout) { // Suppressed because fixing it requires a source-incompatible change to a very
// commonly used constructor, for no benefit: the context parameter is unused
@SuppressLint("UnknownNullness") Context context,
@RecyclerView.Orientation int orientation,
boolean reverseLayout
) {
super(context, orientation, reverseLayout); super(context, orientation, reverseLayout);
setOrientation(orientation); setOrientation(orientation);
setReverseLayout(reverseLayout); setReverseLayout(reverseLayout);
@ -210,6 +243,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
* {@link androidx.recyclerview.R.attr#reverseLayout} * {@link androidx.recyclerview.R.attr#reverseLayout}
* {@link androidx.recyclerview.R.attr#stackFromEnd} * {@link androidx.recyclerview.R.attr#stackFromEnd}
*/ */
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public BetterLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, public BetterLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) { int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
@ -228,6 +262,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public RecyclerView.LayoutParams generateDefaultLayoutParams() { public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT); ViewGroup.LayoutParams.WRAP_CONTENT);
@ -262,6 +297,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) { public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
super.onDetachedFromWindow(view, recycler); super.onDetachedFromWindow(view, recycler);
if (mRecycleChildrenOnDetach) { if (mRecycleChildrenOnDetach) {
@ -271,6 +307,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void onInitializeAccessibilityEvent(AccessibilityEvent event) { public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event); super.onInitializeAccessibilityEvent(event);
if (getChildCount() > 0) { if (getChildCount() > 0) {
@ -280,6 +317,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public Parcelable onSaveInstanceState() { public Parcelable onSaveInstanceState() {
if (mPendingSavedState != null) { if (mPendingSavedState != null) {
return new SavedState(mPendingSavedState); return new SavedState(mPendingSavedState);
@ -307,6 +345,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void onRestoreInstanceState(Parcelable state) { public void onRestoreInstanceState(Parcelable state) {
if (state instanceof SavedState) { if (state instanceof SavedState) {
mPendingSavedState = (SavedState) state; mPendingSavedState = (SavedState) state;
@ -452,6 +491,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public View findViewByPosition(int position) { public View findViewByPosition(int position) {
final int childCount = getChildCount(); final int childCount = getChildCount();
if (childCount == 0) { if (childCount == 0) {
@ -546,6 +586,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) { int position) {
LinearSmoothScroller linearSmoothScroller = LinearSmoothScroller linearSmoothScroller =
@ -555,6 +596,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public PointF computeScrollVectorForPosition(int targetPosition) { public PointF computeScrollVectorForPosition(int targetPosition) {
if (getChildCount() == 0) { if (getChildCount() == 0) {
return null; return null;
@ -572,6 +614,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm: // layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor // 1) by checking children and other variables, find an anchor coordinate and an anchor
@ -598,11 +641,25 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
// resolve layout direction // resolve layout direction
resolveShouldLayoutReverse(); resolveShouldLayoutReverse();
boolean layoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// The 2 booleans below are necessary because if we are laying out from the end, and the
// previous measured dimension is different from the new measured value, then any
// previously calculated anchor will be incorrect.
boolean reCalcAnchorDueToVertical = layoutFromEnd
&& getOrientation() == RecyclerView.VERTICAL
&& state.getPreviousMeasuredHeight() != getHeight();
boolean reCalcAnchorDueToHorizontal = layoutFromEnd
&& getOrientation() == RecyclerView.HORIZONTAL
&& state.getPreviousMeasuredWidth() != getWidth();
boolean reCalcAnchor = reCalcAnchorDueToVertical || reCalcAnchorDueToHorizontal
|| mPendingScrollPosition != RecyclerView.NO_POSITION || mPendingSavedState != null;
final View focused = getFocusedChild(); final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION if (!mAnchorInfo.mValid || reCalcAnchor) {
|| mPendingSavedState != null) {
mAnchorInfo.reset(); mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; mAnchorInfo.mLayoutFromEnd = layoutFromEnd;
// calculate anchor position and coordinate // calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo); updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true; mAnchorInfo.mValid = true;
@ -741,13 +798,17 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
// because layout from end may be changed by scroll to position // because layout from end may be changed by scroll to position
// we re-calculate it. // we re-calculate it.
// find which side we should check for gaps. // find which side we should check for gaps.
if (mShouldReverseLayout ^ mStackFromEnd) { if (layoutFromEnd) {
int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
startOffset += fixOffset; startOffset += fixOffset;
endOffset += fixOffset; endOffset += fixOffset;
fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
startOffset += fixOffset; startOffset += fixOffset;
endOffset += fixOffset; endOffset += fixOffset;
if (!state.isPreLayout() && mThisLayoutFilledViewport == null) {
mThisLayoutFilledViewport =
(startOffset <= mOrientationHelper.getStartAfterPadding());
}
} else { } else {
int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
startOffset += fixOffset; startOffset += fixOffset;
@ -755,6 +816,10 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
startOffset += fixOffset; startOffset += fixOffset;
endOffset += fixOffset; endOffset += fixOffset;
if (!state.isPreLayout() && mThisLayoutFilledViewport == null) {
mThisLayoutFilledViewport =
(endOffset >= mOrientationHelper.getEndAfterPadding());
}
} }
} }
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
@ -770,11 +835,14 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void onLayoutCompleted(RecyclerView.State state) { public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state); super.onLayoutCompleted(state);
mPendingSavedState = null; // we don't need this anymore mPendingSavedState = null; // we don't need this anymore
mPendingScrollPosition = RecyclerView.NO_POSITION; mPendingScrollPosition = RecyclerView.NO_POSITION;
mPendingScrollPositionOffset = INVALID_OFFSET; mPendingScrollPositionOffset = INVALID_OFFSET;
mLastLayoutFilledViewport = mThisLayoutFilledViewport != null && mThisLayoutFilledViewport;
mThisLayoutFilledViewport = null;
mAnchorInfo.reset(); mAnchorInfo.reset();
} }
@ -897,6 +965,13 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused)); anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
return true; return true;
} }
// If we did not fill the layout, don't anchor. This prevents, for example,
// anchoring to the bottom of the list when there is a loading indicator.
if (!mLastLayoutFilledViewport) {
return false;
}
if (mLastStackFromEnd != mStackFromEnd) { if (mLastStackFromEnd != mStackFromEnd) {
return false; return false;
} }
@ -1177,6 +1252,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) { RecyclerView.State state) {
if (mOrientation == VERTICAL) { if (mOrientation == VERTICAL) {
@ -1189,6 +1265,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) { RecyclerView.State state) {
if (mOrientation == HORIZONTAL) { if (mOrientation == HORIZONTAL) {
@ -1198,31 +1275,37 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public int computeHorizontalScrollOffset(RecyclerView.State state) { public int computeHorizontalScrollOffset(RecyclerView.State state) {
return computeScrollOffset(state); return computeScrollOffset(state);
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public int computeVerticalScrollOffset(RecyclerView.State state) { public int computeVerticalScrollOffset(RecyclerView.State state) {
return computeScrollOffset(state); return computeScrollOffset(state);
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public int computeHorizontalScrollExtent(RecyclerView.State state) { public int computeHorizontalScrollExtent(RecyclerView.State state) {
return computeScrollExtent(state); return computeScrollExtent(state);
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public int computeVerticalScrollExtent(RecyclerView.State state) { public int computeVerticalScrollExtent(RecyclerView.State state) {
return computeScrollExtent(state); return computeScrollExtent(state);
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public int computeHorizontalScrollRange(RecyclerView.State state) { public int computeHorizontalScrollRange(RecyclerView.State state) {
return computeScrollRange(state); return computeScrollRange(state);
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public int computeVerticalScrollRange(RecyclerView.State state) { public int computeVerticalScrollRange(RecyclerView.State state) {
return computeScrollRange(state); return computeScrollRange(state);
} }
@ -1348,6 +1431,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void collectInitialPrefetchPositions(int adapterItemCount, public void collectInitialPrefetchPositions(int adapterItemCount,
LayoutPrefetchRegistry layoutPrefetchRegistry) { LayoutPrefetchRegistry layoutPrefetchRegistry) {
final boolean fromEnd; final boolean fromEnd;
@ -1428,6 +1512,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
LayoutPrefetchRegistry layoutPrefetchRegistry) { LayoutPrefetchRegistry layoutPrefetchRegistry) {
int delta = (mOrientation == HORIZONTAL) ? dx : dy; int delta = (mOrientation == HORIZONTAL) ? dx : dy;
@ -1470,6 +1555,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public void assertNotInLayoutOrScroll(String message) { public void assertNotInLayoutOrScroll(String message) {
if (mPendingSavedState == null) { if (mPendingSavedState == null) {
super.assertNotInLayoutOrScroll(message); super.assertNotInLayoutOrScroll(message);
@ -2164,6 +2250,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
} }
@Override @Override
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public View onFocusSearchFailed(View focused, int focusDirection, public View onFocusSearchFailed(View focused, int focusDirection,
RecyclerView.Recycler recycler, RecyclerView.State state) { RecyclerView.Recycler recycler, RecyclerView.State state) {
resolveShouldLayoutReverse(); resolveShouldLayoutReverse();
@ -2544,6 +2631,7 @@ public class BetterLinearLayoutManager extends LinearLayoutManager implements
boolean mAnchorLayoutFromEnd; boolean mAnchorLayoutFromEnd;
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public SavedState() { public SavedState() {
} }