/* * 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.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.ColorInt; import android.support.annotation.DrawableRes; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import android.support.design.R; import android.support.v4.content.ContextCompat; import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewCompat; import android.support.v4.view.WindowInsetsCompat; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.FrameLayout; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar. * It is designed to be used as a direct child of a {@link AppBarLayout}. * CollapsingToolbarLayout contains the following features: * *
The title displayed by this view will shrink and grow based on the scroll offset.
* * @see #setTitle(CharSequence) * @see #isTitleEnabled() * * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled */ public void setTitleEnabled(boolean enabled) { if (enabled != mCollapsingTitleEnabled) { mCollapsingTitleEnabled = enabled; updateDummyView(); requestLayout(); } } /** * Returns whether this view is currently displaying its own title. * * @see #setTitleEnabled(boolean) * * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled */ public boolean isTitleEnabled() { return mCollapsingTitleEnabled; } /** * Set whether the content scrim and/or status bar scrim should be shown or not. Any change * in the vertical scroll may overwrite this value. Any visibility change will be animated if * this view has already been laid out. * * @param shown whether the scrims should be shown * * @see #getStatusBarScrim() * @see #getContentScrim() */ public void setScrimsShown(boolean shown) { setScrimsShown(shown, ViewCompat.isLaidOut(this) && !isInEditMode()); } /** * Set whether the content scrim and/or status bar scrim should be shown or not. Any change * in the vertical scroll may overwrite this value. * * @param shown whether the scrims should be shown * @param animate whether to animate the visibility change * * @see #getStatusBarScrim() * @see #getContentScrim() */ public void setScrimsShown(boolean shown, boolean animate) { if (mScrimsAreShown != shown) { if (animate) { animateScrim(shown ? 0xFF : 0x0); } else { setScrimAlpha(shown ? 0xFF : 0x0); } mScrimsAreShown = shown; } } private void animateScrim(int targetAlpha) { ensureToolbar(); if (mScrimAnimator == null) { mScrimAnimator = ViewUtils.createAnimator(); mScrimAnimator.setDuration(SCRIM_ANIMATION_DURATION); mScrimAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); mScrimAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimatorCompat animator) { setScrimAlpha(animator.getAnimatedIntValue()); } }); } else if (mScrimAnimator.isRunning()) { mScrimAnimator.cancel(); } mScrimAnimator.setIntValues(mScrimAlpha, targetAlpha); mScrimAnimator.start(); } private void setScrimAlpha(int alpha) { if (alpha != mScrimAlpha) { final Drawable contentScrim = mContentScrim; if (contentScrim != null && mToolbar != null) { ViewCompat.postInvalidateOnAnimation(mToolbar); } mScrimAlpha = alpha; ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this); } } /** * Set the drawable to use for the content scrim from resources. Providing null will disable * the scrim functionality. * * @param drawable the drawable to display * * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim * @see #getContentScrim() */ public void setContentScrim(@Nullable Drawable drawable) { if (mContentScrim != drawable) { if (mContentScrim != null) { mContentScrim.setCallback(null); } if (drawable != null) { mContentScrim = drawable.mutate(); drawable.setBounds(0, 0, getWidth(), getHeight()); drawable.setCallback(this); drawable.setAlpha(mScrimAlpha); } else { mContentScrim = null; } ViewCompat.postInvalidateOnAnimation(this); } } /** * Set the color to use for the content scrim. * * @param color the color to display * * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim * @see #getContentScrim() */ public void setContentScrimColor(@ColorInt int color) { setContentScrim(new ColorDrawable(color)); } /** * Set the drawable to use for the content scrim from resources. * * @param resId drawable resource id * * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim * @see #getContentScrim() */ 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. * *This scrim is only shown when we have been given a top system inset.
* * @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. * *This scrim is only shown when we have been given a top system inset.
* * @param color the color to display * * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim * @see #getStatusBarScrim() */ public void setStatusBarScrimColor(@ColorInt 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; } /** * Sets the text color and size for the collapsed title from the specified * TextAppearance resource. * * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance */ public void setCollapsedTitleTextAppearance(@StyleRes int resId) { mCollapsingTextHelper.setCollapsedTextAppearance(resId); } /** * Sets the text color of the collapsed title. * * @param color The new text color in ARGB format */ public void setCollapsedTitleTextColor(@ColorInt int color) { mCollapsingTextHelper.setCollapsedTextColor(color); } /** * Sets the horizontal alignment of the collapsed title and the vertical gravity that will * be used when there is extra space in the collapsed bounds beyond what is required for * the title itself. * * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity */ public void setCollapsedTitleGravity(int gravity) { mCollapsingTextHelper.setExpandedTextGravity(gravity); } /** * Returns the horizontal and vertical alignment for title when collapsed. * * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity */ public int getCollapsedTitleGravity() { return mCollapsingTextHelper.getCollapsedTextGravity(); } /** * Sets the text color and size for the expanded title from the specified * TextAppearance resource. * * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance */ public void setExpandedTitleTextAppearance(@StyleRes int resId) { mCollapsingTextHelper.setExpandedTextAppearance(resId); } /** * Sets the text color of the expanded title. * * @param color The new text color in ARGB format */ public void setExpandedTitleColor(@ColorInt int color) { mCollapsingTextHelper.setExpandedTextColor(color); } /** * Sets the horizontal alignment of the expanded title and the vertical gravity that will * be used when there is extra space in the expanded bounds beyond what is required for * the title itself. * * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity */ public void setExpandedTitleGravity(int gravity) { mCollapsingTextHelper.setExpandedTextGravity(gravity); } /** * Returns the horizontal and vertical alignment for title when expanded. * * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity */ public int getExpandedTitleGravity() { return mCollapsingTextHelper.getExpandedTextGravity(); } /** * The additional offset used to define when to trigger the scrim visibility change. */ final int getScrimTriggerOffset() { return 2 * ViewCompat.getMinimumHeight(this); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(super.generateDefaultLayoutParams()); } @Override public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } public static class LayoutParams extends FrameLayout.LayoutParams { private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f; /** @hide */ @IntDef({ COLLAPSE_MODE_OFF, COLLAPSE_MODE_PIN, COLLAPSE_MODE_PARALLAX }) @Retention(RetentionPolicy.SOURCE) @interface CollapseMode {} /** * The view will act as normal with no collapsing behavior. */ public static final int COLLAPSE_MODE_OFF = 0; /** * The view will pin in place until it reaches the bottom of the * {@link CollapsingToolbarLayout}. */ public static final int COLLAPSE_MODE_PIN = 1; /** * The view will scroll in a parallax fashion. See {@link #setParallaxMultiplier(float)} * to change the multiplier used. */ public static final int COLLAPSE_MODE_PARALLAX = 2; int mCollapseMode = COLLAPSE_MODE_OFF; float mParallaxMult = DEFAULT_PARALLAX_MULTIPLIER; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CollapsingAppBarLayout_LayoutParams); mCollapseMode = a.getInt( R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseMode, COLLAPSE_MODE_OFF); setParallaxMultiplier(a.getFloat( R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseParallaxMultiplier, DEFAULT_PARALLAX_MULTIPLIER)); a.recycle(); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(int width, int height, int gravity) { super(width, height, gravity); } public LayoutParams(ViewGroup.LayoutParams p) { super(p); } public LayoutParams(MarginLayoutParams source) { super(source); } public LayoutParams(FrameLayout.LayoutParams source) { super(source); } /** * Set the collapse mode. * * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} * or {@link #COLLAPSE_MODE_PARALLAX}. */ public void setCollapseMode(@CollapseMode int collapseMode) { mCollapseMode = collapseMode; } /** * Returns the requested collapse mode. * * @return the current mode. One of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} * or {@link #COLLAPSE_MODE_PARALLAX}. */ @CollapseMode public int getCollapseMode() { return mCollapseMode; } /** * Set the parallax scroll multiplier used in conjunction with * {@link #COLLAPSE_MODE_PARALLAX}. A value of {@code 0.0} indicates no movement at all, * {@code 1.0f} indicates normal scroll movement. * * @param multiplier the multiplier. * * @see #getParallaxMultiplier() */ public void setParallaxMultiplier(float multiplier) { mParallaxMult = multiplier; } /** * Returns the parallax scroll multiplier used in conjunction with * {@link #COLLAPSE_MODE_PARALLAX}. * * @see #setParallaxMultiplier(float) */ public float getParallaxMultiplier() { return mParallaxMult; } } private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener { @Override public void onOffsetChanged(AppBarLayout layout, int verticalOffset) { 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); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child); switch (lp.mCollapseMode) { case LayoutParams.COLLAPSE_MODE_PIN: if (getHeight() - insetTop + verticalOffset >= child.getHeight()) { offsetHelper.setTopAndBottomOffset(-verticalOffset); } break; case LayoutParams.COLLAPSE_MODE_PARALLAX: offsetHelper.setTopAndBottomOffset( Math.round(-verticalOffset * lp.mParallaxMult)); break; } } // Show or hide the scrims if needed if (mContentScrim != null || mStatusBarScrim != null) { setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop); } if (mStatusBarScrim != null && insetTop > 0) { ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this); } // Update the collapsing text's fraction final int expandRange = getHeight() - ViewCompat.getMinimumHeight( CollapsingToolbarLayout.this) - insetTop; mCollapsingTextHelper.setExpansionFraction( Math.abs(verticalOffset) / (float) expandRange); 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()); } else { // Otherwise, we're inline with the content ViewCompat.setElevation(layout, 0f); } } } }