8282import java .lang .annotation .Retention ;
8383import java .lang .annotation .RetentionPolicy ;
8484import java .lang .ref .WeakReference ;
85+ import java .util .ArrayDeque ;
8586import java .util .ArrayList ;
8687import java .util .LinkedHashSet ;
8788import java .util .List ;
89+ import java .util .Queue ;
8890
8991/**
9092 * AppBarLayout is a vertical {@link LinearLayout} which implements many of the features of material
@@ -227,7 +229,7 @@ public abstract void onUpdate(
227229 private boolean liftOnScroll ;
228230 @ Nullable private ColorStateList liftOnScrollColor ;
229231 @ IdRes private int liftOnScrollTargetViewId ;
230- @ Nullable private WeakReference <View > liftOnScrollTargetView ;
232+ @ Nullable private WeakReference <View > liftOnScrollTargetViewRef ;
231233 @ Nullable private ValueAnimator liftOnScrollColorAnimator ;
232234 @ Nullable private AnimatorUpdateListener liftOnScrollColorUpdateListener ;
233235 private final List <LiftOnScrollListener > liftOnScrollListeners = new ArrayList <>();
@@ -841,7 +843,7 @@ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
841843 protected void onDetachedFromWindow () {
842844 super .onDetachedFromWindow ();
843845
844- clearLiftOnScrollTargetView ();
846+ clearLiftOnScrollTargetViewRef ();
845847 }
846848
847849 boolean hasChildWithInterpolator () {
@@ -1171,9 +1173,9 @@ public boolean isLiftOnScroll() {
11711173 public void setLiftOnScrollTargetView (@ Nullable View liftOnScrollTargetView ) {
11721174 this .liftOnScrollTargetViewId = View .NO_ID ;
11731175 if (liftOnScrollTargetView == null ) {
1174- clearLiftOnScrollTargetView ();
1176+ clearLiftOnScrollTargetViewRef ();
11751177 } else {
1176- this .liftOnScrollTargetView = new WeakReference <>(liftOnScrollTargetView );
1178+ this .liftOnScrollTargetViewRef = new WeakReference <>(liftOnScrollTargetView );
11771179 }
11781180 }
11791181
@@ -1184,7 +1186,7 @@ public void setLiftOnScrollTargetView(@Nullable View liftOnScrollTargetView) {
11841186 public void setLiftOnScrollTargetViewId (@ IdRes int liftOnScrollTargetViewId ) {
11851187 this .liftOnScrollTargetViewId = liftOnScrollTargetViewId ;
11861188 // Invalidate cached target view so it will be looked up on next scroll.
1187- clearLiftOnScrollTargetView ();
1189+ clearLiftOnScrollTargetViewRef ();
11881190 }
11891191
11901192 /** Sets the color of the {@link AppBarLayout} when it is fully lifted. */
@@ -1207,39 +1209,88 @@ public int getLiftOnScrollTargetViewId() {
12071209 return liftOnScrollTargetViewId ;
12081210 }
12091211
1210- boolean shouldLift (@ Nullable View defaultScrollingView ) {
1211- View scrollingView = findLiftOnScrollTargetView (defaultScrollingView );
1212- if (scrollingView == null ) {
1213- scrollingView = defaultScrollingView ;
1214- }
1212+ boolean shouldBeLifted () {
1213+ final View scrollingView = findLiftOnScrollTargetView ();
12151214 return scrollingView != null
12161215 && (scrollingView .canScrollVertically (-1 ) || scrollingView .getScrollY () > 0 );
12171216 }
12181217
12191218 @ Nullable
1220- private View findLiftOnScrollTargetView (@ Nullable View defaultScrollingView ) {
1219+ private View findLiftOnScrollTargetView () {
1220+ View liftOnScrollTargetView = liftOnScrollTargetViewRef != null
1221+ ? liftOnScrollTargetViewRef .get ()
1222+ : null ;
1223+
1224+ final ViewGroup parent = (ViewGroup ) getParent ();
1225+
12211226 if (liftOnScrollTargetView == null && liftOnScrollTargetViewId != View .NO_ID ) {
1222- View targetView = null ;
1223- if (defaultScrollingView != null ) {
1224- targetView = defaultScrollingView .findViewById (liftOnScrollTargetViewId );
1227+ liftOnScrollTargetView = parent .findViewById (liftOnScrollTargetViewId );
1228+ if (liftOnScrollTargetView != null ) {
1229+ clearLiftOnScrollTargetViewRef ();
1230+ liftOnScrollTargetViewRef = new WeakReference <>(liftOnScrollTargetView );
12251231 }
1226- if (targetView == null && getParent () instanceof ViewGroup ) {
1227- // Assumes the scrolling view is a child of the AppBarLayout's parent,
1228- // which should be true due to the CoordinatorLayout pattern.
1229- targetView = ((ViewGroup ) getParent ()).findViewById (liftOnScrollTargetViewId );
1232+ }
1233+
1234+ return liftOnScrollTargetView != null
1235+ ? liftOnScrollTargetView
1236+ : getDefaultLiftOnScrollTargetView (parent );
1237+ }
1238+
1239+ private View getDefaultLiftOnScrollTargetView (@ NonNull ViewGroup parent ) {
1240+ for (int i = 0 , z = parent .getChildCount (); i < z ; i ++) {
1241+ final View child = parent .getChildAt (i );
1242+ if (hasScrollingBehavior (child )) {
1243+ final View scrollableView = findClosestScrollableView (child );
1244+ if (scrollableView != null ) {
1245+ return scrollableView ;
1246+ }
12301247 }
1231- if (targetView != null ) {
1232- liftOnScrollTargetView = new WeakReference <>(targetView );
1248+ }
1249+ return null ;
1250+ }
1251+
1252+ private boolean hasScrollingBehavior (@ NonNull View view ) {
1253+ if (view .getLayoutParams () instanceof CoordinatorLayout .LayoutParams ) {
1254+ CoordinatorLayout .LayoutParams lp = (CoordinatorLayout .LayoutParams ) view .getLayoutParams ();
1255+ return lp .getBehavior () instanceof ScrollingViewBehavior ;
1256+ }
1257+
1258+ return false ;
1259+ }
1260+
1261+ @ Nullable
1262+ private View findClosestScrollableView (@ NonNull View rootView ) {
1263+ final Queue <View > queue = new ArrayDeque <>();
1264+ queue .add (rootView );
1265+
1266+ while (!queue .isEmpty ()) {
1267+ final View view = queue .remove ();
1268+ if (isScrollableView (view )) {
1269+ return view ;
1270+ } else {
1271+ if (view instanceof ViewGroup ) {
1272+ final ViewGroup viewGroup = (ViewGroup ) view ;
1273+ for (int i = 0 , count = viewGroup .getChildCount (); i < count ; i ++) {
1274+ queue .add (viewGroup .getChildAt (i ));
1275+ }
1276+ }
12331277 }
12341278 }
1235- return liftOnScrollTargetView != null ? liftOnScrollTargetView .get () : null ;
1279+
1280+ return null ;
12361281 }
12371282
1238- private void clearLiftOnScrollTargetView () {
1239- if (liftOnScrollTargetView != null ) {
1240- liftOnScrollTargetView .clear ();
1283+ private boolean isScrollableView (@ NonNull View view ) {
1284+ return view instanceof NestedScrollingChild
1285+ || view instanceof AbsListView
1286+ || view instanceof ScrollView ;
1287+ }
1288+
1289+ private void clearLiftOnScrollTargetViewRef () {
1290+ if (liftOnScrollTargetViewRef != null ) {
1291+ liftOnScrollTargetViewRef .clear ();
12411292 }
1242- liftOnScrollTargetView = null ;
1293+ liftOnScrollTargetViewRef = null ;
12431294 }
12441295
12451296 /**
@@ -1655,12 +1706,12 @@ private boolean canScrollChildren(
16551706
16561707 @ Override
16571708 public void onNestedPreScroll (
1658- CoordinatorLayout coordinatorLayout ,
1709+ @ NonNull CoordinatorLayout coordinatorLayout ,
16591710 @ NonNull T child ,
1660- View target ,
1711+ @ NonNull View target ,
16611712 int dx ,
16621713 int dy ,
1663- int [] consumed ,
1714+ @ NonNull int [] consumed ,
16641715 int type ) {
16651716 if (dy != 0 ) {
16661717 int min ;
@@ -1679,7 +1730,7 @@ public void onNestedPreScroll(
16791730 }
16801731 }
16811732 if (child .isLiftOnScroll ()) {
1682- child .setLiftedState (child .shouldLift ( target ));
1733+ child .setLiftedState (child .shouldBeLifted ( ));
16831734 }
16841735 }
16851736
@@ -1710,7 +1761,10 @@ public void onNestedScroll(
17101761
17111762 @ Override
17121763 public void onStopNestedScroll (
1713- CoordinatorLayout coordinatorLayout , @ NonNull T abl , View target , int type ) {
1764+ @ NonNull CoordinatorLayout coordinatorLayout ,
1765+ @ NonNull T abl ,
1766+ @ NonNull View target ,
1767+ int type ) {
17141768 // onStartNestedScroll for a fling will happen before onStopNestedScroll for the scroll. This
17151769 // isn't necessarily guaranteed yet, but it should be in the future. We use this to our
17161770 // advantage to check if a fling (ViewCompat.TYPE_NON_TOUCH) will start after the touch scroll
@@ -1719,7 +1773,7 @@ public void onStopNestedScroll(
17191773 // If we haven't been flung, or a fling is ending
17201774 snapToChildIfNeeded (coordinatorLayout , abl );
17211775 if (abl .isLiftOnScroll ()) {
1722- abl .setLiftedState (abl .shouldLift ( target ));
1776+ abl .setLiftedState (abl .shouldBeLifted ( ));
17231777 }
17241778 }
17251779
@@ -2114,7 +2168,7 @@ void onFlingFinished(@NonNull CoordinatorLayout parent, @NonNull T layout) {
21142168 // At the end of a manual fling, check to see if we need to snap to the edge-child
21152169 snapToChildIfNeeded (parent , layout );
21162170 if (layout .isLiftOnScroll ()) {
2117- layout .setLiftedState (layout .shouldLift ( findFirstScrollingChild ( parent ) ));
2171+ layout .setLiftedState (layout .shouldBeLifted ( ));
21182172 }
21192173 }
21202174
@@ -2281,9 +2335,7 @@ private void updateAppBarLayoutDrawableState(
22812335 }
22822336
22832337 if (layout .isLiftOnScroll ()) {
2284- // Use first scrolling child as default scrolling view for updating lifted state because
2285- // it represents the content that would be scrolled beneath the app bar.
2286- lifted = layout .shouldLift (findFirstScrollingChild (parent ));
2338+ lifted = layout .shouldBeLifted ();
22872339 }
22882340
22892341 final boolean changed = layout .setLiftedState (lifted );
@@ -2333,19 +2385,6 @@ private static View getAppBarChildOnOffset(
23332385 return null ;
23342386 }
23352387
2336- @ Nullable
2337- private View findFirstScrollingChild (@ NonNull CoordinatorLayout parent ) {
2338- for (int i = 0 , z = parent .getChildCount (); i < z ; i ++) {
2339- final View child = parent .getChildAt (i );
2340- if (child instanceof NestedScrollingChild
2341- || child instanceof AbsListView
2342- || child instanceof ScrollView ) {
2343- return child ;
2344- }
2345- }
2346- return null ;
2347- }
2348-
23492388 @ Override
23502389 int getTopBottomOffsetForScrollingSibling () {
23512390 return getTopAndBottomOffset () + offsetDelta ;
@@ -2482,7 +2521,7 @@ public boolean layoutDependsOn(CoordinatorLayout parent, View child, View depend
24822521 public boolean onDependentViewChanged (
24832522 @ NonNull CoordinatorLayout parent , @ NonNull View child , @ NonNull View dependency ) {
24842523 offsetChildAsNeeded (child , dependency );
2485- updateLiftedStateIfNeeded (child , dependency );
2524+ updateLiftedStateIfNeeded (dependency );
24862525 return false ;
24872526 }
24882527
@@ -2587,11 +2626,11 @@ int getScrollRange(View v) {
25872626 }
25882627 }
25892628
2590- private void updateLiftedStateIfNeeded (View child , View dependency ) {
2629+ private void updateLiftedStateIfNeeded (@ NonNull View dependency ) {
25912630 if (dependency instanceof AppBarLayout ) {
25922631 AppBarLayout appBarLayout = (AppBarLayout ) dependency ;
25932632 if (appBarLayout .isLiftOnScroll ()) {
2594- appBarLayout .setLiftedState (appBarLayout .shouldLift ( child ));
2633+ appBarLayout .setLiftedState (appBarLayout .shouldBeLifted ( ));
25952634 }
25962635 }
25972636 }
0 commit comments