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 ;
@@ -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 /**
@@ -1563,12 +1615,12 @@ private boolean canScrollChildren(
15631615
15641616 @ Override
15651617 public void onNestedPreScroll (
1566- CoordinatorLayout coordinatorLayout ,
1618+ @ NonNull CoordinatorLayout coordinatorLayout ,
15671619 @ NonNull T child ,
1568- View target ,
1620+ @ NonNull View target ,
15691621 int dx ,
15701622 int dy ,
1571- int [] consumed ,
1623+ @ NonNull int [] consumed ,
15721624 int type ) {
15731625 if (dy != 0 ) {
15741626 int min ;
@@ -1587,7 +1639,7 @@ public void onNestedPreScroll(
15871639 }
15881640 }
15891641 if (child .isLiftOnScroll ()) {
1590- child .setLiftedState (child .shouldLift ( target ));
1642+ child .setLiftedState (child .shouldBeLifted ( ));
15911643 }
15921644 }
15931645
@@ -1618,7 +1670,10 @@ public void onNestedScroll(
16181670
16191671 @ Override
16201672 public void onStopNestedScroll (
1621- CoordinatorLayout coordinatorLayout , @ NonNull T abl , View target , int type ) {
1673+ @ NonNull CoordinatorLayout coordinatorLayout ,
1674+ @ NonNull T abl ,
1675+ @ NonNull View target ,
1676+ int type ) {
16221677 // onStartNestedScroll for a fling will happen before onStopNestedScroll for the scroll. This
16231678 // isn't necessarily guaranteed yet, but it should be in the future. We use this to our
16241679 // advantage to check if a fling (ViewCompat.TYPE_NON_TOUCH) will start after the touch scroll
@@ -1627,7 +1682,7 @@ public void onStopNestedScroll(
16271682 // If we haven't been flung, or a fling is ending
16281683 snapToChildIfNeeded (coordinatorLayout , abl );
16291684 if (abl .isLiftOnScroll ()) {
1630- abl .setLiftedState (abl .shouldLift ( target ));
1685+ abl .setLiftedState (abl .shouldBeLifted ( ));
16311686 }
16321687 }
16331688
@@ -2023,7 +2078,7 @@ void onFlingFinished(@NonNull CoordinatorLayout parent, @NonNull T layout) {
20232078 // At the end of a manual fling, check to see if we need to snap to the edge-child
20242079 snapToChildIfNeeded (parent , layout );
20252080 if (layout .isLiftOnScroll ()) {
2026- layout .setLiftedState (layout .shouldLift ( findFirstScrollingChild ( parent ) ));
2081+ layout .setLiftedState (layout .shouldBeLifted ( ));
20272082 }
20282083 }
20292084
@@ -2190,9 +2245,7 @@ private void updateAppBarLayoutDrawableState(
21902245 }
21912246
21922247 if (layout .isLiftOnScroll ()) {
2193- // Use first scrolling child as default scrolling view for updating lifted state because
2194- // it represents the content that would be scrolled beneath the app bar.
2195- lifted = layout .shouldLift (findFirstScrollingChild (parent ));
2248+ lifted = layout .shouldBeLifted ();
21962249 }
21972250
21982251 final boolean changed = layout .setLiftedState (lifted );
@@ -2242,19 +2295,6 @@ private static View getAppBarChildOnOffset(
22422295 return null ;
22432296 }
22442297
2245- @ Nullable
2246- private View findFirstScrollingChild (@ NonNull CoordinatorLayout parent ) {
2247- for (int i = 0 , z = parent .getChildCount (); i < z ; i ++) {
2248- final View child = parent .getChildAt (i );
2249- if (child instanceof NestedScrollingChild
2250- || child instanceof AbsListView
2251- || child instanceof ScrollView ) {
2252- return child ;
2253- }
2254- }
2255- return null ;
2256- }
2257-
22582298 @ Override
22592299 int getTopBottomOffsetForScrollingSibling () {
22602300 return getTopAndBottomOffset () + offsetDelta ;
@@ -2391,7 +2431,7 @@ public boolean layoutDependsOn(CoordinatorLayout parent, View child, View depend
23912431 public boolean onDependentViewChanged (
23922432 @ NonNull CoordinatorLayout parent , @ NonNull View child , @ NonNull View dependency ) {
23932433 offsetChildAsNeeded (child , dependency );
2394- updateLiftedStateIfNeeded (child , dependency );
2434+ updateLiftedStateIfNeeded (dependency );
23952435 return false ;
23962436 }
23972437
@@ -2496,11 +2536,11 @@ int getScrollRange(View v) {
24962536 }
24972537 }
24982538
2499- private void updateLiftedStateIfNeeded (View child , View dependency ) {
2539+ private void updateLiftedStateIfNeeded (@ NonNull View dependency ) {
25002540 if (dependency instanceof AppBarLayout ) {
25012541 AppBarLayout appBarLayout = (AppBarLayout ) dependency ;
25022542 if (appBarLayout .isLiftOnScroll ()) {
2503- appBarLayout .setLiftedState (appBarLayout .shouldLift ( child ));
2543+ appBarLayout .setLiftedState (appBarLayout .shouldBeLifted ( ));
25042544 }
25052545 }
25062546 }
0 commit comments