Source code
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.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.support.design.R;
import android.support.design.widget.AppBarLayout.OnOffsetChangedListener;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.OnApplyWindowInsetsListener;
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.View;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewParent;
import android.widget.FrameLayout;
public class CollapsingToolbarLayout extends FrameLayout {
private static final int SCRIM_ANIMATION_DURATION = 600;
private final CollapsingTextHelper mCollapsingTextHelper;
private boolean mCollapsingTitleEnabled;
private Drawable mContentScrim;
private int mCurrentOffset;
private boolean mDrawCollapsingTitle;
private View mDummyView;
private int mExpandedMarginBottom;
private int mExpandedMarginLeft;
private int mExpandedMarginRight;
private int mExpandedMarginTop;
private WindowInsetsCompat mLastInsets;
private OnOffsetChangedListener mOnOffsetChangedListener;
private boolean mRefreshToolbar;
private int mScrimAlpha;
private ValueAnimatorCompat mScrimAnimator;
private boolean mScrimsAreShown;
private Drawable mStatusBarScrim;
private final Rect mTmpRect;
private Toolbar mToolbar;
private int mToolbarId;
public static class LayoutParams extends android.widget.FrameLayout.LayoutParams {
public static final int COLLAPSE_MODE_OFF = 0;
public static final int COLLAPSE_MODE_PARALLAX = 2;
public static final int COLLAPSE_MODE_PIN = 1;
private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f;
int mCollapseMode = 0;
float mParallaxMult = DEFAULT_PARALLAX_MULTIPLIER;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CollapsingAppBarLayout_LayoutParams);
this.mCollapseMode = a.getInt(R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseMode, 0);
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(android.view.ViewGroup.LayoutParams p) {
super(p);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(android.widget.FrameLayout.LayoutParams source) {
super(source);
}
public void setCollapseMode(int collapseMode) {
this.mCollapseMode = collapseMode;
}
public int getCollapseMode() {
return this.mCollapseMode;
}
public void setParallaxMultiplier(float multiplier) {
this.mParallaxMult = multiplier;
}
public float getParallaxMultiplier() {
return this.mParallaxMult;
}
}
private class OffsetUpdateListener implements OnOffsetChangedListener {
private OffsetUpdateListener() {
}
public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
int insetTop;
boolean z = false;
CollapsingToolbarLayout.this.mCurrentOffset = verticalOffset;
if (CollapsingToolbarLayout.this.mLastInsets != null) {
insetTop = CollapsingToolbarLayout.this.mLastInsets.getSystemWindowInsetTop();
} else {
insetTop = 0;
}
int scrollRange = layout.getTotalScrollRange();
int z2 = CollapsingToolbarLayout.this.getChildCount();
for (int i = 0; i < z2; i++) {
View child = CollapsingToolbarLayout.this.getChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
ViewOffsetHelper offsetHelper = CollapsingToolbarLayout.getViewOffsetHelper(child);
switch (lp.mCollapseMode) {
case 1:
if ((CollapsingToolbarLayout.this.getHeight() - insetTop) + verticalOffset < child.getHeight()) {
break;
}
offsetHelper.setTopAndBottomOffset(-verticalOffset);
break;
case 2:
offsetHelper.setTopAndBottomOffset(Math.round(((float) (-verticalOffset)) * lp.mParallaxMult));
break;
default:
break;
}
}
if (!(CollapsingToolbarLayout.this.mContentScrim == null && CollapsingToolbarLayout.this.mStatusBarScrim == null)) {
CollapsingToolbarLayout collapsingToolbarLayout = CollapsingToolbarLayout.this;
if (CollapsingToolbarLayout.this.getHeight() + verticalOffset < CollapsingToolbarLayout.this.getScrimTriggerOffset() + insetTop) {
z = true;
}
collapsingToolbarLayout.setScrimsShown(z);
}
if (CollapsingToolbarLayout.this.mStatusBarScrim != null && insetTop > 0) {
ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
}
CollapsingToolbarLayout.this.mCollapsingTextHelper.setExpansionFraction(((float) Math.abs(verticalOffset)) / ((float) ((CollapsingToolbarLayout.this.getHeight() - ViewCompat.getMinimumHeight(CollapsingToolbarLayout.this)) - insetTop)));
if (Math.abs(verticalOffset) == scrollRange) {
ViewCompat.setElevation(layout, layout.getTargetElevation());
} else {
ViewCompat.setElevation(layout, 0.0f);
}
}
}
public CollapsingToolbarLayout(Context context) {
this(context, null);
}
public CollapsingToolbarLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
boolean isRtl;
super(context, attrs, defStyleAttr);
this.mRefreshToolbar = true;
this.mTmpRect = new Rect();
ThemeUtils.checkAppCompatTheme(context);
this.mCollapsingTextHelper = new CollapsingTextHelper(this);
this.mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CollapsingToolbarLayout, defStyleAttr, R.style.Widget_Design_CollapsingToolbar);
this.mCollapsingTextHelper.setExpandedTextGravity(a.getInt(R.styleable.CollapsingToolbarLayout_expandedTitleGravity, 8388691));
this.mCollapsingTextHelper.setCollapsedTextGravity(a.getInt(R.styleable.CollapsingToolbarLayout_collapsedTitleGravity, 8388627));
int dimensionPixelSize = a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMargin, 0);
this.mExpandedMarginBottom = dimensionPixelSize;
this.mExpandedMarginRight = dimensionPixelSize;
this.mExpandedMarginTop = dimensionPixelSize;
this.mExpandedMarginLeft = dimensionPixelSize;
if (ViewCompat.getLayoutDirection(this) == 1) {
isRtl = true;
} else {
isRtl = false;
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart)) {
int marginStart = a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart, 0);
if (isRtl) {
this.mExpandedMarginRight = marginStart;
} else {
this.mExpandedMarginLeft = marginStart;
}
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd)) {
int marginEnd = a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd, 0);
if (isRtl) {
this.mExpandedMarginLeft = marginEnd;
} else {
this.mExpandedMarginRight = marginEnd;
}
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop)) {
this.mExpandedMarginTop = a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop, 0);
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom)) {
this.mExpandedMarginBottom = a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom, 0);
}
this.mCollapsingTitleEnabled = a.getBoolean(R.styleable.CollapsingToolbarLayout_titleEnabled, true);
setTitle(a.getText(R.styleable.CollapsingToolbarLayout_title));
this.mCollapsingTextHelper.setExpandedTextAppearance(R.style.TextAppearance_Design_CollapsingToolbar_Expanded);
this.mCollapsingTextHelper.setCollapsedTextAppearance(R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) {
this.mCollapsingTextHelper.setExpandedTextAppearance(a.getResourceId(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance, 0));
}
if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance)) {
this.mCollapsingTextHelper.setCollapsedTextAppearance(a.getResourceId(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 0));
}
setContentScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim));
setStatusBarScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim));
this.mToolbarId = a.getResourceId(R.styleable.CollapsingToolbarLayout_toolbarId, -1);
a.recycle();
setWillNotDraw(false);
ViewCompat.setOnApplyWindowInsetsListener(this, new OnApplyWindowInsetsListener() {
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
CollapsingToolbarLayout.this.mLastInsets = insets;
CollapsingToolbarLayout.this.requestLayout();
return insets.consumeSystemWindowInsets();
}
});
}
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ViewParent parent = getParent();
if (parent instanceof AppBarLayout) {
if (this.mOnOffsetChangedListener == null) {
this.mOnOffsetChangedListener = new OffsetUpdateListener();
}
((AppBarLayout) parent).addOnOffsetChangedListener(this.mOnOffsetChangedListener);
}
}
protected void onDetachedFromWindow() {
ViewParent parent = getParent();
if (this.mOnOffsetChangedListener != null && (parent instanceof AppBarLayout)) {
((AppBarLayout) parent).removeOnOffsetChangedListener(this.mOnOffsetChangedListener);
}
super.onDetachedFromWindow();
}
public void draw(Canvas canvas) {
super.draw(canvas);
ensureToolbar();
if (this.mToolbar == null && this.mContentScrim != null && this.mScrimAlpha > 0) {
this.mContentScrim.mutate().setAlpha(this.mScrimAlpha);
this.mContentScrim.draw(canvas);
}
if (this.mCollapsingTitleEnabled && this.mDrawCollapsingTitle) {
this.mCollapsingTextHelper.draw(canvas);
}
if (this.mStatusBarScrim != null && this.mScrimAlpha > 0) {
int topInset = this.mLastInsets != null ? this.mLastInsets.getSystemWindowInsetTop() : 0;
if (topInset > 0) {
this.mStatusBarScrim.setBounds(0, -this.mCurrentOffset, getWidth(), topInset - this.mCurrentOffset);
this.mStatusBarScrim.mutate().setAlpha(this.mScrimAlpha);
this.mStatusBarScrim.draw(canvas);
}
}
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
ensureToolbar();
if (child == this.mToolbar && this.mContentScrim != null && this.mScrimAlpha > 0) {
this.mContentScrim.mutate().setAlpha(this.mScrimAlpha);
this.mContentScrim.draw(canvas);
}
return super.drawChild(canvas, child, drawingTime);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (this.mContentScrim != null) {
this.mContentScrim.setBounds(0, 0, w, h);
}
}
private void ensureToolbar() {
if (this.mRefreshToolbar) {
Toolbar fallback = null;
Toolbar selected = null;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child instanceof Toolbar) {
if (this.mToolbarId == -1) {
selected = (Toolbar) child;
break;
} else if (this.mToolbarId == child.getId()) {
selected = (Toolbar) child;
break;
} else if (fallback == null) {
fallback = (Toolbar) child;
}
}
}
if (selected == null) {
selected = fallback;
}
this.mToolbar = selected;
updateDummyView();
this.mRefreshToolbar = false;
}
}
private void updateDummyView() {
if (!(this.mCollapsingTitleEnabled || this.mDummyView == null)) {
ViewParent parent = this.mDummyView.getParent();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(this.mDummyView);
}
}
if (this.mCollapsingTitleEnabled && this.mToolbar != null) {
if (this.mDummyView == null) {
this.mDummyView = new View(getContext());
}
if (this.mDummyView.getParent() == null) {
this.mToolbar.addView(this.mDummyView, -1, -1);
}
}
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ensureToolbar();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (this.mCollapsingTitleEnabled && this.mDummyView != null) {
this.mDrawCollapsingTitle = this.mDummyView.isShown();
if (this.mDrawCollapsingTitle) {
ViewGroupUtils.getDescendantRect(this, this.mDummyView, this.mTmpRect);
this.mCollapsingTextHelper.setCollapsedBounds(this.mTmpRect.left, bottom - this.mTmpRect.height(), this.mTmpRect.right, bottom);
this.mCollapsingTextHelper.setExpandedBounds(this.mExpandedMarginLeft, this.mTmpRect.bottom + this.mExpandedMarginTop, (right - left) - this.mExpandedMarginRight, (bottom - top) - this.mExpandedMarginBottom);
this.mCollapsingTextHelper.recalculate();
}
}
int z = getChildCount();
for (int i = 0; i < z; i++) {
View child = getChildAt(i);
if (!(this.mLastInsets == null || ViewCompat.getFitsSystemWindows(child))) {
int insetTop = this.mLastInsets.getSystemWindowInsetTop();
if (child.getTop() < insetTop) {
child.offsetTopAndBottom(insetTop);
}
}
getViewOffsetHelper(child).onViewLayout();
}
if (this.mToolbar != null) {
if (this.mCollapsingTitleEnabled && TextUtils.isEmpty(this.mCollapsingTextHelper.getText())) {
this.mCollapsingTextHelper.setText(this.mToolbar.getTitle());
}
setMinimumHeight(this.mToolbar.getHeight());
}
}
private static ViewOffsetHelper getViewOffsetHelper(View view) {
ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper);
if (offsetHelper != null) {
return offsetHelper;
}
offsetHelper = new ViewOffsetHelper(view);
view.setTag(R.id.view_offset_helper, offsetHelper);
return offsetHelper;
}
public void setTitle(@Nullable CharSequence title) {
this.mCollapsingTextHelper.setText(title);
}
@Nullable
public CharSequence getTitle() {
return this.mCollapsingTitleEnabled ? this.mCollapsingTextHelper.getText() : null;
}
public void setTitleEnabled(boolean enabled) {
if (enabled != this.mCollapsingTitleEnabled) {
this.mCollapsingTitleEnabled = enabled;
updateDummyView();
requestLayout();
}
}
public boolean isTitleEnabled() {
return this.mCollapsingTitleEnabled;
}
public void setScrimsShown(boolean shown) {
boolean z = ViewCompat.isLaidOut(this) && !isInEditMode();
setScrimsShown(shown, z);
}
public void setScrimsShown(boolean shown, boolean animate) {
int i = 255;
if (this.mScrimsAreShown != shown) {
if (animate) {
if (!shown) {
i = 0;
}
animateScrim(i);
} else {
if (!shown) {
i = 0;
}
setScrimAlpha(i);
}
this.mScrimsAreShown = shown;
}
}
private void animateScrim(int targetAlpha) {
ensureToolbar();
if (this.mScrimAnimator == null) {
this.mScrimAnimator = ViewUtils.createAnimator();
this.mScrimAnimator.setDuration(SCRIM_ANIMATION_DURATION);
this.mScrimAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
this.mScrimAnimator.setUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimatorCompat animator) {
CollapsingToolbarLayout.this.setScrimAlpha(animator.getAnimatedIntValue());
}
});
} else if (this.mScrimAnimator.isRunning()) {
this.mScrimAnimator.cancel();
}
this.mScrimAnimator.setIntValues(this.mScrimAlpha, targetAlpha);
this.mScrimAnimator.start();
}
private void setScrimAlpha(int alpha) {
if (alpha != this.mScrimAlpha) {
if (!(this.mContentScrim == null || this.mToolbar == null)) {
ViewCompat.postInvalidateOnAnimation(this.mToolbar);
}
this.mScrimAlpha = alpha;
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void setContentScrim(@Nullable Drawable drawable) {
if (this.mContentScrim != drawable) {
if (this.mContentScrim != null) {
this.mContentScrim.setCallback(null);
}
if (drawable != null) {
this.mContentScrim = drawable.mutate();
drawable.setBounds(0, 0, getWidth(), getHeight());
drawable.setCallback(this);
drawable.setAlpha(this.mScrimAlpha);
} else {
this.mContentScrim = null;
}
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void setContentScrimColor(@ColorInt int color) {
setContentScrim(new ColorDrawable(color));
}
public void setContentScrimResource(@DrawableRes int resId) {
setContentScrim(ContextCompat.getDrawable(getContext(), resId));
}
public Drawable getContentScrim() {
return this.mContentScrim;
}
public void setStatusBarScrim(@Nullable Drawable drawable) {
if (this.mStatusBarScrim != drawable) {
if (this.mStatusBarScrim != null) {
this.mStatusBarScrim.setCallback(null);
}
this.mStatusBarScrim = drawable;
drawable.setCallback(this);
drawable.mutate().setAlpha(this.mScrimAlpha);
ViewCompat.postInvalidateOnAnimation(this);
}
}
public void setStatusBarScrimColor(@ColorInt int color) {
setStatusBarScrim(new ColorDrawable(color));
}
public void setStatusBarScrimResource(@DrawableRes int resId) {
setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId));
}
public Drawable getStatusBarScrim() {
return this.mStatusBarScrim;
}
public void setCollapsedTitleTextAppearance(@StyleRes int resId) {
this.mCollapsingTextHelper.setCollapsedTextAppearance(resId);
}
public void setCollapsedTitleTextColor(@ColorInt int color) {
this.mCollapsingTextHelper.setCollapsedTextColor(color);
}
public void setCollapsedTitleGravity(int gravity) {
this.mCollapsingTextHelper.setExpandedTextGravity(gravity);
}
public int getCollapsedTitleGravity() {
return this.mCollapsingTextHelper.getCollapsedTextGravity();
}
public void setExpandedTitleTextAppearance(@StyleRes int resId) {
this.mCollapsingTextHelper.setExpandedTextAppearance(resId);
}
public void setExpandedTitleColor(@ColorInt int color) {
this.mCollapsingTextHelper.setExpandedTextColor(color);
}
public void setExpandedTitleGravity(int gravity) {
this.mCollapsingTextHelper.setExpandedTextGravity(gravity);
}
public int getExpandedTitleGravity() {
return this.mCollapsingTextHelper.getExpandedTextGravity();
}
public void setCollapsedTitleTypeface(@Nullable Typeface typeface) {
this.mCollapsingTextHelper.setCollapsedTypeface(typeface);
}
@NonNull
public Typeface getCollapsedTitleTypeface() {
return this.mCollapsingTextHelper.getCollapsedTypeface();
}
public void setExpandedTitleTypeface(@Nullable Typeface typeface) {
this.mCollapsingTextHelper.setExpandedTypeface(typeface);
}
@NonNull
public Typeface getExpandedTitleTypeface() {
return this.mCollapsingTextHelper.getExpandedTypeface();
}
final int getScrimTriggerOffset() {
return ViewCompat.getMinimumHeight(this) * 2;
}
protected boolean checkLayoutParams(android.view.ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(super.generateDefaultLayoutParams());
}
public android.widget.FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
protected android.widget.FrameLayout.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
}