CollapsingToolbarLayout.java revision 8a40d691aa783c2e85298e895a3e51e98606aa85
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.design.widget;
18
19import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.graphics.Typeface;
25import android.graphics.drawable.ColorDrawable;
26import android.graphics.drawable.Drawable;
27import android.support.annotation.ColorInt;
28import android.support.annotation.DrawableRes;
29import android.support.annotation.IntDef;
30import android.support.annotation.IntRange;
31import android.support.annotation.NonNull;
32import android.support.annotation.Nullable;
33import android.support.annotation.StyleRes;
34import android.support.design.R;
35import android.support.v4.content.ContextCompat;
36import android.support.v4.graphics.drawable.DrawableCompat;
37import android.support.v4.view.GravityCompat;
38import android.support.v4.view.ViewCompat;
39import android.support.v4.view.WindowInsetsCompat;
40import android.support.v7.widget.Toolbar;
41import android.text.TextUtils;
42import android.util.AttributeSet;
43import android.view.Gravity;
44import android.view.View;
45import android.view.ViewGroup;
46import android.view.ViewParent;
47import android.widget.FrameLayout;
48
49import java.lang.annotation.Retention;
50import java.lang.annotation.RetentionPolicy;
51
52import static android.support.design.widget.MathUtils.constrain;
53import static android.support.design.widget.ViewUtils.objectEquals;
54
55/**
56 * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar.
57 * It is designed to be used as a direct child of a {@link AppBarLayout}.
58 * CollapsingToolbarLayout contains the following features:
59 *
60 * <h4>Collapsing title</h4>
61 * A title which is larger when the layout is fully visible but collapses and becomes smaller as
62 * the layout is scrolled off screen. You can set the title to display via
63 * {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the
64 * {@code collapsedTextAppearance} and {@code expandedTextAppearance} attributes.
65 *
66 * <h4>Content scrim</h4>
67 * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold.
68 * You can change this via {@link #setContentScrim(Drawable)}.
69 *
70 * <h4>Status bar scrim</h4>
71 * A scrim which is show or hidden behind the status bar when the scroll position has hit a certain
72 * threshold. You can change this via {@link #setStatusBarScrim(Drawable)}. This only works
73 * on {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system
74 * windows.
75 *
76 * <h4>Parallax scrolling children</h4>
77 * Child views can opt to be scrolled within this layout in a parallax fashion.
78 * See {@link LayoutParams#COLLAPSE_MODE_PARALLAX} and
79 * {@link LayoutParams#setParallaxMultiplier(float)}.
80 *
81 * <h4>Pinned position children</h4>
82 * Child views can opt to be pinned in space globally. This is useful when implementing a
83 * collapsing as it allows the {@link Toolbar} to be fixed in place even though this layout is
84 * moving. See {@link LayoutParams#COLLAPSE_MODE_PIN}.
85 *
86 * <p><strong>Do not manually add views to the Toolbar at run time</strong>.
87 * We will add a 'dummy view' to the Toolbar which allows us to work out the available space
88 * for the title. This can interfere with any views which you add.</p>
89 *
90 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance
91 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance
92 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_contentScrim
93 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin
94 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
95 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
96 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
97 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_statusBarScrim
98 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_toolbarId
99 */
100public class CollapsingToolbarLayout extends FrameLayout {
101
102    private static final int DEFAULT_SCRIM_ANIMATION_DURATION = 600;
103
104    private boolean mRefreshToolbar = true;
105    private int mToolbarId;
106    private Toolbar mToolbar;
107    private View mToolbarDirectChild;
108    private View mDummyView;
109    private int mToolbarDrawIndex;
110
111    private int mExpandedMarginStart;
112    private int mExpandedMarginTop;
113    private int mExpandedMarginEnd;
114    private int mExpandedMarginBottom;
115
116    private final Rect mTmpRect = new Rect();
117    private final CollapsingTextHelper mCollapsingTextHelper;
118    private boolean mCollapsingTitleEnabled;
119    private boolean mDrawCollapsingTitle;
120
121    private Drawable mContentScrim;
122    private Drawable mStatusBarScrim;
123    private int mScrimAlpha;
124    private boolean mScrimsAreShown;
125    private ValueAnimatorCompat mScrimAnimator;
126    private long mScrimAnimationDuration;
127    private int mScrimVisibleHeightTrigger = -1;
128
129    private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener;
130
131    private int mCurrentOffset;
132
133    private WindowInsetsCompat mLastInsets;
134
135    public CollapsingToolbarLayout(Context context) {
136        this(context, null);
137    }
138
139    public CollapsingToolbarLayout(Context context, AttributeSet attrs) {
140        this(context, attrs, 0);
141    }
142
143    public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
144        super(context, attrs, defStyleAttr);
145
146        ThemeUtils.checkAppCompatTheme(context);
147
148        mCollapsingTextHelper = new CollapsingTextHelper(this);
149        mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
150
151        TypedArray a = context.obtainStyledAttributes(attrs,
152                R.styleable.CollapsingToolbarLayout, defStyleAttr,
153                R.style.Widget_Design_CollapsingToolbar);
154
155        mCollapsingTextHelper.setExpandedTextGravity(
156                a.getInt(R.styleable.CollapsingToolbarLayout_expandedTitleGravity,
157                        GravityCompat.START | Gravity.BOTTOM));
158        mCollapsingTextHelper.setCollapsedTextGravity(
159                a.getInt(R.styleable.CollapsingToolbarLayout_collapsedTitleGravity,
160                        GravityCompat.START | Gravity.CENTER_VERTICAL));
161
162        mExpandedMarginStart = mExpandedMarginTop = mExpandedMarginEnd = mExpandedMarginBottom =
163                a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMargin, 0);
164
165        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart)) {
166            mExpandedMarginStart = a.getDimensionPixelSize(
167                    R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart, 0);
168        }
169        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd)) {
170            mExpandedMarginEnd = a.getDimensionPixelSize(
171                    R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd, 0);
172        }
173        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop)) {
174            mExpandedMarginTop = a.getDimensionPixelSize(
175                    R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop, 0);
176        }
177        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom)) {
178            mExpandedMarginBottom = a.getDimensionPixelSize(
179                    R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom, 0);
180        }
181
182        mCollapsingTitleEnabled = a.getBoolean(
183                R.styleable.CollapsingToolbarLayout_titleEnabled, true);
184        setTitle(a.getText(R.styleable.CollapsingToolbarLayout_title));
185
186        // First load the default text appearances
187        mCollapsingTextHelper.setExpandedTextAppearance(
188                R.style.TextAppearance_Design_CollapsingToolbar_Expanded);
189        mCollapsingTextHelper.setCollapsedTextAppearance(
190                android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
191
192        // Now overlay any custom text appearances
193        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) {
194            mCollapsingTextHelper.setExpandedTextAppearance(
195                    a.getResourceId(
196                            R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance, 0));
197        }
198        if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance)) {
199            mCollapsingTextHelper.setCollapsedTextAppearance(
200                    a.getResourceId(
201                            R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 0));
202        }
203
204        mScrimVisibleHeightTrigger = a.getInt(
205                R.styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger, -1);
206
207        mScrimAnimationDuration = a.getInt(
208                R.styleable.CollapsingToolbarLayout_scrimAnimationDuration,
209                DEFAULT_SCRIM_ANIMATION_DURATION);
210
211        setContentScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim));
212        setStatusBarScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim));
213
214        mToolbarId = a.getResourceId(R.styleable.CollapsingToolbarLayout_toolbarId, -1);
215
216        a.recycle();
217
218        setWillNotDraw(false);
219
220        ViewCompat.setOnApplyWindowInsetsListener(this,
221                new android.support.v4.view.OnApplyWindowInsetsListener() {
222                    @Override
223                    public WindowInsetsCompat onApplyWindowInsets(View v,
224                            WindowInsetsCompat insets) {
225                        return onWindowInsetChanged(insets);
226                    }
227                });
228    }
229
230    @Override
231    protected void onAttachedToWindow() {
232        super.onAttachedToWindow();
233
234        // Add an OnOffsetChangedListener if possible
235        final ViewParent parent = getParent();
236        if (parent instanceof AppBarLayout) {
237            if (mOnOffsetChangedListener == null) {
238                mOnOffsetChangedListener = new OffsetUpdateListener();
239            }
240            ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
241        }
242
243        // We're attached, so lets request an inset dispatch
244        ViewCompat.requestApplyInsets(this);
245    }
246
247    @Override
248    protected void onDetachedFromWindow() {
249        // Remove our OnOffsetChangedListener if possible and it exists
250        final ViewParent parent = getParent();
251        if (mOnOffsetChangedListener != null && parent instanceof AppBarLayout) {
252            ((AppBarLayout) parent).removeOnOffsetChangedListener(mOnOffsetChangedListener);
253        }
254
255        super.onDetachedFromWindow();
256    }
257
258    private WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) {
259        WindowInsetsCompat newInsets = null;
260
261        if (ViewCompat.getFitsSystemWindows(this)) {
262            // If we're set to fit system windows, keep the insets
263            newInsets = insets;
264        }
265
266        // If our insets have changed, keep them and invalidate the scroll ranges...
267        if (!objectEquals(mLastInsets, newInsets)) {
268            mLastInsets = newInsets;
269            requestLayout();
270        }
271
272        return insets;
273    }
274
275    @Override
276    public void draw(Canvas canvas) {
277        super.draw(canvas);
278
279        // If we don't have a toolbar, the scrim will be not be drawn in drawChild() below.
280        // Instead, we draw it here, before our collapsing text.
281        ensureToolbar();
282        if (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) {
283            mContentScrim.mutate().setAlpha(mScrimAlpha);
284            mContentScrim.draw(canvas);
285        }
286
287        // Let the collapsing text helper draw its text
288        if (mCollapsingTitleEnabled && mDrawCollapsingTitle) {
289            mCollapsingTextHelper.draw(canvas);
290        }
291
292        // Now draw the status bar scrim
293        if (mStatusBarScrim != null && mScrimAlpha > 0) {
294            final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
295            if (topInset > 0) {
296                mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(),
297                        topInset - mCurrentOffset);
298                mStatusBarScrim.mutate().setAlpha(mScrimAlpha);
299                mStatusBarScrim.draw(canvas);
300            }
301        }
302    }
303
304    @Override
305    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
306        // This is a little weird. Our scrim needs to be behind the Toolbar (if it is present),
307        // but in front of any other children which are behind it. To do this we intercept the
308        // drawChild() call, and draw our scrim after the preceding view is drawn
309        boolean invalidate = super.drawChild(canvas, child, drawingTime);
310
311        if (mContentScrim != null && mScrimAlpha > 0 && isToolbarChildDrawnNext(child)) {
312            mContentScrim.mutate().setAlpha(mScrimAlpha);
313            mContentScrim.draw(canvas);
314            invalidate = true;
315        }
316
317        return invalidate;
318    }
319
320    @Override
321    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
322        super.onSizeChanged(w, h, oldw, oldh);
323        if (mContentScrim != null) {
324            mContentScrim.setBounds(0, 0, w, h);
325        }
326    }
327
328    private void ensureToolbar() {
329        if (!mRefreshToolbar) {
330            return;
331        }
332
333        // First clear out the current Toolbar
334        mToolbar = null;
335        mToolbarDirectChild = null;
336
337        if (mToolbarId != -1) {
338            // If we have an ID set, try and find it and it's direct parent to us
339            mToolbar = (Toolbar) findViewById(mToolbarId);
340            if (mToolbar != null) {
341                mToolbarDirectChild = findDirectChild(mToolbar);
342            }
343        }
344
345        if (mToolbar == null) {
346            // If we don't have an ID, or couldn't find a Toolbar with the correct ID, try and find
347            // one from our direct children
348            Toolbar toolbar = null;
349            for (int i = 0, count = getChildCount(); i < count; i++) {
350                final View child = getChildAt(i);
351                if (child instanceof Toolbar) {
352                    toolbar = (Toolbar) child;
353                    break;
354                }
355            }
356            mToolbar = toolbar;
357        }
358
359        updateDummyView();
360        mRefreshToolbar = false;
361    }
362
363    private boolean isToolbarChildDrawnNext(View child) {
364        return mToolbarDrawIndex >= 0 && mToolbarDrawIndex == indexOfChild(child) + 1;
365    }
366
367    /**
368     * Returns the direct child of this layout, which itself is the ancestor of the
369     * given view.
370     */
371    private View findDirectChild(final View descendant) {
372        View directChild = descendant;
373        for (ViewParent p = descendant.getParent(); p != this && p != null; p = p.getParent()) {
374            if (p instanceof View) {
375                directChild = (View) p;
376            }
377        }
378        return directChild;
379    }
380
381    private void updateDummyView() {
382        if (!mCollapsingTitleEnabled && mDummyView != null) {
383            // If we have a dummy view and we have our title disabled, remove it from its parent
384            final ViewParent parent = mDummyView.getParent();
385            if (parent instanceof ViewGroup) {
386                ((ViewGroup) parent).removeView(mDummyView);
387            }
388        }
389        if (mCollapsingTitleEnabled && mToolbar != null) {
390            if (mDummyView == null) {
391                mDummyView = new View(getContext());
392            }
393            if (mDummyView.getParent() == null) {
394                mToolbar.addView(mDummyView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
395            }
396        }
397    }
398
399    @Override
400    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
401        ensureToolbar();
402        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
403    }
404
405    @Override
406    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
407        super.onLayout(changed, left, top, right, bottom);
408
409        // Update the collapsed bounds by getting it's transformed bounds. This needs to be done
410        // before the children are offset below
411        if (mCollapsingTitleEnabled && mDummyView != null) {
412            // We only draw the title if the dummy view is being displayed (Toolbar removes
413            // views if there is no space)
414            mDrawCollapsingTitle = ViewCompat.isAttachedToWindow(mDummyView)
415                    && mDummyView.getVisibility() == VISIBLE;
416
417            if (mDrawCollapsingTitle) {
418                final boolean isRtl = ViewCompat.getLayoutDirection(this)
419                        == ViewCompat.LAYOUT_DIRECTION_RTL;
420
421                // Update the collapsed bounds
422                final int maxOffset = getMaxOffsetForPinChild(
423                        mToolbarDirectChild != null ? mToolbarDirectChild : mToolbar);
424                ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
425                mCollapsingTextHelper.setCollapsedBounds(
426                        mTmpRect.left + (isRtl
427                                ? mToolbar.getTitleMarginEnd()
428                                : mToolbar.getTitleMarginStart()),
429                        mTmpRect.top + maxOffset + mToolbar.getTitleMarginTop(),
430                        mTmpRect.right + (isRtl
431                                ? mToolbar.getTitleMarginStart()
432                                : mToolbar.getTitleMarginEnd()),
433                        mTmpRect.bottom + maxOffset - mToolbar.getTitleMarginBottom());
434
435                // Update the expanded bounds
436                mCollapsingTextHelper.setExpandedBounds(
437                        isRtl ? mExpandedMarginEnd : mExpandedMarginStart,
438                        mTmpRect.bottom + mExpandedMarginTop,
439                        right - left - (isRtl ? mExpandedMarginStart : mExpandedMarginEnd),
440                        bottom - top - mExpandedMarginBottom);
441                // Now recalculate using the new bounds
442                mCollapsingTextHelper.recalculate();
443            }
444        }
445
446        // Update our child view offset helpers
447        for (int i = 0, z = getChildCount(); i < z; i++) {
448            final View child = getChildAt(i);
449
450            if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) {
451                final int insetTop = mLastInsets.getSystemWindowInsetTop();
452                if (child.getTop() < insetTop) {
453                    // If the child isn't set to fit system windows but is drawing within the inset
454                    // offset it down
455                    ViewCompat.offsetTopAndBottom(child, insetTop);
456                }
457            }
458
459            getViewOffsetHelper(child).onViewLayout();
460        }
461
462        ensureToolbar();
463        // Finally, set our minimum height to enable proper AppBarLayout collapsing
464        if (mToolbar != null) {
465            if (mCollapsingTitleEnabled && TextUtils.isEmpty(mCollapsingTextHelper.getText())) {
466                // If we do not currently have a title, try and grab it from the Toolbar
467                mCollapsingTextHelper.setText(mToolbar.getTitle());
468            }
469            if (mToolbarDirectChild == null || mToolbarDirectChild == this) {
470                setMinimumHeight(getHeightWithMargins(mToolbar));
471                mToolbarDrawIndex = indexOfChild(mToolbar);
472            } else {
473                setMinimumHeight(getHeightWithMargins(mToolbarDirectChild));
474                mToolbarDrawIndex = indexOfChild(mToolbarDirectChild);
475            }
476        } else {
477            mToolbarDrawIndex = -1;
478        }
479
480        updateScrimVisibility();
481    }
482
483    private static int getHeightWithMargins(@NonNull final View view) {
484        final ViewGroup.LayoutParams lp = view.getLayoutParams();
485        if (lp instanceof MarginLayoutParams) {
486            final MarginLayoutParams mlp = (MarginLayoutParams) lp;
487            return view.getHeight() + mlp.topMargin + mlp.bottomMargin;
488        }
489        return view.getHeight();
490    }
491
492    private static ViewOffsetHelper getViewOffsetHelper(View view) {
493        ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper);
494        if (offsetHelper == null) {
495            offsetHelper = new ViewOffsetHelper(view);
496            view.setTag(R.id.view_offset_helper, offsetHelper);
497        }
498        return offsetHelper;
499    }
500
501    /**
502     * Sets the title to be displayed by this view, if enabled.
503     *
504     * @see #setTitleEnabled(boolean)
505     * @see #getTitle()
506     *
507     * @attr ref R.styleable#CollapsingToolbarLayout_title
508     */
509    public void setTitle(@Nullable CharSequence title) {
510        mCollapsingTextHelper.setText(title);
511    }
512
513    /**
514     * Returns the title currently being displayed by this view. If the title is not enabled, then
515     * this will return {@code null}.
516     *
517     * @attr ref R.styleable#CollapsingToolbarLayout_title
518     */
519    @Nullable
520    public CharSequence getTitle() {
521        return mCollapsingTitleEnabled ? mCollapsingTextHelper.getText() : null;
522    }
523
524    /**
525     * Sets whether this view should display its own title.
526     *
527     * <p>The title displayed by this view will shrink and grow based on the scroll offset.</p>
528     *
529     * @see #setTitle(CharSequence)
530     * @see #isTitleEnabled()
531     *
532     * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled
533     */
534    public void setTitleEnabled(boolean enabled) {
535        if (enabled != mCollapsingTitleEnabled) {
536            mCollapsingTitleEnabled = enabled;
537            updateDummyView();
538            requestLayout();
539        }
540    }
541
542    /**
543     * Returns whether this view is currently displaying its own title.
544     *
545     * @see #setTitleEnabled(boolean)
546     *
547     * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled
548     */
549    public boolean isTitleEnabled() {
550        return mCollapsingTitleEnabled;
551    }
552
553    /**
554     * Set whether the content scrim and/or status bar scrim should be shown or not. Any change
555     * in the vertical scroll may overwrite this value. Any visibility change will be animated if
556     * this view has already been laid out.
557     *
558     * @param shown whether the scrims should be shown
559     *
560     * @see #getStatusBarScrim()
561     * @see #getContentScrim()
562     */
563    public void setScrimsShown(boolean shown) {
564        setScrimsShown(shown, ViewCompat.isLaidOut(this) && !isInEditMode());
565    }
566
567    /**
568     * Set whether the content scrim and/or status bar scrim should be shown or not. Any change
569     * in the vertical scroll may overwrite this value.
570     *
571     * @param shown whether the scrims should be shown
572     * @param animate whether to animate the visibility change
573     *
574     * @see #getStatusBarScrim()
575     * @see #getContentScrim()
576     */
577    public void setScrimsShown(boolean shown, boolean animate) {
578        if (mScrimsAreShown != shown) {
579            if (animate) {
580                animateScrim(shown ? 0xFF : 0x0);
581            } else {
582                setScrimAlpha(shown ? 0xFF : 0x0);
583            }
584            mScrimsAreShown = shown;
585        }
586    }
587
588    private void animateScrim(int targetAlpha) {
589        ensureToolbar();
590        if (mScrimAnimator == null) {
591            mScrimAnimator = ViewUtils.createAnimator();
592            mScrimAnimator.setDuration(mScrimAnimationDuration);
593            mScrimAnimator.setInterpolator(
594                    targetAlpha > mScrimAlpha
595                            ? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR
596                            : AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
597            mScrimAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
598                @Override
599                public void onAnimationUpdate(ValueAnimatorCompat animator) {
600                    setScrimAlpha(animator.getAnimatedIntValue());
601                }
602            });
603        } else if (mScrimAnimator.isRunning()) {
604            mScrimAnimator.cancel();
605        }
606
607        mScrimAnimator.setIntValues(mScrimAlpha, targetAlpha);
608        mScrimAnimator.start();
609    }
610
611    private void setScrimAlpha(int alpha) {
612        if (alpha != mScrimAlpha) {
613            final Drawable contentScrim = mContentScrim;
614            if (contentScrim != null && mToolbar != null) {
615                ViewCompat.postInvalidateOnAnimation(mToolbar);
616            }
617            mScrimAlpha = alpha;
618            ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
619        }
620    }
621
622    /**
623     * Set the drawable to use for the content scrim from resources. Providing null will disable
624     * the scrim functionality.
625     *
626     * @param drawable the drawable to display
627     *
628     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
629     * @see #getContentScrim()
630     */
631    public void setContentScrim(@Nullable Drawable drawable) {
632        if (mContentScrim != drawable) {
633            if (mContentScrim != null) {
634                mContentScrim.setCallback(null);
635            }
636            mContentScrim = drawable != null ? drawable.mutate() : null;
637            if (mContentScrim != null) {
638                mContentScrim.setBounds(0, 0, getWidth(), getHeight());
639                mContentScrim.setCallback(this);
640                mContentScrim.setAlpha(mScrimAlpha);
641            }
642            ViewCompat.postInvalidateOnAnimation(this);
643        }
644    }
645
646    /**
647     * Set the color to use for the content scrim.
648     *
649     * @param color the color to display
650     *
651     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
652     * @see #getContentScrim()
653     */
654    public void setContentScrimColor(@ColorInt int color) {
655        setContentScrim(new ColorDrawable(color));
656    }
657
658    /**
659     * Set the drawable to use for the content scrim from resources.
660     *
661     * @param resId drawable resource id
662     *
663     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
664     * @see #getContentScrim()
665     */
666    public void setContentScrimResource(@DrawableRes int resId) {
667        setContentScrim(ContextCompat.getDrawable(getContext(), resId));
668
669    }
670
671    /**
672     * Returns the drawable which is used for the foreground scrim.
673     *
674     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
675     * @see #setContentScrim(Drawable)
676     */
677    @Nullable
678    public Drawable getContentScrim() {
679        return mContentScrim;
680    }
681
682    /**
683     * Set the drawable to use for the status bar scrim from resources.
684     * Providing null will disable the scrim functionality.
685     *
686     * <p>This scrim is only shown when we have been given a top system inset.</p>
687     *
688     * @param drawable the drawable to display
689     *
690     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
691     * @see #getStatusBarScrim()
692     */
693    public void setStatusBarScrim(@Nullable Drawable drawable) {
694        if (mStatusBarScrim != drawable) {
695            if (mStatusBarScrim != null) {
696                mStatusBarScrim.setCallback(null);
697            }
698            mStatusBarScrim = drawable != null ? drawable.mutate() : null;
699            if (mStatusBarScrim != null) {
700                if (mStatusBarScrim.isStateful()) {
701                    mStatusBarScrim.setState(getDrawableState());
702                }
703                DrawableCompat.setLayoutDirection(mStatusBarScrim,
704                        ViewCompat.getLayoutDirection(this));
705                mStatusBarScrim.setVisible(getVisibility() == VISIBLE, false);
706                mStatusBarScrim.setCallback(this);
707                mStatusBarScrim.setAlpha(mScrimAlpha);
708            }
709            ViewCompat.postInvalidateOnAnimation(this);
710        }
711    }
712
713    @Override
714    protected void drawableStateChanged() {
715        super.drawableStateChanged();
716
717        final int[] state = getDrawableState();
718        boolean changed = false;
719
720        Drawable d = mStatusBarScrim;
721        if (d != null && d.isStateful()) {
722            changed |= d.setState(state);
723        }
724        d = mContentScrim;
725        if (d != null && d.isStateful()) {
726            changed |= d.setState(state);
727        }
728        if (mCollapsingTextHelper != null) {
729            changed |= mCollapsingTextHelper.setState(state);
730        }
731
732        if (changed) {
733            invalidate();
734        }
735    }
736
737    @Override
738    protected boolean verifyDrawable(Drawable who) {
739        return super.verifyDrawable(who) || who == mContentScrim || who == mStatusBarScrim;
740    }
741
742    @Override
743    public void setVisibility(int visibility) {
744        super.setVisibility(visibility);
745
746        final boolean visible = visibility == VISIBLE;
747        if (mStatusBarScrim != null && mStatusBarScrim.isVisible() != visible) {
748            mStatusBarScrim.setVisible(visible, false);
749        }
750        if (mContentScrim != null && mContentScrim.isVisible() != visible) {
751            mContentScrim.setVisible(visible, false);
752        }
753    }
754
755    /**
756     * Set the color to use for the status bar scrim.
757     *
758     * <p>This scrim is only shown when we have been given a top system inset.</p>
759     *
760     * @param color the color to display
761     *
762     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
763     * @see #getStatusBarScrim()
764     */
765    public void setStatusBarScrimColor(@ColorInt int color) {
766        setStatusBarScrim(new ColorDrawable(color));
767    }
768
769    /**
770     * Set the drawable to use for the content scrim from resources.
771     *
772     * @param resId drawable resource id
773     *
774     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
775     * @see #getStatusBarScrim()
776     */
777    public void setStatusBarScrimResource(@DrawableRes int resId) {
778        setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId));
779    }
780
781    /**
782     * Returns the drawable which is used for the status bar scrim.
783     *
784     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
785     * @see #setStatusBarScrim(Drawable)
786     */
787    @Nullable
788    public Drawable getStatusBarScrim() {
789        return mStatusBarScrim;
790    }
791
792    /**
793     * Sets the text color and size for the collapsed title from the specified
794     * TextAppearance resource.
795     *
796     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance
797     */
798    public void setCollapsedTitleTextAppearance(@StyleRes int resId) {
799        mCollapsingTextHelper.setCollapsedTextAppearance(resId);
800    }
801
802    /**
803     * Sets the text color of the collapsed title.
804     *
805     * @param color The new text color in ARGB format
806     */
807    public void setCollapsedTitleTextColor(@ColorInt int color) {
808        setCollapsedTitleTextColor(ColorStateList.valueOf(color));
809    }
810
811    /**
812     * Sets the text colors of the collapsed title.
813     *
814     * @param colors ColorStateList containing the new text colors
815     */
816    public void setCollapsedTitleTextColor(@NonNull ColorStateList colors) {
817        mCollapsingTextHelper.setCollapsedTextColor(colors);
818    }
819
820    /**
821     * Sets the horizontal alignment of the collapsed title and the vertical gravity that will
822     * be used when there is extra space in the collapsed bounds beyond what is required for
823     * the title itself.
824     *
825     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity
826     */
827    public void setCollapsedTitleGravity(int gravity) {
828        mCollapsingTextHelper.setCollapsedTextGravity(gravity);
829    }
830
831    /**
832     * Returns the horizontal and vertical alignment for title when collapsed.
833     *
834     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity
835     */
836    public int getCollapsedTitleGravity() {
837        return mCollapsingTextHelper.getCollapsedTextGravity();
838    }
839
840    /**
841     * Sets the text color and size for the expanded title from the specified
842     * TextAppearance resource.
843     *
844     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance
845     */
846    public void setExpandedTitleTextAppearance(@StyleRes int resId) {
847        mCollapsingTextHelper.setExpandedTextAppearance(resId);
848    }
849
850    /**
851     * Sets the text color of the expanded title.
852     *
853     * @param color The new text color in ARGB format
854     */
855    public void setExpandedTitleColor(@ColorInt int color) {
856        setExpandedTitleTextColor(ColorStateList.valueOf(color));
857    }
858
859    /**
860     * Sets the text colors of the expanded title.
861     *
862     * @param colors ColorStateList containing the new text colors
863     */
864    public void setExpandedTitleTextColor(@NonNull ColorStateList colors) {
865        mCollapsingTextHelper.setExpandedTextColor(colors);
866    }
867
868    /**
869     * Sets the horizontal alignment of the expanded title and the vertical gravity that will
870     * be used when there is extra space in the expanded bounds beyond what is required for
871     * the title itself.
872     *
873     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity
874     */
875    public void setExpandedTitleGravity(int gravity) {
876        mCollapsingTextHelper.setExpandedTextGravity(gravity);
877    }
878
879    /**
880     * Returns the horizontal and vertical alignment for title when expanded.
881     *
882     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity
883     */
884    public int getExpandedTitleGravity() {
885        return mCollapsingTextHelper.getExpandedTextGravity();
886    }
887
888    /**
889     * Set the typeface to use for the collapsed title.
890     *
891     * @param typeface typeface to use, or {@code null} to use the default.
892     */
893    public void setCollapsedTitleTypeface(@Nullable Typeface typeface) {
894        mCollapsingTextHelper.setCollapsedTypeface(typeface);
895    }
896
897    /**
898     * Returns the typeface used for the collapsed title.
899     */
900    @NonNull
901    public Typeface getCollapsedTitleTypeface() {
902        return mCollapsingTextHelper.getCollapsedTypeface();
903    }
904
905    /**
906     * Set the typeface to use for the expanded title.
907     *
908     * @param typeface typeface to use, or {@code null} to use the default.
909     */
910    public void setExpandedTitleTypeface(@Nullable Typeface typeface) {
911        mCollapsingTextHelper.setExpandedTypeface(typeface);
912    }
913
914    /**
915     * Returns the typeface used for the expanded title.
916     */
917    @NonNull
918    public Typeface getExpandedTitleTypeface() {
919        return mCollapsingTextHelper.getExpandedTypeface();
920    }
921
922    /**
923     * Sets the expanded title margins.
924     *
925     * @param start the starting title margin in pixels
926     * @param top the top title margin in pixels
927     * @param end the ending title margin in pixels
928     * @param bottom the bottom title margin in pixels
929     *
930     * @see #getExpandedTitleMarginStart()
931     * @see #getExpandedTitleMarginTop()
932     * @see #getExpandedTitleMarginEnd()
933     * @see #getExpandedTitleMarginBottom()
934     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin
935     */
936    public void setExpandedTitleMargin(int start, int top, int end, int bottom) {
937        mExpandedMarginStart = start;
938        mExpandedMarginTop = top;
939        mExpandedMarginEnd = end;
940        mExpandedMarginBottom = bottom;
941        requestLayout();
942    }
943
944    /**
945     * @return the starting expanded title margin in pixels
946     *
947     * @see #setExpandedTitleMarginStart(int)
948     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
949     */
950    public int getExpandedTitleMarginStart() {
951        return mExpandedMarginStart;
952    }
953
954    /**
955     * Sets the starting expanded title margin in pixels.
956     *
957     * @param margin the starting title margin in pixels
958     * @see #getExpandedTitleMarginStart()
959     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
960     */
961    public void setExpandedTitleMarginStart(int margin) {
962        mExpandedMarginStart = margin;
963        requestLayout();
964    }
965
966    /**
967     * @return the top expanded title margin in pixels
968     * @see #setExpandedTitleMarginTop(int)
969     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
970     */
971    public int getExpandedTitleMarginTop() {
972        return mExpandedMarginTop;
973    }
974
975    /**
976     * Sets the top expanded title margin in pixels.
977     *
978     * @param margin the top title margin in pixels
979     * @see #getExpandedTitleMarginTop()
980     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
981     */
982    public void setExpandedTitleMarginTop(int margin) {
983        mExpandedMarginTop = margin;
984        requestLayout();
985    }
986
987    /**
988     * @return the ending expanded title margin in pixels
989     * @see #setExpandedTitleMarginEnd(int)
990     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
991     */
992    public int getExpandedTitleMarginEnd() {
993        return mExpandedMarginEnd;
994    }
995
996    /**
997     * Sets the ending expanded title margin in pixels.
998     *
999     * @param margin the ending title margin in pixels
1000     * @see #getExpandedTitleMarginEnd()
1001     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
1002     */
1003    public void setExpandedTitleMarginEnd(int margin) {
1004        mExpandedMarginEnd = margin;
1005        requestLayout();
1006    }
1007
1008    /**
1009     * @return the bottom expanded title margin in pixels
1010     * @see #setExpandedTitleMarginBottom(int)
1011     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
1012     */
1013    public int getExpandedTitleMarginBottom() {
1014        return mExpandedMarginBottom;
1015    }
1016
1017    /**
1018     * Sets the bottom expanded title margin in pixels.
1019     *
1020     * @param margin the bottom title margin in pixels
1021     * @see #getExpandedTitleMarginBottom()
1022     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
1023     */
1024    public void setExpandedTitleMarginBottom(int margin) {
1025        mExpandedMarginBottom = margin;
1026        requestLayout();
1027    }
1028
1029    /**
1030     * Set the amount of visible height in pixels used to define when to trigger a scrim
1031     * visibility change.
1032     *
1033     * <p>If the visible height of this view is less than the given value, the scrims will be
1034     * made visible, otherwise they are hidden.</p>
1035     *
1036     * @param height value in pixels used to define when to trigger a scrim visibility change
1037     *
1038     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_scrimVisibleHeightTrigger
1039     */
1040    public void setScrimVisibleHeightTrigger(@IntRange(from = 0) final int height) {
1041        if (mScrimVisibleHeightTrigger != height) {
1042            mScrimVisibleHeightTrigger = height;
1043            // Update the scrim visibility
1044            updateScrimVisibility();
1045        }
1046    }
1047
1048    /**
1049     * Returns the amount of visible height in pixels used to define when to trigger a scrim
1050     * visibility change.
1051     *
1052     * @see #setScrimTriggerOffset(int)
1053     */
1054    public int getScrimVisibleHeightTrigger() {
1055        if (mScrimVisibleHeightTrigger >= 0) {
1056            // If we have one explicitly set, return it
1057            return mScrimVisibleHeightTrigger;
1058        }
1059
1060        // Otherwise we'll use the default computed value
1061        final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
1062
1063        final int minHeight = ViewCompat.getMinimumHeight(this);
1064        if (minHeight > 0) {
1065            // If we have a minHeight set, lets use 2 * minHeight (capped at our height)
1066            return Math.min((minHeight * 2) + insetTop, getHeight());
1067        }
1068
1069        // If we reach here then we don't have a min height set. Instead we'll take a
1070        // guess at 1/3 of our height being visible
1071        return getHeight() / 3;
1072    }
1073
1074    /**
1075     * Set the duration used for scrim visibility animations.
1076     *
1077     * @param duration the duration to use in milliseconds
1078     *
1079     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_scrimAnimationDuration
1080     */
1081    public void setScrimAnimationDuration(@IntRange(from = 0) final long duration) {
1082        mScrimAnimationDuration = duration;
1083    }
1084
1085    /**
1086     * Returns the duration in milliseconds used for scrim visibility animations.
1087     */
1088    public long getScrimAnimationDuration() {
1089        return mScrimAnimationDuration;
1090    }
1091
1092    @Override
1093    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1094        return p instanceof LayoutParams;
1095    }
1096
1097    @Override
1098    protected LayoutParams generateDefaultLayoutParams() {
1099        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
1100    }
1101
1102    @Override
1103    public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
1104        return new LayoutParams(getContext(), attrs);
1105    }
1106
1107    @Override
1108    protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1109        return new LayoutParams(p);
1110    }
1111
1112    public static class LayoutParams extends FrameLayout.LayoutParams {
1113
1114        private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f;
1115
1116        /** @hide */
1117        @IntDef({
1118                COLLAPSE_MODE_OFF,
1119                COLLAPSE_MODE_PIN,
1120                COLLAPSE_MODE_PARALLAX
1121        })
1122        @Retention(RetentionPolicy.SOURCE)
1123        @interface CollapseMode {}
1124
1125        /**
1126         * The view will act as normal with no collapsing behavior.
1127         */
1128        public static final int COLLAPSE_MODE_OFF = 0;
1129
1130        /**
1131         * The view will pin in place until it reaches the bottom of the
1132         * {@link CollapsingToolbarLayout}.
1133         */
1134        public static final int COLLAPSE_MODE_PIN = 1;
1135
1136        /**
1137         * The view will scroll in a parallax fashion. See {@link #setParallaxMultiplier(float)}
1138         * to change the multiplier used.
1139         */
1140        public static final int COLLAPSE_MODE_PARALLAX = 2;
1141
1142        int mCollapseMode = COLLAPSE_MODE_OFF;
1143        float mParallaxMult = DEFAULT_PARALLAX_MULTIPLIER;
1144
1145        public LayoutParams(Context c, AttributeSet attrs) {
1146            super(c, attrs);
1147
1148            TypedArray a = c.obtainStyledAttributes(attrs,
1149                    R.styleable.CollapsingToolbarLayout_Layout);
1150            mCollapseMode = a.getInt(
1151                    R.styleable.CollapsingToolbarLayout_Layout_layout_collapseMode,
1152                    COLLAPSE_MODE_OFF);
1153            setParallaxMultiplier(a.getFloat(
1154                    R.styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier,
1155                    DEFAULT_PARALLAX_MULTIPLIER));
1156            a.recycle();
1157        }
1158
1159        public LayoutParams(int width, int height) {
1160            super(width, height);
1161        }
1162
1163        public LayoutParams(int width, int height, int gravity) {
1164            super(width, height, gravity);
1165        }
1166
1167        public LayoutParams(ViewGroup.LayoutParams p) {
1168            super(p);
1169        }
1170
1171        public LayoutParams(MarginLayoutParams source) {
1172            super(source);
1173        }
1174
1175        public LayoutParams(FrameLayout.LayoutParams source) {
1176            super(source);
1177        }
1178
1179        /**
1180         * Set the collapse mode.
1181         *
1182         * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN}
1183         *                     or {@link #COLLAPSE_MODE_PARALLAX}.
1184         */
1185        public void setCollapseMode(@CollapseMode int collapseMode) {
1186            mCollapseMode = collapseMode;
1187        }
1188
1189        /**
1190         * Returns the requested collapse mode.
1191         *
1192         * @return the current mode. One of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN}
1193         * or {@link #COLLAPSE_MODE_PARALLAX}.
1194         */
1195        @CollapseMode
1196        public int getCollapseMode() {
1197            return mCollapseMode;
1198        }
1199
1200        /**
1201         * Set the parallax scroll multiplier used in conjunction with
1202         * {@link #COLLAPSE_MODE_PARALLAX}. A value of {@code 0.0} indicates no movement at all,
1203         * {@code 1.0f} indicates normal scroll movement.
1204         *
1205         * @param multiplier the multiplier.
1206         *
1207         * @see #getParallaxMultiplier()
1208         */
1209        public void setParallaxMultiplier(float multiplier) {
1210            mParallaxMult = multiplier;
1211        }
1212
1213        /**
1214         * Returns the parallax scroll multiplier used in conjunction with
1215         * {@link #COLLAPSE_MODE_PARALLAX}.
1216         *
1217         * @see #setParallaxMultiplier(float)
1218         */
1219        public float getParallaxMultiplier() {
1220            return mParallaxMult;
1221        }
1222    }
1223
1224    /**
1225     * Show or hide the scrims if needed
1226     */
1227    final void updateScrimVisibility() {
1228        if (mContentScrim != null || mStatusBarScrim != null) {
1229            setScrimsShown(getHeight() + mCurrentOffset < getScrimVisibleHeightTrigger());
1230        }
1231    }
1232
1233    final int getMaxOffsetForPinChild(View child) {
1234        final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
1235        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1236        return getHeight()
1237                + (mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0)
1238                - offsetHelper.getLayoutTop()
1239                - child.getHeight()
1240                - lp.bottomMargin;
1241    }
1242
1243    private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener {
1244        @Override
1245        public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
1246            mCurrentOffset = verticalOffset;
1247
1248            final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
1249
1250            for (int i = 0, z = getChildCount(); i < z; i++) {
1251                final View child = getChildAt(i);
1252                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1253                final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
1254
1255                switch (lp.mCollapseMode) {
1256                    case LayoutParams.COLLAPSE_MODE_PIN:
1257                        offsetHelper.setTopAndBottomOffset(
1258                                constrain(-verticalOffset, 0, getMaxOffsetForPinChild(child)));
1259                        break;
1260                    case LayoutParams.COLLAPSE_MODE_PARALLAX:
1261                        offsetHelper.setTopAndBottomOffset(
1262                                Math.round(-verticalOffset * lp.mParallaxMult));
1263                        break;
1264                }
1265            }
1266
1267            // Show or hide the scrims if needed
1268            updateScrimVisibility();
1269
1270            if (mStatusBarScrim != null && insetTop > 0) {
1271                ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
1272            }
1273
1274            // Update the collapsing text's fraction
1275            final int expandRange = getHeight() - ViewCompat.getMinimumHeight(
1276                    CollapsingToolbarLayout.this) - insetTop;
1277            mCollapsingTextHelper.setExpansionFraction(
1278                    Math.abs(verticalOffset) / (float) expandRange);
1279        }
1280    }
1281}
1282