Skip to content

Commit

Permalink
[TopAppBar] Fix top app bar snapping issue
Browse files Browse the repository at this point in the history
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
  • Loading branch information
drchen authored and josefigueroa168 committed Dec 6, 2021
1 parent b8f2dd5 commit acb7958
Showing 1 changed file with 24 additions and 8 deletions.
32 changes: 24 additions & 8 deletions lib/java/com/google/android/material/appbar/AppBarLayout.java
Expand Up @@ -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)) {
Expand All @@ -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
Expand All @@ -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;
}
Expand Down

0 comments on commit acb7958

Please sign in to comment.