package com.aries.ui.view.title; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; 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.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 com.aries.ui.view.collapsing.R; import com.aries.ui.view.title.util.AnimationUtils; import com.aries.ui.view.title.util.CollapsingTextHelper; import com.aries.ui.view.title.util.ThemeUtils; import com.aries.ui.view.title.util.ViewGroupUtils; import com.aries.ui.view.title.util.ViewOffsetHelper; import com.google.android.material.appbar.AppBarLayout; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.IntDef; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; import androidx.annotation.StyleRes; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.view.GravityCompat; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; /** * @Author: AriesHoo on 2018/7/19 10:50 * @E-Mail: AriesHoo@126.com * Function: 参考 {@link com.google.android.material.appbar.CollapsingToolbarLayout}, * 适配 {@link TitleBarView} * Description: * 1、set方法增加返回本对象支持链式调用 * 2、2018-8-28 14:20:46 新增设置展开及收缩文字大小方法{@link #setCollapsedTitleTextSize(float)}{@link #setExpandedTitleTextSize(float)} */ public class CollapsingTitleBarLayout extends FrameLayout { private static final int DEFAULT_SCRIM_ANIMATION_DURATION = 600; private boolean mRefreshToolbar = true; private int mTitleBarId; private TitleBarView mTitleBar; private View mTitleBarDirectChild; private int mExpandedMarginStart; private int mExpandedMarginTop; private int mExpandedMarginEnd; private int mExpandedMarginBottom; private final Rect mTmpRect = new Rect(); final CollapsingTextHelper mCollapsingTextHelper; private boolean mCollapsingTitleEnabled; private Drawable mContentScrim; Drawable mStatusBarScrim; private int mScrimAlpha; private boolean mScrimsAreShown; private ValueAnimator mScrimAnimator; private long mScrimAnimationDuration; private int mScrimVisibleHeightTrigger = -1; private int mCollapsedTitleGravity; public AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener; int mCurrentOffset; WindowInsetsCompat mLastInsets; public CollapsingTitleBarLayout(Context context) { this(context, null); } public CollapsingTitleBarLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CollapsingTitleBarLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mCollapsingTextHelper = new CollapsingTextHelper(this); mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); ThemeUtils.checkAppCompatTheme(context); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CollapsingTitleBarLayout, defStyleAttr, 0); mCollapsingTextHelper.setExpandedTextGravity( a.getInt(R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleGravity, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)); mCollapsedTitleGravity = a.getInt(R.styleable.CollapsingTitleBarLayout_collapsing_collapsedTitleGravity, GravityCompat.START | Gravity.CENTER_VERTICAL); mCollapsingTextHelper.setCollapsedTextGravity(mCollapsedTitleGravity); mExpandedMarginStart = mExpandedMarginTop = mExpandedMarginEnd = mExpandedMarginBottom = a.getDimensionPixelSize(R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMargin, 0); if (a.hasValue(R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMarginStart)) { mExpandedMarginStart = a.getDimensionPixelSize( R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMarginStart, 0); } if (a.hasValue(R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMarginEnd)) { mExpandedMarginEnd = a.getDimensionPixelSize( R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMarginEnd, 0); } if (a.hasValue(R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMarginTop)) { mExpandedMarginTop = a.getDimensionPixelSize( R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMarginTop, 0); } if (a.hasValue(R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMarginBottom)) { mExpandedMarginBottom = a.getDimensionPixelSize( R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleMarginBottom, 0); } mCollapsingTitleEnabled = a.getBoolean(R.styleable.CollapsingTitleBarLayout_collapsing_titleEnabled, true); setTitle(a.getText(R.styleable.CollapsingTitleBarLayout_title)); // First load the default text appearances mCollapsingTextHelper.setExpandedTextAppearance( R.style.CollapsingTitleBarLayoutExpanded); mCollapsingTextHelper.setCollapsedTextAppearance(R.style.CollapsingTitleBarLayoutCollapsed); // Now overlay any custom text appearances if (a.hasValue(R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleTextAppearance)) { mCollapsingTextHelper.setExpandedTextAppearance( a.getResourceId(R.styleable.CollapsingTitleBarLayout_collapsing_expandedTitleTextAppearance, 0)); } if (a.hasValue(R.styleable.CollapsingTitleBarLayout_collapsing_collapsedTitleTextAppearance)) { mCollapsingTextHelper.setCollapsedTextAppearance( a.getResourceId(R.styleable.CollapsingTitleBarLayout_collapsing_collapsedTitleTextAppearance, 0)); } mScrimVisibleHeightTrigger = a.getDimensionPixelSize( R.styleable.CollapsingTitleBarLayout_collapsing_scrimVisibleHeightTrigger, -1); mScrimAnimationDuration = a.getInt( R.styleable.CollapsingTitleBarLayout_collapsing_scrimAnimationDuration, DEFAULT_SCRIM_ANIMATION_DURATION); setContentScrim(a.getDrawable(R.styleable.CollapsingTitleBarLayout_collapsing_contentScrim)); setStatusBarScrim(a.getDrawable(R.styleable.CollapsingTitleBarLayout_collapsing_statusBarScrim)); mTitleBarId = a.getResourceId(R.styleable.CollapsingTitleBarLayout_collapsing_titleBarId, -1); a.recycle(); setWillNotDraw(false); ViewCompat.setOnApplyWindowInsetsListener(this, new androidx.core.view.OnApplyWindowInsetsListener() { @Override public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { return onWindowInsetChanged(insets); } }); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); // Add an OnOffsetChangedListener if possible final ViewParent parent = getParent(); if (parent instanceof AppBarLayout) { // Copy over from the ABL whether we should fit system windows ViewCompat.setFitsSystemWindows(this, ViewCompat.getFitsSystemWindows((View) parent)); if (mOnOffsetChangedListener == null) { mOnOffsetChangedListener = new OffsetUpdateListener(); } ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener); // We're attached, so lets request an inset dispatch ViewCompat.requestApplyInsets(this); } } @Override protected void onDetachedFromWindow() { // Remove our OnOffsetChangedListener if possible and it exists final ViewParent parent = getParent(); if (mOnOffsetChangedListener != null && parent instanceof AppBarLayout) { ((AppBarLayout) parent).removeOnOffsetChangedListener(mOnOffsetChangedListener); } super.onDetachedFromWindow(); } public boolean objectEquals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); } WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) { WindowInsetsCompat newInsets = null; if (ViewCompat.getFitsSystemWindows(this)) { // If we're set to fit system windows, keep the insets newInsets = insets; } // If our insets have changed, keep them and invalidate the scroll ranges... if (!objectEquals(mLastInsets, newInsets)) { mLastInsets = newInsets; requestLayout(); } // Consume the insets. This is done so that child views with fitSystemWindows=true do not // get the default padding functionality from View return insets.consumeSystemWindowInsets(); } @Override public void draw(Canvas canvas) { super.draw(canvas); // 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. ensureToolbar(); if (mTitleBar == null && mContentScrim != null && mScrimAlpha > 0) { mContentScrim.mutate().setAlpha(mScrimAlpha); mContentScrim.draw(canvas); } // Let the collapsing text helper draw its text if (mCollapsingTitleEnabled) { 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 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { // 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 just before the Toolbar is drawn boolean invalidated = false; if (mContentScrim != null && mScrimAlpha > 0 && isToolbarChild(child)) { mContentScrim.mutate().setAlpha(mScrimAlpha); mContentScrim.draw(canvas); invalidated = true; } return super.drawChild(canvas, child, drawingTime) || invalidated; } @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); } } private void ensureToolbar() { if (!mRefreshToolbar) { return; } // First clear out the current Toolbar mTitleBar = null; mTitleBarDirectChild = null; if (mTitleBarId != -1) { // If we have an ID set, try and find it and it's direct parent to us mTitleBar = (TitleBarView) findViewById(mTitleBarId); if (mTitleBar != null) { mTitleBarDirectChild = findDirectChild(mTitleBar); } } if (mTitleBar == null) { // If we don't have an ID, or couldn't find a Toolbar with the correct ID, try and find // one from our direct children TitleBarView topBar = null; for (int i = 0, count = getChildCount(); i < count; i++) { final View child = getChildAt(i); if (child instanceof TitleBarView) { topBar = (TitleBarView) child; break; } } mTitleBar = topBar; } mRefreshToolbar = false; } private boolean isToolbarChild(View child) { return (mTitleBarDirectChild == null || mTitleBarDirectChild == this) ? child == mTitleBar : child == mTitleBarDirectChild; } /** * Returns the direct child of this layout, which itself is the ancestor of the * given view. */ private View findDirectChild(final View descendant) { View directChild = descendant; for (ViewParent p = descendant.getParent(); p != this && p != null; p = p.getParent()) { if (p instanceof View) { directChild = (View) p; } } return directChild; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { ensureToolbar(); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); int topBarInsetAdjustTop = 0; if (mLastInsets != null) { // Shift down any views which are not set to fit system windows final int insetTop = mLastInsets.getSystemWindowInsetTop(); for (int i = 0, z = getChildCount(); i < z; i++) { final View child = getChildAt(i); if (ViewCompat.getFitsSystemWindows(child)) { if (child.getTop() < insetTop) { // If the child isn't set to fit system windows but is drawing within // the inset offset it down ViewCompat.offsetTopAndBottom(child, insetTop); } } } View adjustView = mTitleBarDirectChild != null ? mTitleBarDirectChild : mTitleBar; if (ViewCompat.getFitsSystemWindows(adjustView)) { topBarInsetAdjustTop = insetTop; } } // Update the collapsed bounds by getting it's transformed bounds if (mCollapsingTitleEnabled) { // Update the collapsed bounds final int maxOffset = getMaxOffsetForPinChild( mTitleBarDirectChild != null ? mTitleBarDirectChild : mTitleBar); ViewGroupUtils.getDescendantRect(this, mTitleBar, mTmpRect); mTmpRect.top = mTmpRect.top - topBarInsetAdjustTop; Rect rect = mTitleBar.getTitleContainerRect(); int horStart = mTmpRect.top + maxOffset; mCollapsingTextHelper.setCollapsedBounds( mTmpRect.left + rect.left, horStart + rect.top, mTmpRect.left + rect.right, horStart + rect.bottom); // Update the expanded bounds mCollapsingTextHelper.setExpandedBounds( mExpandedMarginStart, mTmpRect.top + mExpandedMarginTop, right - left - mExpandedMarginEnd, bottom - top - mExpandedMarginBottom); // Now recalculate using the new bounds mCollapsingTextHelper.recalculate(); } // Update our child view offset helpers. This needs to be done after the title has been // setup, so that any Toolbars are in their original position for (int i = 0, z = getChildCount(); i < z; i++) { getViewOffsetHelper(getChildAt(i)).onViewLayout(); } // Finally, set our minimum height to enable proper AppBarLayout collapsing if (mTitleBar != null) { if (mCollapsingTitleEnabled && TextUtils.isEmpty(mCollapsingTextHelper.getText())) { // If we do not currently have a title, try and grab it from the Toolbar mCollapsingTextHelper.setText(mTitleBar.getTextView(Gravity.CENTER | Gravity.TOP).getText()); } if (mTitleBarDirectChild == null || mTitleBarDirectChild == this) { setMinimumHeight(getHeightWithMargins(mTitleBar)); } else { setMinimumHeight(getHeightWithMargins(mTitleBarDirectChild)); } mTitleBar.setCenterGravityLeft(getCollapsedTitleGravity() == Gravity.LEFT); } updateScrimVisibility(); } private static int getHeightWithMargins(@NonNull final View view) { final ViewGroup.LayoutParams lp = view.getLayoutParams(); if (lp instanceof MarginLayoutParams) { final MarginLayoutParams mlp = (MarginLayoutParams) lp; return view.getHeight() + mlp.topMargin + mlp.bottomMargin; } return view.getHeight(); } static ViewOffsetHelper getViewOffsetHelper(View view) { ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper); if (offsetHelper == null) { offsetHelper = new ViewOffsetHelper(view); view.setTag(R.id.view_offset_helper, offsetHelper); } return offsetHelper; } /** * Sets the title to be displayed by this view, if enabled. * * @see #setTitleEnabled(boolean) * @see #getTitle() */ public CollapsingTitleBarLayout setTitle(@Nullable CharSequence title) { mCollapsingTextHelper.setText(title); return this; } /** * Returns the title currently being displayed by this view. If the title is not enabled, then * this will return {@code null}. */ @Nullable public CharSequence getTitle() { return mCollapsingTitleEnabled ? mCollapsingTextHelper.getText() : null; } /** * Sets whether this view should display its own title. *

*

The title displayed by this view will shrink and grow based on the scroll offset.

* * @see #setTitle(CharSequence) * @see #isTitleEnabled() */ public CollapsingTitleBarLayout setTitleEnabled(boolean enabled) { if (enabled != mCollapsingTitleEnabled) { mCollapsingTitleEnabled = enabled; requestLayout(); } return this; } /** * Returns whether this view is currently displaying its own title. * * @see #setTitleEnabled(boolean) */ 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 CollapsingTitleBarLayout setScrimsShown(boolean shown) { return 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 CollapsingTitleBarLayout setScrimsShown(boolean shown, boolean animate) { if (mScrimsAreShown != shown) { if (animate) { animateScrim(shown ? 0xFF : 0x0); } else { setScrimAlpha(shown ? 0xFF : 0x0); } mScrimsAreShown = shown; } return this; } private void animateScrim(int targetAlpha) { ensureToolbar(); if (mScrimAnimator == null) { mScrimAnimator = new ValueAnimator(); mScrimAnimator.setDuration(mScrimAnimationDuration); mScrimAnimator.setInterpolator( targetAlpha > mScrimAlpha ? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR : AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR); mScrimAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { setScrimAlpha((Integer) animator.getAnimatedValue()); } }); } else if (mScrimAnimator.isRunning()) { mScrimAnimator.cancel(); } mScrimAnimator.setIntValues(mScrimAlpha, targetAlpha); mScrimAnimator.start(); } void setScrimAlpha(int alpha) { if (alpha != mScrimAlpha) { final Drawable contentScrim = mContentScrim; if (contentScrim != null && mTitleBar != null) { ViewCompat.postInvalidateOnAnimation(mTitleBar); } mScrimAlpha = alpha; ViewCompat.postInvalidateOnAnimation(CollapsingTitleBarLayout.this); } } int getScrimAlpha() { return mScrimAlpha; } /** * Set the drawable to use for the content scrim from resources. Providing null will disable * the scrim functionality. * * @param drawable the drawable to display * @see #getContentScrim() */ public CollapsingTitleBarLayout setContentScrim(@Nullable Drawable drawable) { if (mContentScrim != drawable) { if (mContentScrim != null) { mContentScrim.setCallback(null); } mContentScrim = drawable != null ? drawable.mutate() : null; if (mContentScrim != null) { mContentScrim.setBounds(0, 0, getWidth(), getHeight()); mContentScrim.setCallback(this); mContentScrim.setAlpha(mScrimAlpha); } ViewCompat.postInvalidateOnAnimation(this); } return this; } /** * Set the color to use for the content scrim. * * @param color the color to display * @see #getContentScrim() */ public CollapsingTitleBarLayout setContentScrimColor(@ColorInt int color) { return setContentScrim(new ColorDrawable(color)); } /** * Set the drawable to use for the content scrim from resources. * * @param resId drawable resource id * @see #getContentScrim() */ public CollapsingTitleBarLayout setContentScrimResource(@DrawableRes int resId) { return setContentScrim(ContextCompat.getDrawable(getContext(), resId)); } /** * Returns the drawable which is used for the foreground scrim. * * @see #setContentScrim(Drawable) */ @Nullable 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 * @see #getStatusBarScrim() */ public CollapsingTitleBarLayout setStatusBarScrim(@Nullable Drawable drawable) { if (mStatusBarScrim != drawable) { if (mStatusBarScrim != null) { mStatusBarScrim.setCallback(null); } mStatusBarScrim = drawable != null ? drawable.mutate() : null; if (mStatusBarScrim != null) { if (mStatusBarScrim.isStateful()) { mStatusBarScrim.setState(getDrawableState()); } DrawableCompat.setLayoutDirection(mStatusBarScrim, ViewCompat.getLayoutDirection(this)); mStatusBarScrim.setVisible(getVisibility() == VISIBLE, false); mStatusBarScrim.setCallback(this); mStatusBarScrim.setAlpha(mScrimAlpha); } ViewCompat.postInvalidateOnAnimation(this); } return this; } @Override protected void drawableStateChanged() { super.drawableStateChanged(); final int[] state = getDrawableState(); boolean changed = false; Drawable d = mStatusBarScrim; if (d != null && d.isStateful()) { changed |= d.setState(state); } d = mContentScrim; if (d != null && d.isStateful()) { changed |= d.setState(state); } if (mCollapsingTextHelper != null) { changed |= mCollapsingTextHelper.setState(state); } if (changed) { invalidate(); } } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mContentScrim || who == mStatusBarScrim; } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); final boolean visible = visibility == VISIBLE; if (mStatusBarScrim != null && mStatusBarScrim.isVisible() != visible) { mStatusBarScrim.setVisible(visible, false); } if (mContentScrim != null && mContentScrim.isVisible() != visible) { mContentScrim.setVisible(visible, false); } } /** * 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 * @see #getStatusBarScrim() */ public CollapsingTitleBarLayout setStatusBarScrimColor(@ColorInt int color) { return setStatusBarScrim(new ColorDrawable(color)); } /** * Set the drawable to use for the content scrim from resources. * * @param resId drawable resource id * @see #getStatusBarScrim() */ public CollapsingTitleBarLayout setStatusBarScrimResource(@DrawableRes int resId) { return setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId)); } /** * Returns the drawable which is used for the status bar scrim. * * @see #setStatusBarScrim(Drawable) */ @Nullable public Drawable getStatusBarScrim() { return mStatusBarScrim; } /** * Sets the text color and size for the collapsed title from the specified * TextAppearance resource. */ public CollapsingTitleBarLayout setCollapsedTitleTextAppearance(@StyleRes int resId) { mCollapsingTextHelper.setCollapsedTextAppearance(resId); return this; } /** * Sets the text color of the collapsed title. * * @param color The new text color in ARGB format */ public CollapsingTitleBarLayout setCollapsedTitleTextColor(@ColorInt int color) { setCollapsedTitleTextColor(ColorStateList.valueOf(color)); return this; } /** * Sets the text colors of the collapsed title. * * @param colors ColorStateList containing the new text colors */ public CollapsingTitleBarLayout setCollapsedTitleTextColor(@NonNull ColorStateList colors) { mCollapsingTextHelper.setCollapsedTextColor(colors); return this; } public CollapsingTitleBarLayout setCollapsedTitleTextSize(float size) { mCollapsingTextHelper.setCollapsedTextSize(size); return this; } /** * 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. */ public CollapsingTitleBarLayout setCollapsedTitleGravity(int gravity) { mCollapsingTextHelper.setCollapsedTextGravity(gravity); return this; } /** * Returns the horizontal and vertical alignment for title when collapsed. */ public int getCollapsedTitleGravity() { return mCollapsingTextHelper.getCollapsedTextGravity(); } /** * Sets the text color and size for the expanded title from the specified * TextAppearance resource. */ public CollapsingTitleBarLayout setExpandedTitleTextAppearance(@StyleRes int resId) { mCollapsingTextHelper.setExpandedTextAppearance(resId); return this; } /** * Sets the text color of the expanded title. * * @param color The new text color in ARGB format */ public CollapsingTitleBarLayout setExpandedTitleColor(@ColorInt int color) { return setExpandedTitleTextColor(ColorStateList.valueOf(color)); } /** * Sets the text colors of the expanded title. * * @param colors ColorStateList containing the new text colors */ public CollapsingTitleBarLayout setExpandedTitleTextColor(@NonNull ColorStateList colors) { mCollapsingTextHelper.setExpandedTextColor(colors); return this; } public CollapsingTitleBarLayout setExpandedTitleTextSize(float size) { mCollapsingTextHelper.setExpandedTextSize(size); return this; } /** * 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. */ public CollapsingTitleBarLayout setExpandedTitleGravity(int gravity) { mCollapsingTextHelper.setExpandedTextGravity(gravity); return this; } /** * Returns the horizontal and vertical alignment for title when expanded. */ public int getExpandedTitleGravity() { return mCollapsingTextHelper.getExpandedTextGravity(); } /** * Set the typeface to use for the collapsed title. * * @param typeface typeface to use, or {@code null} to use the default. */ public CollapsingTitleBarLayout setCollapsedTitleTypeface(@Nullable Typeface typeface) { mCollapsingTextHelper.setCollapsedTypeface(typeface); return this; } /** * Returns the typeface used for the collapsed title. */ @NonNull public Typeface getCollapsedTitleTypeface() { return mCollapsingTextHelper.getCollapsedTypeface(); } /** * Set the typeface to use for the expanded title. * * @param typeface typeface to use, or {@code null} to use the default. */ public CollapsingTitleBarLayout setExpandedTitleTypeface(@Nullable Typeface typeface) { mCollapsingTextHelper.setExpandedTypeface(typeface); return this; } /** * Returns the typeface used for the expanded title. */ @NonNull public Typeface getExpandedTitleTypeface() { return mCollapsingTextHelper.getExpandedTypeface(); } /** * Sets the expanded title margins. * * @param start the starting title margin in pixels * @param top the top title margin in pixels * @param end the ending title margin in pixels * @param bottom the bottom title margin in pixels * @see #getExpandedTitleMarginStart() * @see #getExpandedTitleMarginTop() * @see #getExpandedTitleMarginEnd() * @see #getExpandedTitleMarginBottom() */ public CollapsingTitleBarLayout setExpandedTitleMargin(int start, int top, int end, int bottom) { mExpandedMarginStart = start; mExpandedMarginTop = top; mExpandedMarginEnd = end; mExpandedMarginBottom = bottom; requestLayout(); return this; } /** * @return the starting expanded title margin in pixels * @see #setExpandedTitleMarginStart(int) */ public int getExpandedTitleMarginStart() { return mExpandedMarginStart; } /** * Sets the starting expanded title margin in pixels. * * @param margin the starting title margin in pixels * @see #getExpandedTitleMarginStart() */ public CollapsingTitleBarLayout setExpandedTitleMarginStart(int margin) { mExpandedMarginStart = margin; requestLayout(); return this; } /** * @return the top expanded title margin in pixels * @see #setExpandedTitleMarginTop(int) */ public int getExpandedTitleMarginTop() { return mExpandedMarginTop; } /** * Sets the top expanded title margin in pixels. * * @param margin the top title margin in pixels * @see #getExpandedTitleMarginTop() */ public CollapsingTitleBarLayout setExpandedTitleMarginTop(int margin) { mExpandedMarginTop = margin; requestLayout(); return this; } /** * @return the ending expanded title margin in pixels * @see #setExpandedTitleMarginEnd(int) */ public int getExpandedTitleMarginEnd() { return mExpandedMarginEnd; } /** * Sets the ending expanded title margin in pixels. * * @param margin the ending title margin in pixels * @see #getExpandedTitleMarginEnd() */ public CollapsingTitleBarLayout setExpandedTitleMarginEnd(int margin) { mExpandedMarginEnd = margin; requestLayout(); return this; } /** * @return the bottom expanded title margin in pixels * @see #setExpandedTitleMarginBottom(int) */ public int getExpandedTitleMarginBottom() { return mExpandedMarginBottom; } /** * Sets the bottom expanded title margin in pixels. * * @param margin the bottom title margin in pixels * @see #getExpandedTitleMarginBottom() */ public CollapsingTitleBarLayout setExpandedTitleMarginBottom(int margin) { mExpandedMarginBottom = margin; requestLayout(); return this; } /** * Set the amount of visible height in pixels used to define when to trigger a scrim * visibility change. *

*

If the visible height of this view is less than the given value, the scrims will be * made visible, otherwise they are hidden.

* * @param height value in pixels used to define when to trigger a scrim visibility change */ public CollapsingTitleBarLayout setScrimVisibleHeightTrigger(@IntRange(from = 0) final int height) { if (mScrimVisibleHeightTrigger != height) { mScrimVisibleHeightTrigger = height; // Update the scrim visibility updateScrimVisibility(); } return this; } /** * Returns the amount of visible height in pixels used to define when to trigger a scrim * visibility change. * * @see #setScrimVisibleHeightTrigger(int) */ public int getScrimVisibleHeightTrigger() { if (mScrimVisibleHeightTrigger >= 0) { // If we have one explicitly set, return it return mScrimVisibleHeightTrigger; } // Otherwise we'll use the default computed value final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; final int minHeight = ViewCompat.getMinimumHeight(this); if (minHeight > 0) { // If we have a minHeight set, lets use 2 * minHeight (capped at our height) return Math.min((minHeight * 2) + insetTop, getHeight()); } // If we reach here then we don't have a min height set. Instead we'll take a // guess at 1/3 of our height being visible return getHeight() / 3; } /** * Set the duration used for scrim visibility animations. * * @param duration the duration to use in milliseconds */ public CollapsingTitleBarLayout setScrimAnimationDuration(@IntRange(from = 0) final long duration) { mScrimAnimationDuration = duration; return this; } /** * Returns the duration in milliseconds used for scrim visibility animations. */ public long getScrimAnimationDuration() { return mScrimAnimationDuration; } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof CollapsingTitleBarLayout.LayoutParams; } @Override protected CollapsingTitleBarLayout.LayoutParams generateDefaultLayoutParams() { return new CollapsingTitleBarLayout.LayoutParams(CollapsingTitleBarLayout.LayoutParams.MATCH_PARENT, CollapsingTitleBarLayout.LayoutParams.MATCH_PARENT); } @Override public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { return new CollapsingTitleBarLayout.LayoutParams(getContext(), attrs); } @Override protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new CollapsingTitleBarLayout.LayoutParams(p); } public static class LayoutParams extends FrameLayout.LayoutParams { private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f; /** * @hide */ @RestrictTo(LIBRARY_GROUP) @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 CollapsingTitleBarLayout}. */ 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.CollapsingTitleBarLayout_Layout); mCollapseMode = a.getInt( R.styleable.CollapsingTitleBarLayout_Layout_collapsing_layout_collapseMode, COLLAPSE_MODE_OFF); setParallaxMultiplier(a.getFloat( R.styleable.CollapsingTitleBarLayout_Layout_collapsing_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); } @RequiresApi(19) @TargetApi(19) public LayoutParams(FrameLayout.LayoutParams source) { // The copy constructor called here only exists on API 19+. 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; } } /** * Show or hide the scrims if needed */ final void updateScrimVisibility() { if (mContentScrim != null || mStatusBarScrim != null) { setScrimsShown(getHeight() + mCurrentOffset < getScrimVisibleHeightTrigger()); } } final int getMaxOffsetForPinChild(View child) { final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child); final CollapsingTitleBarLayout.LayoutParams lp = (CollapsingTitleBarLayout.LayoutParams) child.getLayoutParams(); return getHeight() - offsetHelper.getLayoutTop() - child.getHeight() - lp.bottomMargin; } private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener { OffsetUpdateListener() { } @Override public void onOffsetChanged(AppBarLayout layout, int verticalOffset) { mCurrentOffset = verticalOffset; final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; 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 CollapsingTitleBarLayout.LayoutParams.COLLAPSE_MODE_PIN: offsetHelper.setTopAndBottomOffset(constrain(-verticalOffset, 0, getMaxOffsetForPinChild(child))); break; case CollapsingTitleBarLayout.LayoutParams.COLLAPSE_MODE_PARALLAX: offsetHelper.setTopAndBottomOffset( Math.round(-verticalOffset * lp.mParallaxMult)); break; } } // Show or hide the scrims if needed updateScrimVisibility(); if (mStatusBarScrim != null && insetTop > 0) { ViewCompat.postInvalidateOnAnimation(CollapsingTitleBarLayout.this); } // Update the collapsing text's fraction final int expandRange = getHeight() - ViewCompat.getMinimumHeight( CollapsingTitleBarLayout.this) - insetTop; mCollapsingTextHelper.setExpansionFraction( Math.abs(verticalOffset) / (float) expandRange); } } public static int constrain(int amount, int low, int high) { return amount < low ? low : (amount > high ? high : amount); } }