From acb7958c662f37d5a89589f67f05a72ffa0ce816 Mon Sep 17 00:00:00 2001 From: conradchen Date: Thu, 2 Dec 2021 21:02:21 +0000 Subject: [PATCH] [TopAppBar] Fix top app bar snapping issue Unlike other child views of AppBarLayout, CollapsingToolbarLayout will shift its top boundary to consume the window insets as well. Therefore we need to shift the top boundary back when calculating snapping positions to have a consistent logic with other views. This CL also simplifies the calculation so the origin of the y-coordinate used in the calculation will always be the top of the AppBarLayout. PiperOrigin-RevId: 413750156 --- .../android/material/appbar/AppBarLayout.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/java/com/google/android/material/appbar/AppBarLayout.java b/lib/java/com/google/android/material/appbar/AppBarLayout.java index 9529540bfa8..ffac5ac8e6b 100644 --- a/lib/java/com/google/android/material/appbar/AppBarLayout.java +++ b/lib/java/com/google/android/material/appbar/AppBarLayout.java @@ -1526,11 +1526,10 @@ public void onAnimationUpdate(@NonNull ValueAnimator animator) { } private int getChildIndexOnOffset(@NonNull T abl, final int offset) { - final int ablTopInset = abl.getTopInset() + abl.getPaddingTop(); for (int i = 0, count = abl.getChildCount(); i < count; i++) { View child = abl.getChildAt(i); - int top = child.getTop() - ablTopInset; - int bottom = child.getBottom() - ablTopInset; + int top = child.getTop(); + int bottom = child.getBottom(); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (checkFlag(lp.getScrollFlags(), LayoutParams.SCROLL_FLAG_SNAP_MARGINS)) { @@ -1547,18 +1546,29 @@ private int getChildIndexOnOffset(@NonNull T abl, final int offset) { } private void snapToChildIfNeeded(CoordinatorLayout coordinatorLayout, @NonNull T abl) { - final int offset = getTopBottomOffsetForScrollingSibling(); + final int topInset = abl.getTopInset() + abl.getPaddingTop(); + // The "baseline" of scrolling is the top of the first child. We "add" insets and paddings + // to the scrolling amount to align offsets and views with the same y-coordinate. (The origin + // is at the top of the AppBarLayout, so all the coordinates are with negative values.) + final int offset = getTopBottomOffsetForScrollingSibling() - topInset; final int offsetChildIndex = getChildIndexOnOffset(abl, offset); if (offsetChildIndex >= 0) { final View offsetChild = abl.getChildAt(offsetChildIndex); final LayoutParams lp = (LayoutParams) offsetChild.getLayoutParams(); final int flags = lp.getScrollFlags(); - final int ablTopInset = abl.getTopInset() + abl.getPaddingTop(); if ((flags & LayoutParams.FLAG_SNAP) == LayoutParams.FLAG_SNAP) { // We're set the snap, so animate the offset to the nearest edge - int snapTop = -offsetChild.getTop() + ablTopInset; - int snapBottom = -offsetChild.getBottom() + ablTopInset; + int snapTop = -offsetChild.getTop(); + int snapBottom = -offsetChild.getBottom(); + + // If the child is set to fit system windows, its top will include the inset area, we need + // to minus the inset from snapTop to make the calculation consistent. + if (offsetChildIndex == 0 + && ViewCompat.getFitsSystemWindows(abl) + && ViewCompat.getFitsSystemWindows(offsetChild)) { + snapTop -= abl.getTopInset(); + } if (checkFlag(flags, LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED)) { // If the view is set only exit until it is collapsed, we'll abide by that @@ -1581,13 +1591,19 @@ private void snapToChildIfNeeded(CoordinatorLayout coordinatorLayout, @NonNull T snapBottom -= lp.bottomMargin; } - final int newOffset = offset < (snapBottom + snapTop) / 2 ? snapBottom : snapTop; + // Excludes insets and paddings from the offset. (Offsets use the top of child views as + // the origin.) + final int newOffset = calculateSnapOffset(offset, snapBottom, snapTop) + topInset; animateOffsetTo( coordinatorLayout, abl, clamp(newOffset, -abl.getTotalScrollRange(), 0), 0); } } } + private int calculateSnapOffset(int value, int bottom, int top) { + return value < (bottom + top) / 2 ? bottom : top; + } + private static boolean checkFlag(final int flags, final int check) { return (flags & check) == check; }