6868import androidx .core .view .accessibility .AccessibilityViewCommand ;
6969import androidx .customview .view .AbsSavedState ;
7070import com .google .android .material .animation .AnimationUtils ;
71+ import com .google .android .material .appbar .AppBarLayout .BaseBehavior .SavedState ;
7172import com .google .android .material .internal .ThemeEnforcement ;
7273import com .google .android .material .shape .MaterialShapeDrawable ;
7374import 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