AppBarLayout layout/measure fixes
- We no longer display above the scrolling view
over a collapsed AppBarLayout.
- Make sure we measure the scrolling view to fill
any window inset.
- Make sure the ABL is measured before we measure the
the scrolling view.
- Make sure we use the measured height of views when
calculating the scroll ranges.
- Made some pkg-private methods actually private.
BUG: 23025697
Change-Id: I854d97125c26f1584f13ec554e74247cc8c539d6
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 3f60522..9947b23 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -196,14 +196,15 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ invalidateScrollRanges();
+ }
+
+ @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- // Invalidate the scroll ranges
- mTotalScrollRange = INVALID_SCROLL_RANGE;
- mDownPreScrollRange = INVALID_SCROLL_RANGE;
- mDownPreScrollRange = INVALID_SCROLL_RANGE;
-
mHaveChildWithInterpolator = false;
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
@@ -217,6 +218,13 @@
}
}
+ private void invalidateScrollRanges() {
+ // Invalidate the scroll ranges
+ mTotalScrollRange = INVALID_SCROLL_RANGE;
+ mDownPreScrollRange = INVALID_SCROLL_RANGE;
+ mDownScrollRange = INVALID_SCROLL_RANGE;
+ }
+
@Override
public void setOrientation(int orientation) {
if (orientation != VERTICAL) {
@@ -285,7 +293,7 @@
return new LayoutParams(p);
}
- final boolean hasChildWithInterpolator() {
+ private boolean hasChildWithInterpolator() {
return mHaveChildWithInterpolator;
}
@@ -303,9 +311,7 @@
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int childHeight = ViewCompat.isLaidOut(child)
- ? child.getHeight()
- : child.getMeasuredHeight();
+ final int childHeight = child.getMeasuredHeight();
final int flags = lp.mScrollFlags;
if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
@@ -325,25 +331,24 @@
break;
}
}
- final int top = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
- return mTotalScrollRange = (range - top);
+ return mTotalScrollRange = (range - getTopInset());
}
- final boolean hasScrollableChildren() {
+ private boolean hasScrollableChildren() {
return getTotalScrollRange() != 0;
}
/**
* Return the scroll range when scrolling up from a nested pre-scroll.
*/
- final int getUpNestedPreScrollRange() {
+ private int getUpNestedPreScrollRange() {
return getTotalScrollRange();
}
/**
* Return the scroll range when scrolling down from a nested pre-scroll.
*/
- final int getDownNestedPreScrollRange() {
+ private int getDownNestedPreScrollRange() {
if (mDownPreScrollRange != INVALID_SCROLL_RANGE) {
// If we already have a valid value, return it
return mDownPreScrollRange;
@@ -353,9 +358,7 @@
for (int i = getChildCount() - 1; i >= 0; i--) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int childHeight = ViewCompat.isLaidOut(child)
- ? child.getHeight()
- : child.getMeasuredHeight();
+ final int childHeight = child.getMeasuredHeight();
final int flags = lp.mScrollFlags;
if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) {
@@ -381,7 +384,7 @@
/**
* Return the scroll range when scrolling down from a nested scroll.
*/
- final int getDownNestedScrollRange() {
+ private int getDownNestedScrollRange() {
if (mDownScrollRange != INVALID_SCROLL_RANGE) {
// If we already have a valid value, return it
return mDownScrollRange;
@@ -391,9 +394,7 @@
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- int childHeight = ViewCompat.isLaidOut(child)
- ? child.getHeight()
- : child.getMeasuredHeight();
+ int childHeight = child.getMeasuredHeight();
childHeight += lp.topMargin + lp.bottomMargin;
final int flags = lp.mScrollFlags;
@@ -406,7 +407,7 @@
// For a collapsing exit scroll, we to take the collapsed height into account.
// We also return the range straight away since later views can't scroll
// beneath us
- return range - ViewCompat.getMinimumHeight(child);
+ return mDownScrollRange = (range - ViewCompat.getMinimumHeight(child));
}
} else {
// As soon as a view doesn't have the scroll flag, we end the range calculation.
@@ -456,14 +457,18 @@
return mTargetElevation;
}
- int getPendingAction() {
+ private int getPendingAction() {
return mPendingAction;
}
- void resetPendingAction() {
+ private void resetPendingAction() {
mPendingAction = PENDING_ACTION_NONE;
}
+ private int getTopInset() {
+ return mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
+ }
+
private void setWindowInsets(WindowInsetsCompat insets) {
// Invalidate the total scroll range...
mTotalScrollRange = INVALID_SCROLL_RANGE;
@@ -1074,6 +1079,10 @@
}
}
+ if (ViewCompat.getFitsSystemWindows(child)) {
+ childScrollableHeight -= layout.getTopInset();
+ }
+
if (childScrollableHeight > 0) {
final int offsetForView = absOffset - child.getTop();
final int interpolatedDiff = Math.round(childScrollableHeight *
@@ -1201,7 +1210,7 @@
}
@Override
- public boolean onMeasureChild(CoordinatorLayout parent, View child,
+ public boolean onMeasureChild(CoordinatorLayout parent, final View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final int childLpHeight = child.getLayoutParams().height;
@@ -1217,7 +1226,19 @@
}
final AppBarLayout appBar = findFirstAppBarLayout(dependencies);
- if (appBar != null && ViewCompat.isLaidOut(appBar)) {
+ if (appBar != null && appBar.getVisibility() == View.VISIBLE) {
+ if (appBar.getMeasuredHeight() <= 0 && appBar.getMeasuredWidth() <= 0) {
+ // The AppBar hasn't been measured yet, so we'll post a Runnable to delay
+ // our measure until after the ABLs has happened
+ child.post(new Runnable() {
+ @Override
+ public void run() {
+ child.requestLayout();
+ }
+ });
+ return false;
+ }
+
if (ViewCompat.getFitsSystemWindows(appBar)) {
// If the AppBarLayout is fitting system windows then we need to also,
// otherwise we'll get CoL's compatible layout functionality
@@ -1229,6 +1250,7 @@
// If the measure spec doesn't specify a size, use the current height
availableHeight = parent.getHeight();
}
+
final int height = availableHeight - appBar.getMeasuredHeight()
+ appBar.getTotalScrollRange();
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
@@ -1253,26 +1275,35 @@
((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
if (behavior instanceof Behavior) {
// Offset the child so that it is below the app-bar (with any overlap)
-
- final int appBarOffset = ((Behavior) behavior)
- .getTopBottomOffsetForScrollingSibling();
- final int expandedMax = dependency.getHeight() - mOverlayTop;
- final int collapsedMin = parent.getHeight() - child.getHeight();
-
- if (mOverlayTop != 0 && dependency instanceof AppBarLayout) {
- // If we have an overlap top, and the dependency is an AppBarLayout, we control
- // the offset ourselves based on the appbar's scroll progress. This is so that
- // the scroll happens sequentially rather than linearly
- final int scrollRange = ((AppBarLayout) dependency).getTotalScrollRange();
- setTopAndBottomOffset(AnimationUtils.lerp(expandedMax, collapsedMin,
- Math.abs(appBarOffset) / (float) scrollRange));
- } else {
- setTopAndBottomOffset(dependency.getHeight() - mOverlayTop + appBarOffset);
- }
+ final int offset = ((Behavior) behavior).getTopBottomOffsetForScrollingSibling();
+ setTopAndBottomOffset(dependency.getHeight() + offset
+ - getOverlapForOffset(dependency, offset));
}
return false;
}
+ private int getOverlapForOffset(final View dependency, final int offset) {
+ if (mOverlayTop != 0 && dependency instanceof AppBarLayout) {
+ final AppBarLayout abl = (AppBarLayout) dependency;
+ final int totalScrollRange = abl.getTotalScrollRange();
+ final int preScrollDown = abl.getDownNestedPreScrollRange();
+
+ if (preScrollDown != 0 && (totalScrollRange + offset) <= preScrollDown) {
+ // If we're in a pre-scroll down. Don't use the offset at all.
+ return 0;
+ } else {
+ final int availScrollRange = totalScrollRange - preScrollDown;
+ if (availScrollRange == 0) {
+ // Else we'll use a interpolated ratio of the overlap, depending on offset
+ final float percScrolled = offset / (float) availScrollRange;
+ return MathUtils.constrain(
+ Math.round((1f + percScrolled) * mOverlayTop), 0, mOverlayTop);
+ }
+ }
+ }
+ return mOverlayTop;
+ }
+
/**
* Set the distance that this view should overlap any {@link AppBarLayout}.
*
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 04b9ece..8765e39 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -443,7 +443,7 @@
if (mBehaviorTouchView == null) {
handled |= super.onTouchEvent(ev);
} else if (cancelSuper) {
- if (cancelEvent != null) {
+ if (cancelEvent == null) {
final long now = SystemClock.uptimeMillis();
cancelEvent = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);