com.tr4android.support.extension.widget.FlexibleToolbarLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.tr4android.support.extension.widget.FlexibleToolbarLayout.java

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 * Copyright (C) 2015 Thomas Robert Altstidl
 *
 * 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 com.tr4android.support.extension.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.widget.AppBarLayout;
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 android.widget.ImageView;

import com.tr4android.appcompat.extension.R;
import com.tr4android.support.extension.animation.AnimationUtils;
import com.tr4android.support.extension.animation.ValueAnimatorCompat;
import com.tr4android.support.extension.internal.ViewOffsetHelper;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * FlexibleToolbarLayout 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}.
 * FlexibleToolbarLayout contains the following features:
 * <p/>
 * <h3>Collapsing title</h3>
 * A title which is larger when the layout is fully visible but collapses and becomes smaller as
 * the layout is scrolled off screen. You can set the title to display via
 * {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the
 * {@code titleCollapsedTextAppearance} and {@code titleExpandedTextAppearance} attributes.
 * <p/>
 * <h3>Collapsing subtitle</h3>
 * A subtitle which can be larger when the layout is fully visible but collapses and becomes smaller as
 * the layout is scrolled off screen. You can set the subtitle to display via
 * {@link #setSubtitle(CharSequence)}. The subtitle appearance can be tweaked via the
 * {@code subtitleCollapsedTextAppearance} and {@code subtitleExpandedTextAppearance} attributes.
 * <p/>
 * <h3>Collapsing title</h3>
 * An icon which is larger when the layout is fully visible but collapses and becomes smaller as
 * the layout is scrolled off screen. You can set the icon to display via
 * {@link #setIcon(Drawable)}. The icon size can be tweaked via the
 * {@code iconCollapsedSize} and {@code iconExpandedSize} attributes.
 * <p/>
 * <h3>Content scrim</h3>
 * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold.
 * You can change this via {@link #setContentScrim(Drawable)}.
 * <p/>
 * <h3>Status bar scrim</h3>
 * A scrim which is show or hidden behind the status bar when the scroll position has hit a certain
 * threshold. You can change this via {@link #setStatusBarScrim(Drawable)}. This only works
 * on {@link Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system windows.
 * <p/>
 * <h3>Parallax scrolling children</h3>
 * Child views can opt to be scrolled within this layout in a parallax fashion.
 * See {@link LayoutParams#COLLAPSE_MODE_PARALLAX} and
 * {@link LayoutParams#setParallaxMultiplier(float)}.
 * <p/>
 * <h3>Pinned position children</h3>
 * Child views can opt to be pinned in space globally. This is useful when implementing a
 * collapsing as it allows the {@link Toolbar} to be fixed in place even though this layout is
 * moving. See {@link LayoutParams#COLLAPSE_MODE_PIN}.
 *
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_titleCollapsedTextAppearance
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_titleExpandedTextAppearance
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_subtitleCollapsedTextAppearance
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_subtitleExpandedTextAppearance
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_iconCollapsedSize
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_iconExpandedSize
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_contentScrimColor
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_expandedMargin
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_expandedMarginStart
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_expandedMarginEnd
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_expandedMarginBottom
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_statusBarScrimColor
 * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_toolbarRefId
 */
public class FlexibleToolbarLayout extends FrameLayout {

    private static final int SCRIM_ANIMATION_DURATION = 600;

    private boolean mRefreshToolbar = true;
    private int mToolbarId;
    private Toolbar mToolbar;
    private View mDummyView;

    private int mExpandedMarginLeft;
    private int mExpandedMarginTop;
    private int mExpandedMarginRight;
    private int mExpandedMarginBottom;

    private final Rect mTmpRect = new Rect();
    private final Rect mExpandedBounds = new Rect();
    private boolean mDrawTitles;
    private int mSpaceTitleSubtitle;
    private int mSpaceIconTitles;

    private final CollapsingTextHelper mTitleCollapsingTextHelper;
    private boolean mCollapsingTitleEnabled;

    private final CollapsingTextHelper mSubtitleCollapsingTextHelper;
    private boolean mCollapsingSubtitleEnabled;

    private final CollapsingDrawableHelper mIconCollapsingHelper;
    private boolean mCollapsingIconEnabled;

    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 FlexibleToolbarLayout(Context context) {
        this(context, null);
    }

    public FlexibleToolbarLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FlexibleToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mTitleCollapsingTextHelper = new CollapsingTextHelper(this);
        mTitleCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.LINEAR_INTERPOLATOR);
        mSubtitleCollapsingTextHelper = new CollapsingTextHelper(this);
        mSubtitleCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.LINEAR_INTERPOLATOR);
        mIconCollapsingHelper = new CollapsingDrawableHelper(this);
        mIconCollapsingHelper.setIconSizeInterpolator(AnimationUtils.LINEAR_INTERPOLATOR);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FlexibleToolbarLayout, defStyleAttr,
                R.style.Widget_Design_FlexibleToolbarLayout);

        int expandedVerticalGravity = a.getInt(R.styleable.FlexibleToolbarLayout_expandedGravity,
                Gravity.CENTER_VERTICAL);

        mTitleCollapsingTextHelper.setExpandedTextGravity(GravityCompat.START | expandedVerticalGravity);
        mTitleCollapsingTextHelper.setCollapsedTextGravity(GravityCompat.START | Gravity.CENTER_VERTICAL);
        mSubtitleCollapsingTextHelper.setExpandedTextGravity(GravityCompat.START | expandedVerticalGravity);
        mSubtitleCollapsingTextHelper.setCollapsedTextGravity(GravityCompat.START | Gravity.CENTER_VERTICAL);

        mExpandedMarginLeft = mExpandedMarginTop = mExpandedMarginRight = mExpandedMarginBottom = a
                .getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_expandedMargin, 0);

        final boolean isRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
        if (a.hasValue(R.styleable.FlexibleToolbarLayout_expandedMarginStart)) {
            final int marginStart = a.getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_expandedMarginStart,
                    0);
            if (isRtl) {
                mExpandedMarginRight = marginStart;
            } else {
                mExpandedMarginLeft = marginStart;
            }
        }
        if (a.hasValue(R.styleable.FlexibleToolbarLayout_expandedMarginEnd)) {
            final int marginEnd = a.getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_expandedMarginEnd, 0);
            if (isRtl) {
                mExpandedMarginLeft = marginEnd;
            } else {
                mExpandedMarginRight = marginEnd;
            }
        }
        if (a.hasValue(R.styleable.FlexibleToolbarLayout_expandedMarginTop)) {
            mExpandedMarginTop = a.getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_expandedMarginTop, 0);
        }
        if (a.hasValue(R.styleable.FlexibleToolbarLayout_expandedMarginBottom)) {
            mExpandedMarginBottom = a.getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_expandedMarginBottom,
                    0);
        }

        mCollapsingTitleEnabled = a.getBoolean(R.styleable.FlexibleToolbarLayout_titleFlexibleEnabled, true);
        setTitle(a.getText(R.styleable.FlexibleToolbarLayout_title));
        mCollapsingSubtitleEnabled = a.getBoolean(R.styleable.FlexibleToolbarLayout_subtitleFlexibleEnabled, true);
        setSubtitle(a.getText(R.styleable.FlexibleToolbarLayout_subtitle));
        mCollapsingIconEnabled = a.getBoolean(R.styleable.FlexibleToolbarLayout_iconFlexibleEnabled, true);
        setIcon(a.getDrawable(R.styleable.FlexibleToolbarLayout_icon));

        // First load the default text appearances
        mTitleCollapsingTextHelper
                .setExpandedTextAppearance(R.style.TextAppearance_Design_FlexibleToolbarLayout_ExpandedTitle);
        mTitleCollapsingTextHelper
                .setCollapsedTextAppearance(R.style.TextAppearance_Design_FlexibleToolbarLayout_CollapsedTitle);
        mSubtitleCollapsingTextHelper
                .setExpandedTextAppearance(R.style.TextAppearance_Design_FlexibleToolbarLayout_Subtitle);
        mSubtitleCollapsingTextHelper
                .setCollapsedTextAppearance(R.style.TextAppearance_Design_FlexibleToolbarLayout_Subtitle);

        // Now overlay any custom text appearances
        if (a.hasValue(R.styleable.FlexibleToolbarLayout_titleExpandedTextAppearance)) {
            mTitleCollapsingTextHelper.setExpandedTextAppearance(
                    a.getResourceId(R.styleable.FlexibleToolbarLayout_titleExpandedTextAppearance, 0));
        }
        if (a.hasValue(R.styleable.FlexibleToolbarLayout_titleCollapsedTextAppearance)) {
            mTitleCollapsingTextHelper.setCollapsedTextAppearance(
                    a.getResourceId(R.styleable.FlexibleToolbarLayout_titleCollapsedTextAppearance, 0));

        }
        if (a.hasValue(R.styleable.FlexibleToolbarLayout_subtitleExpandedTextAppearance)) {
            mSubtitleCollapsingTextHelper.setExpandedTextAppearance(
                    a.getResourceId(R.styleable.FlexibleToolbarLayout_subtitleExpandedTextAppearance, 0));
        }
        if (a.hasValue(R.styleable.FlexibleToolbarLayout_subtitleCollapsedTextAppearance)) {
            mSubtitleCollapsingTextHelper.setCollapsedTextAppearance(
                    a.getResourceId(R.styleable.FlexibleToolbarLayout_subtitleCollapsedTextAppearance, 0));

        }

        // Load the icon sizes
        mIconCollapsingHelper.setCollapsedIconSize(
                a.getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_iconCollapsedSize, 0));
        mIconCollapsingHelper.setExpandedIconSize(
                a.getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_iconExpandedSize, 0));

        mSpaceTitleSubtitle = a.getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_spaceTitleSubtitle, 0);
        mSpaceIconTitles = a.getDimensionPixelSize(R.styleable.FlexibleToolbarLayout_spaceIconTitles, 0);

        setContentScrim(a.getDrawable(R.styleable.FlexibleToolbarLayout_contentScrimColor));
        setStatusBarScrim(a.getDrawable(R.styleable.FlexibleToolbarLayout_statusBarScrimColor));

        mToolbarId = a.getResourceId(R.styleable.FlexibleToolbarLayout_toolbarRefId, -1);

        a.recycle();

        setWillNotDraw(false);

        ViewCompat.setOnApplyWindowInsetsListener(this, new android.support.v4.view.OnApplyWindowInsetsListener() {
            @Override
            public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
                mLastInsets = insets;
                requestLayout();
                return insets.consumeSystemWindowInsets();
            }
        });
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Add an OnOffsetChangedListener if possible
        final ViewParent parent = getParent();
        if (parent instanceof AppBarLayout) {
            if (mOnOffsetChangedListener == null) {
                mOnOffsetChangedListener = new OffsetUpdateListener();
            }
            ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
        }
    }

    @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();
    }

    @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 (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) {
            mContentScrim.mutate().setAlpha(mScrimAlpha);
            mContentScrim.draw(canvas);
        }

        // Let the collapsing text helper draw it's text
        if (mCollapsingTitleEnabled && mDrawTitles) {
            mTitleCollapsingTextHelper.draw(canvas);
        }
        if (mCollapsingSubtitleEnabled && mDrawTitles) {
            mSubtitleCollapsingTextHelper.draw(canvas);
        }
        // Let the collapsing drawable helper draw it's drawable
        if (mCollapsingIconEnabled && mDrawTitles) {
            mIconCollapsingHelper.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 first when drawing the toolbar
        ensureToolbar();
        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);
        }
    }

    private void ensureToolbar() {
        if (!mRefreshToolbar) {
            return;
        }

        Toolbar fallback = null, selected = null;

        for (int i = 0, count = getChildCount(); i < count; i++) {
            final View child = getChildAt(i);
            if (child instanceof Toolbar) {
                if (mToolbarId != -1) {
                    // There's a toolbar id set so try and find it...
                    if (mToolbarId == child.getId()) {
                        // We found the primary Toolbar, use it
                        selected = (Toolbar) child;
                        break;
                    }
                    if (fallback == null) {
                        // We'll record the first Toolbar as our fallback
                        fallback = (Toolbar) child;
                    }
                } else {
                    // We don't have a id to check for so just use the first we come across
                    selected = (Toolbar) child;
                    break;
                }
            }
        }

        if (selected == null) {
            // If we didn't find a primary Toolbar, use the fallback
            selected = fallback;
        }

        mToolbar = selected;
        updateDummyView();
        mRefreshToolbar = false;
    }

    private void updateDummyView() {
        if (!mCollapsingTitleEnabled && mDummyView != null) {
            // If we have a dummy view and we have our title disabled, remove it from its parent
            final ViewParent parent = mDummyView.getParent();
            if (parent instanceof ViewGroup) {
                ((ViewGroup) parent).removeView(mDummyView);
            }
        }
        if (mCollapsingTitleEnabled && mToolbar != null) {
            if (mDummyView == null) {
                mDummyView = new View(getContext());
            }
            if (mDummyView.getParent() == null) {
                mToolbar.addView(mDummyView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            }
        }
    }

    @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);

        // We only draw the title and subtitle if the dummy view is being displayed (Toolbar removes
        // views if there is no space)
        mDrawTitles = mDummyView != null && mDummyView.isShown();

        // This is the amount of horizontal offset needed for the title and subtitle
        int horizontalOffsetCollapsed = mCollapsingIconEnabled
                ? (int) mIconCollapsingHelper.getCollapsedIconSize() + mSpaceIconTitles
                : 0;
        int horizontalOffsetExpanded = mCollapsingIconEnabled
                ? (int) mIconCollapsingHelper.getExpandedIconSize() + mSpaceIconTitles
                : 0;

        // These are the expanded bounds needed for the icon, title and subtitle
        int expandedIconSize = (int) mIconCollapsingHelper.getExpandedIconSize();
        mExpandedBounds.set(mExpandedMarginLeft,
                mCollapsingIconEnabled ? (bottom - top - mExpandedMarginBottom - expandedIconSize)
                        : (mTmpRect.bottom + mExpandedMarginTop),
                right - left - mExpandedMarginRight, bottom - top - mExpandedMarginBottom);

        // Update the collapsed bounds by getting it's transformed bounds. This needs to be done
        // before the children are offset below
        if (mCollapsingTitleEnabled && mDrawTitles) {
            ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
            // Update the collapsed bounds
            mTitleCollapsingTextHelper.setCollapsedBounds(mTmpRect.left + horizontalOffsetCollapsed,
                    bottom - mTmpRect.height(), mTmpRect.right, bottom);
            // Update the expanded bounds
            mTitleCollapsingTextHelper.setExpandedBounds(mExpandedBounds.left + horizontalOffsetExpanded,
                    mExpandedBounds.top, mExpandedBounds.right, mExpandedBounds.bottom);
            // Adjust the offset when subtitle is present
            if (mCollapsingSubtitleEnabled) {
                mTitleCollapsingTextHelper.setCollapsedTextOffsetBottom(
                        mSpaceTitleSubtitle + mSubtitleCollapsingTextHelper.getCollapsedTextHeight());
                mTitleCollapsingTextHelper.setExpandedTextOffsetBottom(
                        mSpaceTitleSubtitle + mSubtitleCollapsingTextHelper.getExpandedTextHeight());
            }
            // Now recalculate using the new bounds
            mTitleCollapsingTextHelper.recalculate();
        }
        if (mCollapsingSubtitleEnabled && mDrawTitles) {
            ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
            // Update the collapsed bounds
            mSubtitleCollapsingTextHelper.setCollapsedBounds(mTmpRect.left + horizontalOffsetCollapsed,
                    bottom - mTmpRect.height(), mTmpRect.right, bottom);
            // Update the expanded bounds
            mSubtitleCollapsingTextHelper.setExpandedBounds(mExpandedBounds.left + horizontalOffsetExpanded,
                    mExpandedBounds.top, mExpandedBounds.right, mExpandedBounds.bottom);
            // Adjust the offset when title is present
            if (mCollapsingTitleEnabled) {
                mSubtitleCollapsingTextHelper.setCollapsedTextOffsetTop(
                        mSpaceTitleSubtitle + mTitleCollapsingTextHelper.getCollapsedTextHeight());
                mSubtitleCollapsingTextHelper.setExpandedTextOffsetTop(
                        mSpaceTitleSubtitle + mTitleCollapsingTextHelper.getExpandedTextHeight());
            }
            // Now recalculate using the new bounds
            mSubtitleCollapsingTextHelper.recalculate();
        }
        if (mCollapsingIconEnabled && mDrawTitles) {
            ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
            // Update the collapsed bounds
            int collapsedIconSize = (int) mIconCollapsingHelper.getCollapsedIconSize();
            int collapsedTop = bottom - mTmpRect.height()
                    + Math.round((mTmpRect.height() - collapsedIconSize) / 2f);
            mIconCollapsingHelper.setCollapsedBounds(mTmpRect.left, collapsedTop, mTmpRect.left + collapsedIconSize,
                    collapsedTop + collapsedIconSize);
            // Update the expanded bounds
            mIconCollapsingHelper.setExpandedBounds(mExpandedBounds.left, mExpandedBounds.top,
                    mExpandedMarginLeft + expandedIconSize, mExpandedBounds.bottom);
            // Now recalculate using the new bounds
            mIconCollapsingHelper.recalculate();
        }

        // Update our child view offset helpers
        for (int i = 0, z = getChildCount(); i < z; i++) {
            final View child = getChildAt(i);

            if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) {
                final int insetTop = mLastInsets.getSystemWindowInsetTop();
                if (child.getTop() < insetTop) {
                    // If the child isn't set to fit system windows but is drawing within the inset
                    // offset it down
                    child.offsetTopAndBottom(insetTop);
                }
            }

            getViewOffsetHelper(child).onViewLayout();
        }

        // Finally, set our minimum height to enable proper AppBarLayout collapsing
        if (mToolbar != null) {
            if (mCollapsingTitleEnabled && TextUtils.isEmpty(mTitleCollapsingTextHelper.getText())) {
                // If we do not currently have a title, try and grab it from the Toolbar
                mTitleCollapsingTextHelper.setText(mToolbar.getTitle());
            }
            setMinimumHeight(mToolbar.getHeight());
        }
    }

    private 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;
    }

    /**
     * Collapses the {@link FlexibleToolbarLayout}.
     * This will only have an effect if the {@link FlexibleToolbarLayout}
     * is used as a child of {@link AppBarLayout}.
     */
    public void collapse() {
        // Passes call to AppBarLayout if possible
        ViewParent parent = getParent();
        if (parent instanceof AppBarLayout) {
            ((AppBarLayout) parent).setExpanded(false);
        }
    }

    /**
     * Collapses the FlexibleToolbarLayout.
     * This will only have an effect if the {@link FlexibleToolbarLayout}
     * is used as a child of {@link AppBarLayout}.
     *
     * @param animate Whether or not the collapse should be animated
     */
    public void collapse(boolean animate) {
        // Passes call to AppBarLayout if possible
        ViewParent parent = getParent();
        if (parent instanceof AppBarLayout) {
            ((AppBarLayout) parent).setExpanded(false, animate);
        }
    }

    /**
     * Expands the {@link FlexibleToolbarLayout}.
     * This will only have an effect if the {@link FlexibleToolbarLayout}
     * is used as a child of {@link AppBarLayout}.
     */
    public void expand() {
        // Passes call to AppBarLayout if possible
        ViewParent parent = getParent();
        if (parent instanceof AppBarLayout) {
            ((AppBarLayout) parent).setExpanded(true);
        }
    }

    /**
     * Expands the FlexibleToolbarLayout.
     * This will only have an effect if the {@link FlexibleToolbarLayout}
     * is used as a child of {@link AppBarLayout}.
     *
     * @param animate Whether or not the expansion should be animated
     */
    public void expand(boolean animate) {
        // Passes call to AppBarLayout if possible
        ViewParent parent = getParent();
        if (parent instanceof AppBarLayout) {
            ((AppBarLayout) parent).setExpanded(true, animate);
        }
    }

    /**
     * Sets the title to be displayed by this view, if enabled.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_title
     * @see #setTitleEnabled(boolean)
     * @see #getTitle()
     */
    public void setTitle(@Nullable CharSequence title) {
        mTitleCollapsingTextHelper.setText(title);
    }

    /**
     * Returns the title currently being displayed by this view. If the title is not enabled, then
     * this will return {@code null}.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_title
     */
    @Nullable
    public CharSequence getTitle() {
        return mCollapsingTitleEnabled ? mTitleCollapsingTextHelper.getText() : null;
    }

    /**
     * Sets whether this view should display its own title.
     * <p/>
     * <p>The title displayed by this view will shrink and grow based on the scroll offset.</p>
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_titleFlexibleEnabled
     * @see #setTitle(CharSequence)
     * @see #isTitleEnabled()
     */
    public void setTitleEnabled(boolean enabled) {
        if (enabled != mCollapsingTitleEnabled) {
            mCollapsingTitleEnabled = enabled;
            updateDummyView();
            requestLayout();
        }
    }

    /**
     * Returns whether this view is currently displaying its own title.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_titleFlexibleEnabled
     * @see #setTitleEnabled(boolean)
     */
    public boolean isTitleEnabled() {
        return mCollapsingTitleEnabled;
    }

    /**
     * Sets the subtitle to be displayed by this view, if enabled.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_subtitle
     * @see #setSubtitleEnabled(boolean)
     * @see #getSubtitle()
     */
    public void setSubtitle(@Nullable CharSequence subtitle) {
        mSubtitleCollapsingTextHelper.setText(subtitle);
    }

    /**
     * Returns the subtitle currently being displayed by this view. If the subtitle is not enabled, then
     * this will return {@code null}.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_subtitle
     */
    @Nullable
    public CharSequence getSubtitle() {
        return mCollapsingSubtitleEnabled ? mSubtitleCollapsingTextHelper.getText() : null;
    }

    /**
     * Sets whether this view should display its own subtitle.
     * <p/>
     * <p>The subtitle displayed by this view will shrink and grow based on the scroll offset.</p>
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_subtitleFlexibleEnabled
     * @see #setSubtitle(CharSequence)
     * @see #isSubtitleEnabled()
     */
    public void setSubtitleEnabled(boolean enabled) {
        if (enabled != mCollapsingSubtitleEnabled) {
            mCollapsingSubtitleEnabled = enabled;
            updateDummyView();
            requestLayout();
        }
    }

    /**
     * Returns whether this view is currently displaying its own subtitle.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_subtitleFlexibleEnabled
     * @see #setSubtitleEnabled(boolean)
     */
    public boolean isSubtitleEnabled() {
        return mCollapsingSubtitleEnabled;
    }

    /**
     * Sets the icon to be displayed by this view, if enabled.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_icon
     * @see #setIconEnabled(boolean)
     * @see #getIcon()
     */
    public void setIcon(@Nullable Drawable icon) {
        mIconCollapsingHelper.setDrawable(icon);
    }

    /**
     * Returns the icon currently being displayed by this view. If the icon is not enabled, then
     * this will return {@code null}.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_icon
     */
    @Nullable
    public Drawable getIcon() {
        return mCollapsingIconEnabled ? mIconCollapsingHelper.getDrawable() : null;
    }

    /**
     * Sets whether this view should display its own icon.
     * <p/>
     * <p>The icon displayed by this view will shrink and grow based on the scroll offset.</p>
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_iconFlexibleEnabled
     * @see #setIcon(Drawable)
     * @see #isIconEnabled()
     */
    public void setIconEnabled(boolean enabled) {
        if (enabled != mCollapsingIconEnabled) {
            mCollapsingIconEnabled = enabled;
            requestLayout();
        }
    }

    /**
     * Returns whether this view is currently displaying its own icon.
     *
     * @attr ref R.styleable#FlexibleToolbarLayout_iconFlexibleEnabled
     * @see #setIconEnabled(boolean)
     */
    public boolean isIconEnabled() {
        return mCollapsingIconEnabled;
    }

    /**
     * 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 = AnimationUtils.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(FlexibleToolbarLayout.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#FlexibleToolbarLayout_contentScrimColor
     * @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#FlexibleToolbarLayout_contentScrimColor
     * @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#FlexibleToolbarLayout_contentScrimColor
     * @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#FlexibleToolbarLayout_contentScrimColor
     * @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/>
     * <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#FlexibleToolbarLayout_statusBarScrimColor
     * @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/>
     * <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#FlexibleToolbarLayout_statusBarScrimColor
     * @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#FlexibleToolbarLayout_statusBarScrimColor
     * @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#FlexibleToolbarLayout_statusBarScrimColor
     * @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#FlexibleToolbarLayout_titleCollapsedTextAppearance
     */
    public void setCollapsedTitleTextAppearance(@StyleRes int resId) {
        mTitleCollapsingTextHelper.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) {
        mTitleCollapsingTextHelper.setCollapsedTextColor(color);
    }

    /**
     * Sets the text color and size for the expanded title from the specified
     * TextAppearance resource.
     *
     * @attr ref android.support.design.R.styleable#FlexibleToolbarLayout_titleExpandedTextAppearance
     */
    public void setExpandedTitleTextAppearance(@StyleRes int resId) {
        mTitleCollapsingTextHelper.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) {
        mTitleCollapsingTextHelper.setExpandedTextColor(color);
    }

    /**
     * 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 FlexibleToolbarLayout}.
         */
        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.FlexibleToolbarLayout_LayoutParams);
            mCollapseMode = a.getInt(R.styleable.FlexibleToolbarLayout_LayoutParams_layout_collapseMode,
                    COLLAPSE_MODE_OFF);
            setParallaxMultiplier(
                    a.getFloat(R.styleable.FlexibleToolbarLayout_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(FlexibleToolbarLayout.this);
            }

            // Update the collapsing text's fraction
            final int expandRange = getHeight() - ViewCompat.getMinimumHeight(FlexibleToolbarLayout.this)
                    - insetTop;
            float fraction = Math.abs(verticalOffset) / (float) expandRange;
            mTitleCollapsingTextHelper.setExpansionFraction(fraction);
            mSubtitleCollapsingTextHelper.setExpansionFraction(fraction);
            mIconCollapsingHelper.setExpansionFraction(fraction);
        }
    }
}