7676import com .google .android .material .color .MaterialColors ;
7777import com .google .android .material .drawable .DrawableUtils ;
7878import com .google .android .material .internal .ThemeEnforcement ;
79+ import com .google .android .material .internal .ViewUtils ;
7980import com .google .android .material .motion .MotionUtils ;
8081import com .google .android .material .resources .MaterialResources ;
8182import com .google .android .material .shape .MaterialShapeDrawable ;
8283import com .google .android .material .shape .MaterialShapeUtils ;
8384import java .lang .annotation .Retention ;
8485import java .lang .annotation .RetentionPolicy ;
8586import java .lang .ref .WeakReference ;
87+ import java .util .ArrayDeque ;
8688import java .util .ArrayList ;
8789import java .util .List ;
90+ import java .util .Queue ;
8891
8992/**
9093 * AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of material
@@ -207,7 +210,7 @@ public interface LiftOnScrollListener {
207210
208211 private boolean liftOnScroll ;
209212 @ IdRes private int liftOnScrollTargetViewId ;
210- @ Nullable private WeakReference <View > liftOnScrollTargetView ;
213+ @ Nullable private WeakReference <View > liftOnScrollTargetViewRef ;
211214 private final boolean hasLiftOnScrollColor ;
212215 @ Nullable private ValueAnimator liftOnScrollColorAnimator ;
213216 @ Nullable private AnimatorUpdateListener liftOnScrollColorUpdateListener ;
@@ -761,7 +764,7 @@ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
761764 protected void onDetachedFromWindow () {
762765 super .onDetachedFromWindow ();
763766
764- clearLiftOnScrollTargetView ();
767+ clearLiftOnScrollTargetViewRef ();
765768 }
766769
767770 boolean hasChildWithInterpolator () {
@@ -1087,9 +1090,9 @@ public boolean isLiftOnScroll() {
10871090 public void setLiftOnScrollTargetView (@ Nullable View liftOnScrollTargetView ) {
10881091 this .liftOnScrollTargetViewId = View .NO_ID ;
10891092 if (liftOnScrollTargetView == null ) {
1090- clearLiftOnScrollTargetView ();
1093+ clearLiftOnScrollTargetViewRef ();
10911094 } else {
1092- this .liftOnScrollTargetView = new WeakReference <>(liftOnScrollTargetView );
1095+ this .liftOnScrollTargetViewRef = new WeakReference <>(liftOnScrollTargetView );
10931096 }
10941097 }
10951098
@@ -1100,7 +1103,7 @@ public void setLiftOnScrollTargetView(@Nullable View liftOnScrollTargetView) {
11001103 public void setLiftOnScrollTargetViewId (@ IdRes int liftOnScrollTargetViewId ) {
11011104 this .liftOnScrollTargetViewId = liftOnScrollTargetViewId ;
11021105 // Invalidate cached target view so it will be looked up on next scroll.
1103- clearLiftOnScrollTargetView ();
1106+ clearLiftOnScrollTargetViewRef ();
11041107 }
11051108
11061109 /**
@@ -1112,39 +1115,88 @@ public int getLiftOnScrollTargetViewId() {
11121115 return liftOnScrollTargetViewId ;
11131116 }
11141117
1115- boolean shouldLift (@ Nullable View defaultScrollingView ) {
1116- View scrollingView = findLiftOnScrollTargetView (defaultScrollingView );
1117- if (scrollingView == null ) {
1118- scrollingView = defaultScrollingView ;
1119- }
1118+ boolean shouldBeLifted () {
1119+ final View scrollingView = findLiftOnScrollTargetView ();
11201120 return scrollingView != null
11211121 && (scrollingView .canScrollVertically (-1 ) || scrollingView .getScrollY () > 0 );
11221122 }
11231123
11241124 @ Nullable
1125- private View findLiftOnScrollTargetView (@ Nullable View defaultScrollingView ) {
1125+ private View findLiftOnScrollTargetView () {
1126+ View liftOnScrollTargetView = liftOnScrollTargetViewRef != null
1127+ ? liftOnScrollTargetViewRef .get ()
1128+ : null ;
1129+
1130+ final ViewGroup parent = (ViewGroup ) getParent ();
1131+
11261132 if (liftOnScrollTargetView == null && liftOnScrollTargetViewId != View .NO_ID ) {
1127- View targetView = null ;
1128- if (defaultScrollingView != null ) {
1129- targetView = defaultScrollingView .findViewById (liftOnScrollTargetViewId );
1133+ liftOnScrollTargetView = parent .findViewById (liftOnScrollTargetViewId );
1134+ if (liftOnScrollTargetView != null ) {
1135+ clearLiftOnScrollTargetViewRef ();
1136+ liftOnScrollTargetViewRef = new WeakReference <>(liftOnScrollTargetView );
11301137 }
1131- if (targetView == null && getParent () instanceof ViewGroup ) {
1132- // Assumes the scrolling view is a child of the AppBarLayout's parent,
1133- // which should be true due to the CoordinatorLayout pattern.
1134- targetView = ((ViewGroup ) getParent ()).findViewById (liftOnScrollTargetViewId );
1138+ }
1139+
1140+ return liftOnScrollTargetView != null
1141+ ? liftOnScrollTargetView
1142+ : getDefaultLiftOnScrollTargetView (parent );
1143+ }
1144+
1145+ private View getDefaultLiftOnScrollTargetView (@ NonNull ViewGroup parent ) {
1146+ for (int i = 0 , z = parent .getChildCount (); i < z ; i ++) {
1147+ final View child = parent .getChildAt (i );
1148+ if (hasScrollingBehavior (child )) {
1149+ final View scrollableView = findClosestScrollableView (child );
1150+ if (scrollableView != null ) {
1151+ return scrollableView ;
1152+ }
11351153 }
1136- if (targetView != null ) {
1137- liftOnScrollTargetView = new WeakReference <>(targetView );
1154+ }
1155+ return null ;
1156+ }
1157+
1158+ private boolean hasScrollingBehavior (@ NonNull View view ) {
1159+ if (view .getLayoutParams () instanceof CoordinatorLayout .LayoutParams ) {
1160+ CoordinatorLayout .LayoutParams lp = (CoordinatorLayout .LayoutParams ) view .getLayoutParams ();
1161+ return lp .getBehavior () instanceof ScrollingViewBehavior ;
1162+ }
1163+
1164+ return false ;
1165+ }
1166+
1167+ @ Nullable
1168+ private View findClosestScrollableView (@ NonNull View rootView ) {
1169+ final Queue <View > queue = new ArrayDeque <>();
1170+ queue .add (rootView );
1171+
1172+ while (!queue .isEmpty ()) {
1173+ final View view = queue .remove ();
1174+ if (isScrollableView (view )) {
1175+ return view ;
1176+ } else {
1177+ if (view instanceof ViewGroup ) {
1178+ final ViewGroup viewGroup = (ViewGroup ) view ;
1179+ for (int i = 0 , count = viewGroup .getChildCount (); i < count ; i ++) {
1180+ queue .add (viewGroup .getChildAt (i ));
1181+ }
1182+ }
11381183 }
11391184 }
1140- return liftOnScrollTargetView != null ? liftOnScrollTargetView .get () : null ;
1185+
1186+ return null ;
11411187 }
11421188
1143- private void clearLiftOnScrollTargetView () {
1144- if (liftOnScrollTargetView != null ) {
1145- liftOnScrollTargetView .clear ();
1189+ private boolean isScrollableView (@ NonNull View view ) {
1190+ return view instanceof NestedScrollingChild
1191+ || view instanceof AbsListView
1192+ || view instanceof ScrollView ;
1193+ }
1194+
1195+ private void clearLiftOnScrollTargetViewRef () {
1196+ if (liftOnScrollTargetViewRef != null ) {
1197+ liftOnScrollTargetViewRef .clear ();
11461198 }
1147- liftOnScrollTargetView = null ;
1199+ liftOnScrollTargetViewRef = null ;
11481200 }
11491201
11501202 /**
@@ -1561,12 +1613,12 @@ private boolean canScrollChildren(
15611613
15621614 @ Override
15631615 public void onNestedPreScroll (
1564- CoordinatorLayout coordinatorLayout ,
1616+ @ NonNull CoordinatorLayout coordinatorLayout ,
15651617 @ NonNull T child ,
1566- View target ,
1618+ @ NonNull View target ,
15671619 int dx ,
15681620 int dy ,
1569- int [] consumed ,
1621+ @ NonNull int [] consumed ,
15701622 int type ) {
15711623 if (dy != 0 ) {
15721624 int min ;
@@ -1585,7 +1637,7 @@ public void onNestedPreScroll(
15851637 }
15861638 }
15871639 if (child .isLiftOnScroll ()) {
1588- child .setLiftedState (child .shouldLift ( target ));
1640+ child .setLiftedState (child .shouldBeLifted ( ));
15891641 }
15901642 }
15911643
@@ -1616,7 +1668,10 @@ public void onNestedScroll(
16161668
16171669 @ Override
16181670 public void onStopNestedScroll (
1619- CoordinatorLayout coordinatorLayout , @ NonNull T abl , View target , int type ) {
1671+ @ NonNull CoordinatorLayout coordinatorLayout ,
1672+ @ NonNull T abl ,
1673+ @ NonNull View target ,
1674+ int type ) {
16201675 // onStartNestedScroll for a fling will happen before onStopNestedScroll for the scroll. This
16211676 // isn't necessarily guaranteed yet, but it should be in the future. We use this to our
16221677 // advantage to check if a fling (ViewCompat.TYPE_NON_TOUCH) will start after the touch scroll
@@ -1625,7 +1680,7 @@ public void onStopNestedScroll(
16251680 // If we haven't been flung, or a fling is ending
16261681 snapToChildIfNeeded (coordinatorLayout , abl );
16271682 if (abl .isLiftOnScroll ()) {
1628- abl .setLiftedState (abl .shouldLift ( target ));
1683+ abl .setLiftedState (abl .shouldBeLifted ( ));
16291684 }
16301685 }
16311686
@@ -2034,7 +2089,7 @@ void onFlingFinished(@NonNull CoordinatorLayout parent, @NonNull T layout) {
20342089 // At the end of a manual fling, check to see if we need to snap to the edge-child
20352090 snapToChildIfNeeded (parent , layout );
20362091 if (layout .isLiftOnScroll ()) {
2037- layout .setLiftedState (layout .shouldLift ( findFirstScrollingChild ( parent ) ));
2092+ layout .setLiftedState (layout .shouldBeLifted ( ));
20382093 }
20392094 }
20402095
@@ -2201,9 +2256,7 @@ private void updateAppBarLayoutDrawableState(
22012256 }
22022257
22032258 if (layout .isLiftOnScroll ()) {
2204- // Use first scrolling child as default scrolling view for updating lifted state because
2205- // it represents the content that would be scrolled beneath the app bar.
2206- lifted = layout .shouldLift (findFirstScrollingChild (parent ));
2259+ lifted = layout .shouldBeLifted ();
22072260 }
22082261
22092262 final boolean changed = layout .setLiftedState (lifted );
@@ -2253,19 +2306,6 @@ private static View getAppBarChildOnOffset(
22532306 return null ;
22542307 }
22552308
2256- @ Nullable
2257- private View findFirstScrollingChild (@ NonNull CoordinatorLayout parent ) {
2258- for (int i = 0 , z = parent .getChildCount (); i < z ; i ++) {
2259- final View child = parent .getChildAt (i );
2260- if (child instanceof NestedScrollingChild
2261- || child instanceof AbsListView
2262- || child instanceof ScrollView ) {
2263- return child ;
2264- }
2265- }
2266- return null ;
2267- }
2268-
22692309 @ Override
22702310 int getTopBottomOffsetForScrollingSibling () {
22712311 return getTopAndBottomOffset () + offsetDelta ;
@@ -2402,7 +2442,7 @@ public boolean layoutDependsOn(CoordinatorLayout parent, View child, View depend
24022442 public boolean onDependentViewChanged (
24032443 @ NonNull CoordinatorLayout parent , @ NonNull View child , @ NonNull View dependency ) {
24042444 offsetChildAsNeeded (child , dependency );
2405- updateLiftedStateIfNeeded (child , dependency );
2445+ updateLiftedStateIfNeeded (dependency );
24062446 return false ;
24072447 }
24082448
@@ -2509,11 +2549,11 @@ int getScrollRange(View v) {
25092549 }
25102550 }
25112551
2512- private void updateLiftedStateIfNeeded (View child , View dependency ) {
2552+ private void updateLiftedStateIfNeeded (@ NonNull View dependency ) {
25132553 if (dependency instanceof AppBarLayout ) {
25142554 AppBarLayout appBarLayout = (AppBarLayout ) dependency ;
25152555 if (appBarLayout .isLiftOnScroll ()) {
2516- appBarLayout .setLiftedState (appBarLayout .shouldLift ( child ));
2556+ appBarLayout .setLiftedState (appBarLayout .shouldBeLifted ( ));
25172557 }
25182558 }
25192559 }
0 commit comments