am 5c3341f5: am 4c37ebd8: Merge "Window inset support for CoL + AppBarLayout" into mnc-dev

* commit '5c3341f5ab819b806ca8f63af6f9e27782e88401':
  Window inset support for CoL + AppBarLayout

GitOrigin-RevId=72659081a3301eeadd659c7a063555260decdb90

PiperOrigin-RevId: 140548433
This commit is contained in:
Chris Banes 2016-11-30 01:16:07 +00:00 committed by Travis Collins
parent ea53e10e1d
commit e8435b7bbd
10 changed files with 507 additions and 84 deletions

View File

@ -5,6 +5,7 @@ package android.support.design.widget {
ctor public AppBarLayout(android.content.Context, android.util.AttributeSet);
method public void addOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
method public float getTargetElevation();
method public final int getTotalScrollRange();
method public void removeOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
method public void setTargetElevation(float);
}
@ -56,13 +57,18 @@ package android.support.design.widget {
ctor public CollapsingToolbarLayout(android.content.Context);
ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet);
ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet, int);
method public int getForegroundScrimColor();
method public android.graphics.drawable.Drawable getContentScrim();
method public android.graphics.drawable.Drawable getStatusBarScrim();
method public void setCollapsedTitleTextAppearance(int);
method public void setCollapsedTitleTextColor(int);
method public void setContentScrim(android.graphics.drawable.Drawable);
method public void setContentScrimColor(int);
method public void setContentScrimResource(int);
method public void setExpandedTitleColor(int);
method public void setExpandedTitleTextAppearance(int);
method public void setForegroundScrimColor(int);
method public void setForegroundScrimColorResource(int);
method public void setStatusBarScrim(android.graphics.drawable.Drawable);
method public void setStatusBarScrimColor(int);
method public void setStatusBarScrimResource(int);
method public void setTitle(java.lang.CharSequence);
}
@ -89,12 +95,17 @@ package android.support.design.widget {
method public void dispatchDependentViewsChanged(android.view.View);
method public boolean doViewsOverlap(android.view.View, android.view.View);
method public java.util.List<android.view.View> getDependencies(android.view.View);
method public android.graphics.drawable.Drawable getStatusBarBackground();
method public boolean isPointInChildBounds(android.view.View, int, int);
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onDraw(android.graphics.Canvas);
method protected void onLayout(boolean, int, int, int, int);
method public void onLayoutChild(android.view.View, int);
method public void onMeasureChild(android.view.View, int, int, int, int);
method public void setStatusBarBackground(android.graphics.drawable.Drawable);
method public void setStatusBarBackgroundColor(int);
method public void setStatusBarBackgroundResource(int);
}
public static abstract class CoordinatorLayout.Behavior {
@ -106,6 +117,7 @@ package android.support.design.widget {
method public static java.lang.Object getTag(android.view.View);
method public boolean isDirty(android.support.design.widget.CoordinatorLayout, V);
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, V, android.view.View);
method public android.support.v4.view.WindowInsetsCompat onApplyWindowInsets(android.support.design.widget.CoordinatorLayout, V, android.support.v4.view.WindowInsetsCompat);
method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, V, android.view.View);
method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, V, int);

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.design.widget;
import android.support.v4.view.OnApplyWindowInsetsListener;
import android.view.View;
interface CoordinatorLayoutInsetsHelper {
void setupForWindowInsets(View view, OnApplyWindowInsetsListener insetsListener);
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.design.widget;
import android.support.v4.view.OnApplyWindowInsetsListener;
import android.support.v4.view.ViewCompat;
import android.view.View;
class CoordinatorLayoutInsetsHelperLollipop implements CoordinatorLayoutInsetsHelper {
public void setupForWindowInsets(View view, OnApplyWindowInsetsListener insetsListener) {
if (ViewCompat.getFitsSystemWindows(view)) {
// First apply the insets listener
ViewCompat.setOnApplyWindowInsetsListener(view, insetsListener);
// Now set the sys ui flags to enable us to lay out in the window insets
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
}
}

View File

@ -19,6 +19,7 @@
<public type="attr" name="backgroundTint"/>
<public type="attr" name="backgroundTintMode"/>
<public type="attr" name="collapsedTitleTextAppearance"/>
<public type="attr" name="contentScrim"/>
<public type="attr" name="elevation"/>
<public type="attr" name="errorEnabled"/>
<public type="attr" name="errorTextAppearance"/>
@ -28,7 +29,6 @@
<public type="attr" name="expandedTitleMarginStart"/>
<public type="attr" name="expandedTitleTextAppearance"/>
<public type="attr" name="fabSize"/>
<public type="attr" name="foregroundScrimColor"/>
<public type="attr" name="headerLayout"/>
<public type="attr" name="hintTextAppearance"/>
<public type="attr" name="itemBackground"/>
@ -38,6 +38,8 @@
<public type="attr" name="menu"/>
<public type="attr" name="pressedTranslationZ"/>
<public type="attr" name="rippleColor"/>
<public type="attr" name="statusBarBackground"/>
<public type="attr" name="statusBarScrim"/>
<public type="attr" name="tabBackground"/>
<public type="attr" name="tabContentStart"/>
<public type="attr" name="tabGravity"/>

View File

@ -21,6 +21,7 @@
<public type="style" name="TextAppearance.Design.Tab"/>
<public type="style" name="Widget.Design.AppBarLayout"/>
<public type="style" name="Widget.Design.CollapsingToolbar"/>
<public type="style" name="Widget.Design.CoordinatorLayout"/>
<public type="style" name="Widget.Design.FloatingActionButton"/>
<public type="style" name="Widget.Design.NavigationView"/>
<public type="style" name="Widget.Design.Snackbar"/>

View File

@ -94,6 +94,8 @@
layout_keyline="index" where index is a 0-based index into
this array. -->
<attr name="keylines" format="reference"/>
<!-- Drawable to display behind the status bar when the view is set to draw behind it. -->
<attr name="statusBarBackground" format="reference"/>
</declare-styleable>
<declare-styleable name="CoordinatorLayout_LayoutParams">
@ -207,9 +209,19 @@
<attr name="expandedTitleMarginStart" format="dimension"/>
<attr name="expandedTitleMarginBottom" format="dimension"/>
<attr name="expandedTitleMarginEnd" format="dimension"/>
<!-- The text appearance of the CollapsingToolbarLayouts title when it is fully
'expanded' -->
<attr name="expandedTitleTextAppearance" format="reference"/>
<!-- The text appearance of the CollapsingToolbarLayouts title when it is fully
'collapsed' -->
<attr name="collapsedTitleTextAppearance" format="reference"/>
<attr name="foregroundScrimColor" format="color"/>
<!-- The drawable to use as a scrim on top of the CollapsingToolbarLayouts content when
it has been scrolled sufficiently off screen. -->
<attr name="contentScrim" format="color"/>
<!-- The drawable to use as a scrim for the status bar content when the
CollapsingToolbarLayout has been scrolled sufficiently off screen. Only works on
Lollipop with the correct setup. -->
<attr name="statusBarScrim" format="color" />
</declare-styleable>
<declare-styleable name="CollapsingAppBarLayout_LayoutParams">

View File

@ -97,7 +97,7 @@
<item name="expandedTitleMarginBottom">20dp</item>
<item name="expandedTitleTextAppearance">@style/TextAppearance.AppCompat.Headline</item>
<item name="collapsedTitleTextAppearance">@style/TextAppearance.AppCompat.Widget.ActionBar.Title</item>
<item name="foregroundScrimColor">@android:color/transparent</item>
<item name="statusBarScrim">?attr/colorPrimaryDark</item>
</style>
<style name="Widget.Design.AppBarLayout" parent="android:Widget">
@ -105,5 +105,9 @@
<item name="android:background">?attr/colorPrimary</item>
</style>
<style name="Widget.Design.CoordinatorLayout" parent="android:Widget">
<item name="statusBarBackground">?attr/colorPrimaryDark</item>
</style>
</resources>

View File

@ -21,6 +21,7 @@ import android.content.res.TypedArray;
import android.support.annotation.IntDef;
import android.support.design.R;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
import android.support.v4.widget.ScrollerCompat;
import android.util.AttributeSet;
import android.view.View;
@ -118,6 +119,8 @@ public class AppBarLayout extends LinearLayout {
private float mTargetElevation;
private WindowInsetsCompat mLastInsets;
private final List<WeakReference<OnOffsetChangedListener>> mListeners;
public AppBarLayout(Context context) {
@ -140,6 +143,16 @@ public class AppBarLayout extends LinearLayout {
mListeners = new ArrayList<>();
ViewCompat.setElevation(this, mTargetElevation);
ViewCompat.setOnApplyWindowInsetsListener(this,
new android.support.v4.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
setWindowInsets(insets);
return insets.consumeSystemWindowInsets();
}
});
}
/**
@ -238,11 +251,11 @@ public class AppBarLayout extends LinearLayout {
}
/**
* Return the scroll range of any children
* Returns the scroll range of all children.
*
* @return the scroll range in px
*/
final int getTotalScrollRange() {
public final int getTotalScrollRange() {
if (mTotalScrollRange != INVALID_SCROLL_RANGE) {
return mTotalScrollRange;
}
@ -262,9 +275,10 @@ public class AppBarLayout extends LinearLayout {
if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
// For a collapsing scroll, we to take the collapsed height into account.
// We also return the range straight away since later views can't scroll beneath
// We also break straight away since later views can't scroll beneath
// us
return range - ViewCompat.getMinimumHeight(child);
range -= ViewCompat.getMinimumHeight(child);
break;
}
} else {
// As soon as a view doesn't have the scroll flag, we end the range calculation.
@ -272,7 +286,8 @@ public class AppBarLayout extends LinearLayout {
break;
}
}
return mTotalScrollRange = range;
final int top = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
return mTotalScrollRange = (range - top);
}
final boolean hasScrollableChildren() {
@ -361,16 +376,17 @@ public class AppBarLayout extends LinearLayout {
}
final int getMinimumHeightForVisibleOverlappingContent() {
final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
final int minHeight = ViewCompat.getMinimumHeight(this);
if (minHeight != 0) {
// If this layout has a min height, use it (doubled)
return minHeight * 2;
return (minHeight * 2) + topInset;
}
// Otherwise, we'll use twice the min height of our last child
final int childCount = getChildCount();
return childCount >= 1
? ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) * 2
? (ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) * 2) + topInset
: 0;
}
@ -398,6 +414,21 @@ public class AppBarLayout extends LinearLayout {
return mTargetElevation;
}
private void setWindowInsets(WindowInsetsCompat insets) {
// Invalidate the total scroll range...
mTotalScrollRange = INVALID_SCROLL_RANGE;
mLastInsets = insets;
// Now dispatch them to our children
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
insets = ViewCompat.dispatchApplyWindowInsets(child, insets);
if (insets.isConsumed()) {
break;
}
}
}
public static class LayoutParams extends LinearLayout.LayoutParams {
/** @hide */
@ -886,15 +917,15 @@ public class AppBarLayout extends LinearLayout {
}
final AppBarLayout appBar = findFirstAppBarLayout(dependencies);
if (appBar != null) {
if (appBar.getMeasuredWidth() == 0 || appBar.getMeasuredHeight() == 0) {
// If the AppBar hasn't been measured yet, we need to do it now
parent.onMeasureChild(appBar, parentWidthMeasureSpec,
widthUsed, parentHeightMeasureSpec, heightUsed);
if (appBar != null && ViewCompat.isLaidOut(appBar)) {
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
ViewCompat.setFitsSystemWindows(child, true);
}
final int scrollRange = appBar.getTotalScrollRange();
final int height = MeasureSpec.getSize(parentHeightMeasureSpec)
final int height = parent.getHeight()
- appBar.getMeasuredHeight() + scrollRange;
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.AT_MOST);

View File

@ -19,21 +19,22 @@ package android.support.design.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.support.annotation.ColorRes;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.support.design.R;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
import java.lang.annotation.Retention;
@ -52,7 +53,7 @@ import java.lang.annotation.RetentionPolicy;
*
* <h3>Foreground scrim</h3>
* A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold.
* You can change the color via {@link #setForegroundScrimColor(int)}.
* You can change this via {@link #setContentScrim(Drawable)}.
*
* <h3>Parallax scrolling children</h3>
* Child views can opt to be scrolled within this layout in a parallax fashion.
@ -66,11 +67,12 @@ import java.lang.annotation.RetentionPolicy;
*
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_foregroundScrimColor
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_contentScrim
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_statusBarScrim
*/
public class CollapsingToolbarLayout extends FrameLayout {
@ -86,13 +88,18 @@ public class CollapsingToolbarLayout extends FrameLayout {
private final Rect mRect = new Rect();
private final CollapsingTextHelper mCollapsingTextHelper;
private int mForegroundScrimColor;
private int mCurrentForegroundColor;
private boolean mScrimIsShown;
private Drawable mContentScrim;
private Drawable mStatusBarScrim;
private int mScrimAlpha;
private boolean mScrimsAreShown;
private ValueAnimatorCompat mScrimAnimator;
private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener;
private int mCurrentOffset;
private WindowInsetsCompat mLastInsets;
public CollapsingToolbarLayout(Context context) {
this(context, null);
}
@ -150,12 +157,31 @@ public class CollapsingToolbarLayout extends FrameLayout {
R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
mCollapsingTextHelper.setCollapsedTextAppearance(tp);
mForegroundScrimColor = a.getColor(
R.styleable.CollapsingToolbarLayout_foregroundScrimColor, 0);
setContentScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim));
setStatusBarScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim));
a.recycle();
setWillNotDraw(false);
ViewCompat.setOnApplyWindowInsetsListener(this,
new android.support.v4.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
mLastInsets = insets;
for (int i = 0, z = getChildCount(); i < z; i++) {
View child = getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.height != LayoutParams.MATCH_PARENT) {
lp.topMargin = insets.getSystemWindowInsetTop();
}
}
return insets.consumeSystemWindowInsets();
}
});
}
@Override
@ -200,12 +226,24 @@ public class CollapsingToolbarLayout extends FrameLayout {
// If we don't have a toolbar, the scrim will be not be drawn in drawChild() below.
// Instead, we draw it here, before our collapsing text.
if (mToolbar == null && Color.alpha(mCurrentForegroundColor) > 0) {
canvas.drawColor(mCurrentForegroundColor);
if (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) {
mContentScrim.mutate().setAlpha(mScrimAlpha);
mContentScrim.draw(canvas);
}
// Let the collapsing text helper draw it's text
mCollapsingTextHelper.draw(canvas);
// Now draw the status bar scrim
if (mStatusBarScrim != null && mScrimAlpha > 0) {
final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
if (topInset > 0) {
mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(),
topInset - mCurrentOffset);
mStatusBarScrim.mutate().setAlpha(mScrimAlpha);
mStatusBarScrim.draw(canvas);
}
}
}
@Override
@ -213,14 +251,23 @@ public class CollapsingToolbarLayout extends FrameLayout {
// This is a little weird. Our scrim needs to be behind the Toolbar (if it is present),
// but in front of any other children which are behind it. To do this we intercept the
// drawChild() call, and draw our scrim first when drawing the toolbar
if (child == mToolbar && Color.alpha(mCurrentForegroundColor) > 0) {
canvas.drawColor(mCurrentForegroundColor);
if (child == mToolbar && mContentScrim != null && mScrimAlpha > 0) {
mContentScrim.mutate().setAlpha(mScrimAlpha);
mContentScrim.draw(canvas);
}
// Carry on drawing the child...
return super.drawChild(canvas, child, drawingTime);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mContentScrim != null) {
mContentScrim.setBounds(0, 0, w, h);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@ -264,16 +311,16 @@ public class CollapsingToolbarLayout extends FrameLayout {
}
private void showScrim() {
if (!mScrimIsShown) {
animateScrim(Color.alpha(mForegroundScrimColor));
mScrimIsShown = true;
if (!mScrimsAreShown) {
animateScrim(255);
mScrimsAreShown = true;
}
}
private void hideScrim() {
if (mScrimIsShown) {
if (mScrimsAreShown) {
animateScrim(0);
mScrimIsShown = false;
mScrimsAreShown = false;
}
}
@ -285,18 +332,14 @@ public class CollapsingToolbarLayout extends FrameLayout {
mScrimAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimatorCompat animator) {
final int newColor = ColorUtils.setAlphaComponent(
mForegroundScrimColor, animator.getAnimatedIntValue());
if (newColor != mCurrentForegroundColor) {
mCurrentForegroundColor = newColor;
// We need to manually invalidate ourselves and the Toolbar to ensure the scrim
// is drawn
invalidate();
if (mToolbar != null) {
mToolbar.invalidate();
final int newAlpha = animator.getAnimatedIntValue();
if (newAlpha != mScrimAlpha) {
final Drawable contentScrim = mContentScrim;
if (contentScrim != null && mToolbar != null) {
ViewCompat.postInvalidateOnAnimation(mToolbar);
}
mScrimAlpha = newAlpha;
ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
}
}
});
@ -304,44 +347,126 @@ public class CollapsingToolbarLayout extends FrameLayout {
mScrimAnimator.cancel();
}
mScrimAnimator.setIntValues(Color.alpha(mCurrentForegroundColor), targetAlpha);
mScrimAnimator.setIntValues(mScrimAlpha, targetAlpha);
mScrimAnimator.start();
}
/**
* Set the color to use for the foreground scrim. Providing a transparent color which disable
* the scrim.
* Set the drawable to use for the content scrim from resources. Providing null will disable
* the scrim functionality.
*
* @param color ARGB color to use
* @param drawable the drawable to display
*
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_foregroundScrimColor
* @see #getForegroundScrimColor()
* @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
* @see #getContentScrim()
*/
public void setForegroundScrimColor(int color) {
mForegroundScrimColor = color;
public void setContentScrim(@Nullable Drawable drawable) {
if (mContentScrim != drawable) {
if (mContentScrim != null) {
mContentScrim.setCallback(null);
}
mContentScrim = drawable;
drawable.setBounds(0, 0, getWidth(), getHeight());
drawable.setCallback(this);
drawable.mutate().setAlpha(mScrimAlpha);
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**
* Set the color to use for the foreground scrim from resources. Providing a transparent color
* which disable the scrim.
* Set the color to use for the content scrim.
*
* @param resId color resource id
* @param color the color to display
*
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_foregroundScrimColor
* @see #getForegroundScrimColor()
* @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
* @see #getContentScrim()
*/
public void setForegroundScrimColorResource(@ColorRes int resId) {
mForegroundScrimColor = getResources().getColor(resId);
public void setContentScrimColor(int color) {
setContentScrim(new ColorDrawable(color));
}
/**
* Returns the color which is used for the foreground scrim.
* Set the drawable to use for the content scrim from resources.
*
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_foregroundScrimColor
* @see #setForegroundScrimColor(int)
* @param resId drawable resource id
*
* @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
* @see #getContentScrim()
*/
public int getForegroundScrimColor() {
return mForegroundScrimColor;
public void setContentScrimResource(@DrawableRes int resId) {
setContentScrim(ContextCompat.getDrawable(getContext(), resId));
}
/**
* Returns the drawable which is used for the foreground scrim.
*
* @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
* @see #setContentScrim(Drawable)
*/
public Drawable getContentScrim() {
return mContentScrim;
}
/**
* Set the drawable to use for the status bar scrim from resources.
* Providing null will disable the scrim functionality.
*
* <p>This scrim is only shown when we have been given a top system inset.</p>
*
* @param drawable the drawable to display
*
* @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
* @see #getStatusBarScrim()
*/
public void setStatusBarScrim(@Nullable Drawable drawable) {
if (mStatusBarScrim != drawable) {
if (mStatusBarScrim != null) {
mStatusBarScrim.setCallback(null);
}
mStatusBarScrim = drawable;
drawable.setCallback(this);
drawable.mutate().setAlpha(mScrimAlpha);
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**
* Set the color to use for the status bar scrim.
*
* <p>This scrim is only shown when we have been given a top system inset.</p>
*
* @param color the color to display
*
* @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
* @see #getStatusBarScrim()
*/
public void setStatusBarScrimColor(int color) {
setStatusBarScrim(new ColorDrawable(color));
}
/**
* Set the drawable to use for the content scrim from resources.
*
* @param resId drawable resource id
*
* @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
* @see #getStatusBarScrim()
*/
public void setStatusBarScrimResource(@DrawableRes int resId) {
setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId));
}
/**
* Returns the drawable which is used for the status bar scrim.
*
* @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
* @see #setStatusBarScrim(Drawable)
*/
public Drawable getStatusBarScrim() {
return mStatusBarScrim;
}
/**
@ -382,6 +507,9 @@ public class CollapsingToolbarLayout extends FrameLayout {
mCollapsingTextHelper.setExpandedTextColor(color);
}
/**
* The additional offset used to define when to trigger the scrim visibility change.
*/
final int getScrimTriggerOffset() {
return 2 * ViewCompat.getMinimumHeight(this);
}
@ -521,7 +649,10 @@ public class CollapsingToolbarLayout extends FrameLayout {
private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener {
@Override
public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
int pinnedHeight = 0;
mCurrentOffset = verticalOffset;
final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
final int scrollRange = layout.getTotalScrollRange();
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
@ -530,10 +661,9 @@ public class CollapsingToolbarLayout extends FrameLayout {
switch (lp.mCollapseMode) {
case LayoutParams.COLLAPSE_MODE_PIN:
if (getHeight() + verticalOffset >= child.getHeight()) {
if (getHeight() - insetTop + verticalOffset >= child.getHeight()) {
offsetHelper.setTopAndBottomOffset(-verticalOffset);
}
pinnedHeight += child.getHeight();
break;
case LayoutParams.COLLAPSE_MODE_PARALLAX:
offsetHelper.setTopAndBottomOffset(
@ -542,22 +672,26 @@ public class CollapsingToolbarLayout extends FrameLayout {
}
}
// Show or hide the scrim if needed
if (Color.alpha(mForegroundScrimColor) > 0) {
if (getHeight() + verticalOffset < getScrimTriggerOffset()) {
// Show or hide the scrims if needed
if (mContentScrim != null || mStatusBarScrim != null) {
if (getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop) {
showScrim();
} else {
hideScrim();
}
}
if (mStatusBarScrim != null && insetTop > 0) {
ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
}
// Update the collapsing text's fraction
final int expandRange = getHeight() - ViewCompat.getMinimumHeight(
CollapsingToolbarLayout.this);
CollapsingToolbarLayout.this) - insetTop;
mCollapsingTextHelper.setExpansionFraction(
Math.abs(verticalOffset) / (float) expandRange);
if (pinnedHeight > 0 && (getHeight() + verticalOffset) == pinnedHeight) {
if (Math.abs(verticalOffset) == scrollRange) {
// If we have some pinned children, and we're offset to only show those views,
// we want to be elevate
ViewCompat.setElevation(layout, layout.getTargetElevation());

View File

@ -23,13 +23,17 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.SystemClock;
import android.support.design.R;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@ -82,8 +86,10 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
static {
if (Build.VERSION.SDK_INT >= 21) {
TOP_SORTED_CHILDREN_COMPARATOR = new ViewElevationComparator();
INSETS_HELPER = new CoordinatorLayoutInsetsHelperLollipop();
} else {
TOP_SORTED_CHILDREN_COMPARATOR = null;
INSETS_HELPER = null;
}
}
@ -113,6 +119,7 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
};
static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
static final CoordinatorLayoutInsetsHelper INSETS_HELPER;
private final List<View> mDependencySortedChildren = new ArrayList<View>();
private final List<View> mTempList1 = new ArrayList<>();
@ -134,6 +141,10 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
private OnPreDrawListener mOnPreDrawListener;
private boolean mNeedsPreDrawListener;
private WindowInsetsCompat mLastInsets;
private boolean mDrawStatusBarBackground;
private Drawable mStatusBarBackground;
private final NestedScrollingParentHelper mNestedScrollingParentHelper =
new NestedScrollingParentHelper(this);
@ -149,7 +160,7 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout,
defStyleAttr, 0);
defStyleAttr, R.style.Widget_Design_CoordinatorLayout);
final int keylineArrayRes = a.getResourceId(R.styleable.CoordinatorLayout_keylines, 0);
if (keylineArrayRes != 0) {
final Resources res = context.getResources();
@ -160,7 +171,12 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
mKeylines[i] *= density;
}
}
mStatusBarBackground = a.getDrawable(R.styleable.CoordinatorLayout_statusBarBackground);
a.recycle();
if (INSETS_HELPER != null) {
INSETS_HELPER.setupForWindowInsets(this, new ApplyInsetsListener());
}
}
@Override
@ -191,6 +207,57 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
mIsAttachedToWindow = false;
}
/**
* Set a drawable to draw in the insets area for the status bar.
* Note that this will only be activated if this DrawerLayout fitsSystemWindows.
*
* @param bg Background drawable to draw behind the status bar
*/
public void setStatusBarBackground(Drawable bg) {
mStatusBarBackground = bg;
invalidate();
}
/**
* Gets the drawable used to draw in the insets area for the status bar.
*
* @return The status bar background drawable, or null if none set
*/
public Drawable getStatusBarBackground() {
return mStatusBarBackground;
}
/**
* Set a drawable to draw in the insets area for the status bar.
* Note that this will only be activated if this DrawerLayout fitsSystemWindows.
*
* @param resId Resource id of a background drawable to draw behind the status bar
*/
public void setStatusBarBackgroundResource(int resId) {
setStatusBarBackground(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null);
}
/**
* Set a drawable to draw in the insets area for the status bar.
* Note that this will only be activated if this DrawerLayout fitsSystemWindows.
*
* @param color Color to use as a background drawable to draw behind the status bar
* in 0xAARRGGBB format.
*/
public void setStatusBarBackgroundColor(int color) {
setStatusBarBackground(new ColorDrawable(color));
}
private void setWindowInsets(WindowInsetsCompat insets) {
if (mLastInsets != insets) {
mLastInsets = insets;
mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0;
setWillNotDraw(!mDrawStatusBarBackground && getBackground() == null);
dispatchChildApplyWindowInsets(insets);
requestLayout();
}
}
/**
* Reset all Behavior-related tracking records either to clean up or in preparation
* for a new event stream. This should be called when attached or detached from a window,
@ -518,6 +585,8 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
final boolean isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthPadding = paddingLeft + paddingRight;
final int heightPadding = paddingTop + paddingBottom;
@ -525,6 +594,8 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
int heightUsed = getSuggestedMinimumHeight();
int childState = 0;
final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
final int childCount = mDependencySortedChildren.size();
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
@ -545,11 +616,27 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
}
}
int childWidthMeasureSpec = widthMeasureSpec;
int childHeightMeasureSpec = heightMeasureSpec;
if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
// We're set to handle insets but this child isn't, so we will measure the
// child as if there are no insets
final int horizInsets = mLastInsets.getSystemWindowInsetLeft()
+ mLastInsets.getSystemWindowInsetRight();
final int vertInsets = mLastInsets.getSystemWindowInsetTop()
+ mLastInsets.getSystemWindowInsetBottom();
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
widthSize - horizInsets, widthMode);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
heightSize - vertInsets, heightMode);
}
final Behavior b = lp.getBehavior();
if (b == null || !b.onMeasureChild(this, child, widthMeasureSpec, keylineWidthUsed,
heightMeasureSpec, 0)) {
onMeasureChild(child, widthMeasureSpec, keylineWidthUsed,
heightMeasureSpec, 0);
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0)) {
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}
widthUsed = Math.max(widthUsed, widthPadding + child.getMeasuredWidth() +
@ -568,6 +655,35 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
setMeasuredDimension(width, height);
}
private void dispatchChildApplyWindowInsets(WindowInsetsCompat insets) {
if (insets.isConsumed()) {
return;
}
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
if (ViewCompat.getFitsSystemWindows(child)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior b = lp.getBehavior();
if (b != null) {
// If the view has a behavior, let it try first
insets = b.onApplyWindowInsets(this, child, insets);
if (insets.isConsumed()) {
// If it consumed the insets, break
break;
}
}
// Now let the view try and consume them
insets = ViewCompat.dispatchApplyWindowInsets(child, insets);
if (insets.isConsumed()) {
break;
}
}
}
}
/**
* Called to lay out each individual child view unless a
* {@link CoordinatorLayout.Behavior Behavior} is present. The Behavior may choose to
@ -608,6 +724,18 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
}
}
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
if (mDrawStatusBarBackground && mStatusBarBackground != null) {
final int inset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
if (inset > 0) {
mStatusBarBackground.setBounds(0, 0, getWidth(), inset);
mStatusBarBackground.draw(c);
}
}
}
/**
* Mark the last known child position rect for the given child view.
* This will be used when checking if a child view's position has changed between frames.
@ -851,6 +979,17 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
getPaddingTop() + lp.topMargin,
getWidth() - getPaddingRight() - lp.rightMargin,
getHeight() - getPaddingBottom() - lp.bottomMargin);
if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
&& !ViewCompat.getFitsSystemWindows(child)) {
// If we're set to handle insets but this child isn't, then it has been measured as
// if there are no insets. We need to lay it out to match.
parent.left += mLastInsets.getSystemWindowInsetLeft();
parent.top += mLastInsets.getSystemWindowInsetTop();
parent.right -= mLastInsets.getSystemWindowInsetRight();
parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
}
final Rect out = mTempRect2;
GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
child.getMeasuredHeight(), parent, out, layoutDirection);
@ -1885,6 +2024,25 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
float velocityX, float velocityY) {
return false;
}
/**
* Called when the window insets have changed.
*
* <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
* to handle the window inset change on behalf of it's associated view.
* </p>
*
* @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
* associated with
* @param child the child view of the CoordinatorLayout this Behavior is associated with
* @param insets the new window insets.
*
* @return The insets supplied, minus any insets that were consumed
*/
public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout,
V child, WindowInsetsCompat insets) {
return insets;
}
}
/**
@ -2247,4 +2405,12 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen
return true;
}
}
final class ApplyInsetsListener implements android.support.v4.view.OnApplyWindowInsetsListener {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
setWindowInsets(insets);
return insets.consumeSystemWindowInsets();
}
}
}