7575import com .google .android .material .color .MaterialColors ;
7676import com .google .android .material .drawable .DrawableUtils ;
7777import com .google .android .material .internal .ThemeEnforcement ;
78+ import com .google .android .material .internal .ViewUtils ;
7879import com .google .android .material .motion .MotionUtils ;
7980import com .google .android .material .resources .MaterialResources ;
8081import com .google .android .material .shape .MaterialShapeDrawable ;
8182import com .google .android .material .shape .MaterialShapeUtils ;
8283import java .lang .annotation .Retention ;
8384import java .lang .annotation .RetentionPolicy ;
8485import java .lang .ref .WeakReference ;
86+ import java .util .ArrayDeque ;
8587import java .util .ArrayList ;
8688import java .util .List ;
89+ import java .util .Queue ;
8790
8891/**
8992 * AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of material
@@ -206,7 +209,7 @@ public interface LiftOnScrollListener {
206209
207210 private boolean liftOnScroll ;
208211 @ IdRes private int liftOnScrollTargetViewId ;
209- @ Nullable private WeakReference <View > liftOnScrollTargetView ;
212+ @ Nullable private WeakReference <View > liftOnScrollTargetViewRef ;
210213 private final boolean hasLiftOnScrollColor ;
211214 @ Nullable private ValueAnimator liftOnScrollColorAnimator ;
212215 @ Nullable private AnimatorUpdateListener liftOnScrollColorUpdateListener ;
@@ -760,7 +763,7 @@ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
760763 protected void onDetachedFromWindow () {
761764 super .onDetachedFromWindow ();
762765
763- clearLiftOnScrollTargetView ();
766+ clearLiftOnScrollTargetViewRef ();
764767 }
765768
766769 boolean hasChildWithInterpolator () {
@@ -1086,9 +1089,9 @@ public boolean isLiftOnScroll() {
10861089 public void setLiftOnScrollTargetView (@ Nullable View liftOnScrollTargetView ) {
10871090 this .liftOnScrollTargetViewId = View .NO_ID ;
10881091 if (liftOnScrollTargetView == null ) {
1089- clearLiftOnScrollTargetView ();
1092+ clearLiftOnScrollTargetViewRef ();
10901093 } else {
1091- this .liftOnScrollTargetView = new WeakReference <>(liftOnScrollTargetView );
1094+ this .liftOnScrollTargetViewRef = new WeakReference <>(liftOnScrollTargetView );
10921095 }
10931096 }
10941097
@@ -1099,7 +1102,7 @@ public void setLiftOnScrollTargetView(@Nullable View liftOnScrollTargetView) {
10991102 public void setLiftOnScrollTargetViewId (@ IdRes int liftOnScrollTargetViewId ) {
11001103 this .liftOnScrollTargetViewId = liftOnScrollTargetViewId ;
11011104 // Invalidate cached target view so it will be looked up on next scroll.
1102- clearLiftOnScrollTargetView ();
1105+ clearLiftOnScrollTargetViewRef ();
11031106 }
11041107
11051108 /**
@@ -1111,39 +1114,88 @@ public int getLiftOnScrollTargetViewId() {
11111114 return liftOnScrollTargetViewId ;
11121115 }
11131116
1114- boolean shouldLift (@ Nullable View defaultScrollingView ) {
1115- View scrollingView = findLiftOnScrollTargetView (defaultScrollingView );
1116- if (scrollingView == null ) {
1117- scrollingView = defaultScrollingView ;
1118- }
1117+ boolean shouldBeLifted () {
1118+ final View scrollingView = findLiftOnScrollTargetView ();
11191119 return scrollingView != null
11201120 && (scrollingView .canScrollVertically (-1 ) || scrollingView .getScrollY () > 0 );
11211121 }
11221122
11231123 @ Nullable
1124- private View findLiftOnScrollTargetView (@ Nullable View defaultScrollingView ) {
1124+ private View findLiftOnScrollTargetView () {
1125+ View liftOnScrollTargetView = liftOnScrollTargetViewRef != null
1126+ ? liftOnScrollTargetViewRef .get ()
1127+ : null ;
1128+
1129+ final ViewGroup parent = (ViewGroup ) getParent ();
1130+
11251131 if (liftOnScrollTargetView == null && liftOnScrollTargetViewId != View .NO_ID ) {
1126- View targetView = null ;
1127- if (defaultScrollingView != null ) {
1128- targetView = defaultScrollingView .findViewById (liftOnScrollTargetViewId );
1132+ liftOnScrollTargetView = parent .findViewById (liftOnScrollTargetViewId );
1133+ if (liftOnScrollTargetView != null ) {
1134+ clearLiftOnScrollTargetViewRef ();
1135+ liftOnScrollTargetViewRef = new WeakReference <>(liftOnScrollTargetView );
11291136 }
1130- if (targetView == null && getParent () instanceof ViewGroup ) {
1131- // Assumes the scrolling view is a child of the AppBarLayout's parent,
1132- // which should be true due to the CoordinatorLayout pattern.
1133- targetView = ((ViewGroup ) getParent ()).findViewById (liftOnScrollTargetViewId );
1137+ }
1138+
1139+ return liftOnScrollTargetView != null
1140+ ? liftOnScrollTargetView
1141+ : getDefaultLiftOnScrollTargetView (parent );
1142+ }
1143+
1144+ private View getDefaultLiftOnScrollTargetView (@ NonNull ViewGroup parent ) {
1145+ for (int i = 0 , z = parent .getChildCount (); i < z ; i ++) {
1146+ final View child = parent .getChildAt (i );
1147+ if (hasScrollingBehavior (child )) {
1148+ final View scrollableView = findClosestScrollableView (child );
1149+ if (scrollableView != null ) {
1150+ return scrollableView ;
1151+ }
11341152 }
1135- if (targetView != null ) {
1136- liftOnScrollTargetView = new WeakReference <>(targetView );
1153+ }
1154+ return null ;
1155+ }
1156+
1157+ private boolean hasScrollingBehavior (@ NonNull View view ) {
1158+ if (view .getLayoutParams () instanceof CoordinatorLayout .LayoutParams ) {
1159+ CoordinatorLayout .LayoutParams lp = (CoordinatorLayout .LayoutParams ) view .getLayoutParams ();
1160+ return lp .getBehavior () instanceof ScrollingViewBehavior ;
1161+ }
1162+
1163+ return false ;
1164+ }
1165+
1166+ @ Nullable
1167+ private View findClosestScrollableView (@ NonNull View rootView ) {
1168+ final Queue <View > queue = new ArrayDeque <>();
1169+ queue .add (rootView );
1170+
1171+ while (!queue .isEmpty ()) {
1172+ final View view = queue .remove ();
1173+ if (isScrollableView (view )) {
1174+ return view ;
1175+ } else {
1176+ if (view instanceof ViewGroup ) {
1177+ final ViewGroup viewGroup = (ViewGroup ) view ;
1178+ for (int i = 0 , count = viewGroup .getChildCount (); i < count ; i ++) {
1179+ queue .add (viewGroup .getChildAt (i ));
1180+ }
1181+ }
11371182 }
11381183 }
1139- return liftOnScrollTargetView != null ? liftOnScrollTargetView .get () : null ;
1184+
1185+ return null ;
11401186 }
11411187
1142- private void clearLiftOnScrollTargetView () {
1143- if (liftOnScrollTargetView != null ) {
1144- liftOnScrollTargetView .clear ();
1188+ private boolean isScrollableView (@ NonNull View view ) {
1189+ return view instanceof NestedScrollingChild
1190+ || view instanceof AbsListView
1191+ || view instanceof ScrollView ;
1192+ }
1193+
1194+ private void clearLiftOnScrollTargetViewRef () {
1195+ if (liftOnScrollTargetViewRef != null ) {
1196+ liftOnScrollTargetViewRef .clear ();
11451197 }
1146- liftOnScrollTargetView = null ;
1198+ liftOnScrollTargetViewRef = null ;
11471199 }
11481200
11491201 /**
@@ -1560,12 +1612,12 @@ private boolean canScrollChildren(
15601612
15611613 @ Override
15621614 public void onNestedPreScroll (
1563- CoordinatorLayout coordinatorLayout ,
1615+ @ NonNull CoordinatorLayout coordinatorLayout ,
15641616 @ NonNull T child ,
1565- View target ,
1617+ @ NonNull View target ,
15661618 int dx ,
15671619 int dy ,
1568- int [] consumed ,
1620+ @ NonNull int [] consumed ,
15691621 int type ) {
15701622 if (dy != 0 ) {
15711623 int min ;
@@ -1584,7 +1636,7 @@ public void onNestedPreScroll(
15841636 }
15851637 }
15861638 if (child .isLiftOnScroll ()) {
1587- child .setLiftedState (child .shouldLift ( target ));
1639+ child .setLiftedState (child .shouldBeLifted ( ));
15881640 }
15891641 }
15901642
@@ -1615,7 +1667,10 @@ public void onNestedScroll(
16151667
16161668 @ Override
16171669 public void onStopNestedScroll (
1618- CoordinatorLayout coordinatorLayout , @ NonNull T abl , View target , int type ) {
1670+ @ NonNull CoordinatorLayout coordinatorLayout ,
1671+ @ NonNull T abl ,
1672+ @ NonNull View target ,
1673+ int type ) {
16191674 // onStartNestedScroll for a fling will happen before onStopNestedScroll for the scroll. This
16201675 // isn't necessarily guaranteed yet, but it should be in the future. We use this to our
16211676 // advantage to check if a fling (ViewCompat.TYPE_NON_TOUCH) will start after the touch scroll
@@ -1624,7 +1679,7 @@ public void onStopNestedScroll(
16241679 // If we haven't been flung, or a fling is ending
16251680 snapToChildIfNeeded (coordinatorLayout , abl );
16261681 if (abl .isLiftOnScroll ()) {
1627- abl .setLiftedState (abl .shouldLift ( target ));
1682+ abl .setLiftedState (abl .shouldBeLifted ( ));
16281683 }
16291684 }
16301685
@@ -2020,7 +2075,7 @@ void onFlingFinished(@NonNull CoordinatorLayout parent, @NonNull T layout) {
20202075 // At the end of a manual fling, check to see if we need to snap to the edge-child
20212076 snapToChildIfNeeded (parent , layout );
20222077 if (layout .isLiftOnScroll ()) {
2023- layout .setLiftedState (layout .shouldLift ( findFirstScrollingChild ( parent ) ));
2078+ layout .setLiftedState (layout .shouldBeLifted ( ));
20242079 }
20252080 }
20262081
@@ -2187,9 +2242,7 @@ private void updateAppBarLayoutDrawableState(
21872242 }
21882243
21892244 if (layout .isLiftOnScroll ()) {
2190- // Use first scrolling child as default scrolling view for updating lifted state because
2191- // it represents the content that would be scrolled beneath the app bar.
2192- lifted = layout .shouldLift (findFirstScrollingChild (parent ));
2245+ lifted = layout .shouldBeLifted ();
21932246 }
21942247
21952248 final boolean changed = layout .setLiftedState (lifted );
@@ -2239,19 +2292,6 @@ private static View getAppBarChildOnOffset(
22392292 return null ;
22402293 }
22412294
2242- @ Nullable
2243- private View findFirstScrollingChild (@ NonNull CoordinatorLayout parent ) {
2244- for (int i = 0 , z = parent .getChildCount (); i < z ; i ++) {
2245- final View child = parent .getChildAt (i );
2246- if (child instanceof NestedScrollingChild
2247- || child instanceof AbsListView
2248- || child instanceof ScrollView ) {
2249- return child ;
2250- }
2251- }
2252- return null ;
2253- }
2254-
22552295 @ Override
22562296 int getTopBottomOffsetForScrollingSibling () {
22572297 return getTopAndBottomOffset () + offsetDelta ;
@@ -2388,7 +2428,7 @@ public boolean layoutDependsOn(CoordinatorLayout parent, View child, View depend
23882428 public boolean onDependentViewChanged (
23892429 @ NonNull CoordinatorLayout parent , @ NonNull View child , @ NonNull View dependency ) {
23902430 offsetChildAsNeeded (child , dependency );
2391- updateLiftedStateIfNeeded (child , dependency );
2431+ updateLiftedStateIfNeeded (dependency );
23922432 return false ;
23932433 }
23942434
@@ -2493,11 +2533,11 @@ int getScrollRange(View v) {
24932533 }
24942534 }
24952535
2496- private void updateLiftedStateIfNeeded (View child , View dependency ) {
2536+ private void updateLiftedStateIfNeeded (@ NonNull View dependency ) {
24972537 if (dependency instanceof AppBarLayout ) {
24982538 AppBarLayout appBarLayout = (AppBarLayout ) dependency ;
24992539 if (appBarLayout .isLiftOnScroll ()) {
2500- appBarLayout .setLiftedState (appBarLayout .shouldLift ( child ));
2540+ appBarLayout .setLiftedState (appBarLayout .shouldBeLifted ( ));
25012541 }
25022542 }
25032543 }
0 commit comments