Skip to content

Commit 698cf9b

Browse files
drchendsn5ft
authored andcommitted
[AppBarLayout] Save and restore scroll state during scroll range recalculation
When scroll range changes, the current scroll position may not make sense anymore. Therefore we need to save the scroll state and restore it after the scroll range is invalidated. This change reuses and refactors the existing saving instance state logic to support this need. Also adds a flag to denote "fully expanded" state to avoid improper scroll position calculation when views are still being initialized. PiperOrigin-RevId: 421348135
1 parent ad97f01 commit 698cf9b

File tree

1 file changed

+48
-16
lines changed

1 file changed

+48
-16
lines changed

lib/java/com/google/android/material/appbar/AppBarLayout.java

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import androidx.core.view.accessibility.AccessibilityViewCommand;
6969
import androidx.customview.view.AbsSavedState;
7070
import com.google.android.material.animation.AnimationUtils;
71+
import com.google.android.material.appbar.AppBarLayout.BaseBehavior.SavedState;
7172
import com.google.android.material.internal.ThemeEnforcement;
7273
import com.google.android.material.shape.MaterialShapeDrawable;
7374
import com.google.android.material.shape.MaterialShapeUtils;
@@ -203,6 +204,8 @@ public interface LiftOnScrollListener {
203204

204205
@Nullable private Drawable statusBarForeground;
205206

207+
private Behavior behavior;
208+
206209
public AppBarLayout(@NonNull Context context) {
207210
this(context, null);
208211
}
@@ -533,10 +536,20 @@ private boolean hasCollapsibleChild() {
533536
}
534537

535538
private void invalidateScrollRanges() {
539+
// Saves the current scrolling state when we need to recalculate scroll ranges
540+
SavedState savedState = behavior == null || totalScrollRange == INVALID_SCROLL_RANGE
541+
? null : behavior.saveScrollState(AbsSavedState.EMPTY_STATE, this);
536542
// Invalidate the scroll ranges
537543
totalScrollRange = INVALID_SCROLL_RANGE;
538544
downPreScrollRange = INVALID_SCROLL_RANGE;
539545
downScrollRange = INVALID_SCROLL_RANGE;
546+
// Restores the previous scrolling state. Don't override if there's a previously saved state
547+
// which has not be restored yet. Multiple re-measuring can happen before the scroll state
548+
// is actually restored. We don't want to restore the state in-between those re-measuring,
549+
// since they can be incorrect.
550+
if (savedState != null) {
551+
behavior.restoreScrollState(savedState, false);
552+
}
540553
}
541554

542555
@Override
@@ -558,7 +571,8 @@ protected void onAttachedToWindow() {
558571
@Override
559572
@NonNull
560573
public CoordinatorLayout.Behavior<AppBarLayout> getBehavior() {
561-
return new AppBarLayout.Behavior();
574+
behavior = new AppBarLayout.Behavior();
575+
return behavior;
562576
}
563577

564578
@RequiresApi(VERSION_CODES.LOLLIPOP)
@@ -1651,6 +1665,9 @@ public boolean onLayoutChild(
16511665
if (savedState.fullyScrolled) {
16521666
// Keep fully scrolled.
16531667
setHeaderTopBottomOffset(parent, abl, -abl.getTotalScrollRange());
1668+
} else if (savedState.fullyExpanded) {
1669+
// Keep fully expanded.
1670+
setHeaderTopBottomOffset(parent, abl, 0);
16541671
} else {
16551672
// Not fully scrolled, restore the visible percetage of child layout.
16561673
View child = abl.getChildAt(savedState.firstVisibleChildIndex);
@@ -2042,7 +2059,25 @@ int getTopBottomOffsetForScrollingSibling() {
20422059

20432060
@Override
20442061
public Parcelable onSaveInstanceState(@NonNull CoordinatorLayout parent, @NonNull T abl) {
2045-
final Parcelable superState = super.onSaveInstanceState(parent, abl);
2062+
Parcelable superState = super.onSaveInstanceState(parent, abl);
2063+
SavedState scrollState = saveScrollState(superState, abl);
2064+
return scrollState == null ? superState : scrollState;
2065+
}
2066+
2067+
@Override
2068+
public void onRestoreInstanceState(
2069+
@NonNull CoordinatorLayout parent, @NonNull T appBarLayout, Parcelable state) {
2070+
if (state instanceof SavedState) {
2071+
restoreScrollState((SavedState) state, true);
2072+
super.onRestoreInstanceState(parent, appBarLayout, savedState.getSuperState());
2073+
} else {
2074+
super.onRestoreInstanceState(parent, appBarLayout, state);
2075+
savedState = null;
2076+
}
2077+
}
2078+
2079+
@Nullable
2080+
SavedState saveScrollState(@Nullable Parcelable superState, @NonNull T abl) {
20462081
final int offset = getTopAndBottomOffset();
20472082

20482083
// Try and find the first visible child...
@@ -2051,42 +2086,38 @@ public Parcelable onSaveInstanceState(@NonNull CoordinatorLayout parent, @NonNul
20512086
final int visBottom = child.getBottom() + offset;
20522087

20532088
if (child.getTop() + offset <= 0 && visBottom >= 0) {
2054-
final SavedState ss = new SavedState(superState);
2055-
ss.fullyScrolled = -getTopAndBottomOffset() >= abl.getTotalScrollRange();
2089+
final SavedState ss =
2090+
new SavedState(superState == null ? AbsSavedState.EMPTY_STATE : superState);
2091+
ss.fullyExpanded = offset == 0;
2092+
ss.fullyScrolled = !ss.fullyExpanded && -offset >= abl.getTotalScrollRange();
20562093
ss.firstVisibleChildIndex = i;
20572094
ss.firstVisibleChildAtMinimumHeight =
20582095
visBottom == (ViewCompat.getMinimumHeight(child) + abl.getTopInset());
20592096
ss.firstVisibleChildPercentageShown = visBottom / (float) child.getHeight();
20602097
return ss;
20612098
}
20622099
}
2063-
2064-
// Else we'll just return the super state
2065-
return superState;
2100+
return null;
20662101
}
20672102

2068-
@Override
2069-
public void onRestoreInstanceState(
2070-
@NonNull CoordinatorLayout parent, @NonNull T appBarLayout, Parcelable state) {
2071-
if (state instanceof SavedState) {
2072-
savedState = (SavedState) state;
2073-
super.onRestoreInstanceState(parent, appBarLayout, savedState.getSuperState());
2074-
} else {
2075-
super.onRestoreInstanceState(parent, appBarLayout, state);
2076-
savedState = null;
2103+
void restoreScrollState(@Nullable SavedState state, boolean force) {
2104+
if (savedState == null || force) {
2105+
savedState = state;
20772106
}
20782107
}
20792108

20802109
/** A {@link Parcelable} implementation for {@link AppBarLayout}. */
20812110
protected static class SavedState extends AbsSavedState {
20822111
boolean fullyScrolled;
2112+
boolean fullyExpanded;
20832113
int firstVisibleChildIndex;
20842114
float firstVisibleChildPercentageShown;
20852115
boolean firstVisibleChildAtMinimumHeight;
20862116

20872117
public SavedState(@NonNull Parcel source, ClassLoader loader) {
20882118
super(source, loader);
20892119
fullyScrolled = source.readByte() != 0;
2120+
fullyExpanded = source.readByte() != 0;
20902121
firstVisibleChildIndex = source.readInt();
20912122
firstVisibleChildPercentageShown = source.readFloat();
20922123
firstVisibleChildAtMinimumHeight = source.readByte() != 0;
@@ -2100,6 +2131,7 @@ public SavedState(Parcelable superState) {
21002131
public void writeToParcel(@NonNull Parcel dest, int flags) {
21012132
super.writeToParcel(dest, flags);
21022133
dest.writeByte((byte) (fullyScrolled ? 1 : 0));
2134+
dest.writeByte((byte) (fullyExpanded ? 1 : 0));
21032135
dest.writeInt(firstVisibleChildIndex);
21042136
dest.writeFloat(firstVisibleChildPercentageShown);
21052137
dest.writeByte((byte) (firstVisibleChildAtMinimumHeight ? 1 : 0));

0 commit comments

Comments
 (0)