CollapsingToolbarLayout.java revision cd7313d4cdb81d0abaf9dc7aefc6ebadea899ab5
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.TypedArray;
21import android.graphics.Canvas;
22import android.graphics.Rect;
23import android.graphics.Typeface;
24import android.graphics.drawable.ColorDrawable;
25import android.graphics.drawable.Drawable;
26import android.os.Build;
27import android.support.annotation.ColorInt;
28import android.support.annotation.DrawableRes;
29import android.support.annotation.IntDef;
30import android.support.annotation.NonNull;
31import android.support.annotation.Nullable;
32import android.support.annotation.StyleRes;
33import android.support.design.R;
34import android.support.v4.content.ContextCompat;
35import android.support.v4.view.GravityCompat;
36import android.support.v4.view.ViewCompat;
37import android.support.v4.view.WindowInsetsCompat;
38import android.support.v7.widget.Toolbar;
39import android.text.TextUtils;
40import android.util.AttributeSet;
41import android.view.Gravity;
42import android.view.View;
43import android.view.ViewGroup;
44import android.view.ViewParent;
45import android.widget.FrameLayout;
46
47import java.lang.annotation.Retention;
48import java.lang.annotation.RetentionPolicy;
49
50/**
51 * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar.
52 * It is designed to be used as a direct child of a {@link AppBarLayout}.
53 * CollapsingToolbarLayout contains the following features:
54 *
55 * <h3>Collapsing title</h3>
56 * A title which is larger when the layout is fully visible but collapses and becomes smaller as
57 * the layout is scrolled off screen. You can set the title to display via
58 * {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the
59 * {@code collapsedTextAppearance} and {@code expandedTextAppearance} attributes.
60 *
61 * <h3>Content scrim</h3>
62 * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold.
63 * You can change this via {@link #setContentScrim(Drawable)}.
64 *
65 * <h3>Status bar scrim</h3>
66 * A scrim which is show or hidden behind the status bar when the scroll position has hit a certain
67 * threshold. You can change this via {@link #setStatusBarScrim(Drawable)}. This only works
68 * on {@link Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system windows.
69 *
70 * <h3>Parallax scrolling children</h3>
71 * Child views can opt to be scrolled within this layout in a parallax fashion.
72 * See {@link LayoutParams#COLLAPSE_MODE_PARALLAX} and
73 * {@link LayoutParams#setParallaxMultiplier(float)}.
74 *
75 * <h3>Pinned position children</h3>
76 * Child views can opt to be pinned in space globally. This is useful when implementing a
77 * collapsing as it allows the {@link Toolbar} to be fixed in place even though this layout is
78 * moving. See {@link LayoutParams#COLLAPSE_MODE_PIN}.
79 *
80 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance
81 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance
82 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_contentScrim
83 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin
84 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
85 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
86 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
87 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_statusBarScrim
88 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_toolbarId
89 */
90public class CollapsingToolbarLayout extends FrameLayout {
91
92    private static final int SCRIM_ANIMATION_DURATION = 600;
93
94    private boolean mRefreshToolbar = true;
95    private int mToolbarId;
96    private Toolbar mToolbar;
97    private View mDummyView;
98
99    private int mExpandedMarginStart;
100    private int mExpandedMarginTop;
101    private int mExpandedMarginEnd;
102    private int mExpandedMarginBottom;
103
104    private final Rect mTmpRect = new Rect();
105    private final CollapsingTextHelper mCollapsingTextHelper;
106    private boolean mCollapsingTitleEnabled;
107    private boolean mDrawCollapsingTitle;
108
109    private Drawable mContentScrim;
110    private Drawable mStatusBarScrim;
111    private int mScrimAlpha;
112    private boolean mScrimsAreShown;
113    private ValueAnimatorCompat mScrimAnimator;
114
115    private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener;
116
117    private int mCurrentOffset;
118
119    private WindowInsetsCompat mLastInsets;
120
121    public CollapsingToolbarLayout(Context context) {
122        this(context, null);
123    }
124
125    public CollapsingToolbarLayout(Context context, AttributeSet attrs) {
126        this(context, attrs, 0);
127    }
128
129    public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
130        super(context, attrs, defStyleAttr);
131
132        ThemeUtils.checkAppCompatTheme(context);
133
134        mCollapsingTextHelper = new CollapsingTextHelper(this);
135        mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
136
137        TypedArray a = context.obtainStyledAttributes(attrs,
138                R.styleable.CollapsingToolbarLayout, defStyleAttr,
139                R.style.Widget_Design_CollapsingToolbar);
140
141        mCollapsingTextHelper.setExpandedTextGravity(
142                a.getInt(R.styleable.CollapsingToolbarLayout_expandedTitleGravity,
143                        GravityCompat.START | Gravity.BOTTOM));
144        mCollapsingTextHelper.setCollapsedTextGravity(
145                a.getInt(R.styleable.CollapsingToolbarLayout_collapsedTitleGravity,
146                        GravityCompat.START | Gravity.CENTER_VERTICAL));
147
148        mExpandedMarginStart = mExpandedMarginTop = mExpandedMarginEnd = mExpandedMarginBottom =
149                a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMargin, 0);
150
151        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart)) {
152            mExpandedMarginStart = a.getDimensionPixelSize(
153                    R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart, 0);
154        }
155        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd)) {
156            mExpandedMarginEnd = a.getDimensionPixelSize(
157                    R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd, 0);
158        }
159        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop)) {
160            mExpandedMarginTop = a.getDimensionPixelSize(
161                    R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop, 0);
162        }
163        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom)) {
164            mExpandedMarginBottom = a.getDimensionPixelSize(
165                    R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom, 0);
166        }
167
168        mCollapsingTitleEnabled = a.getBoolean(
169                R.styleable.CollapsingToolbarLayout_titleEnabled, true);
170        setTitle(a.getText(R.styleable.CollapsingToolbarLayout_title));
171
172        // First load the default text appearances
173        mCollapsingTextHelper.setExpandedTextAppearance(
174                R.style.TextAppearance_Design_CollapsingToolbar_Expanded);
175        mCollapsingTextHelper.setCollapsedTextAppearance(
176                R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
177
178        // Now overlay any custom text appearances
179        if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) {
180            mCollapsingTextHelper.setExpandedTextAppearance(
181                    a.getResourceId(
182                            R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance, 0));
183        }
184        if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance)) {
185            mCollapsingTextHelper.setCollapsedTextAppearance(
186                    a.getResourceId(
187                            R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 0));
188        }
189
190        setContentScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim));
191        setStatusBarScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim));
192
193        mToolbarId = a.getResourceId(R.styleable.CollapsingToolbarLayout_toolbarId, -1);
194
195        a.recycle();
196
197        setWillNotDraw(false);
198
199        ViewCompat.setOnApplyWindowInsetsListener(this,
200                new android.support.v4.view.OnApplyWindowInsetsListener() {
201                    @Override
202                    public WindowInsetsCompat onApplyWindowInsets(View v,
203                            WindowInsetsCompat insets) {
204                        mLastInsets = insets;
205                        requestLayout();
206                        return insets.consumeSystemWindowInsets();
207                    }
208                });
209    }
210
211    @Override
212    protected void onAttachedToWindow() {
213        super.onAttachedToWindow();
214
215        // Add an OnOffsetChangedListener if possible
216        final ViewParent parent = getParent();
217        if (parent instanceof AppBarLayout) {
218            if (mOnOffsetChangedListener == null) {
219                mOnOffsetChangedListener = new OffsetUpdateListener();
220            }
221            ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
222        }
223    }
224
225    @Override
226    protected void onDetachedFromWindow() {
227        // Remove our OnOffsetChangedListener if possible and it exists
228        final ViewParent parent = getParent();
229        if (mOnOffsetChangedListener != null && parent instanceof AppBarLayout) {
230            ((AppBarLayout) parent).removeOnOffsetChangedListener(mOnOffsetChangedListener);
231        }
232
233        super.onDetachedFromWindow();
234    }
235
236    @Override
237    public void draw(Canvas canvas) {
238        super.draw(canvas);
239
240        // If we don't have a toolbar, the scrim will be not be drawn in drawChild() below.
241        // Instead, we draw it here, before our collapsing text.
242        ensureToolbar();
243        if (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) {
244            mContentScrim.mutate().setAlpha(mScrimAlpha);
245            mContentScrim.draw(canvas);
246        }
247
248        // Let the collapsing text helper draw it's text
249        if (mCollapsingTitleEnabled && mDrawCollapsingTitle) {
250            mCollapsingTextHelper.draw(canvas);
251        }
252
253        // Now draw the status bar scrim
254        if (mStatusBarScrim != null && mScrimAlpha > 0) {
255            final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
256            if (topInset > 0) {
257                mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(),
258                        topInset - mCurrentOffset);
259                mStatusBarScrim.mutate().setAlpha(mScrimAlpha);
260                mStatusBarScrim.draw(canvas);
261            }
262        }
263    }
264
265    @Override
266    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
267        // This is a little weird. Our scrim needs to be behind the Toolbar (if it is present),
268        // but in front of any other children which are behind it. To do this we intercept the
269        // drawChild() call, and draw our scrim first when drawing the toolbar
270        ensureToolbar();
271        if (child == mToolbar && mContentScrim != null && mScrimAlpha > 0) {
272            mContentScrim.mutate().setAlpha(mScrimAlpha);
273            mContentScrim.draw(canvas);
274        }
275
276        // Carry on drawing the child...
277        return super.drawChild(canvas, child, drawingTime);
278    }
279
280    @Override
281    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
282        super.onSizeChanged(w, h, oldw, oldh);
283        if (mContentScrim != null) {
284            mContentScrim.setBounds(0, 0, w, h);
285        }
286    }
287
288    private void ensureToolbar() {
289        if (!mRefreshToolbar) {
290            return;
291        }
292
293        Toolbar fallback = null, selected = null;
294
295        for (int i = 0, count = getChildCount(); i < count; i++) {
296            final View child = getChildAt(i);
297            if (child instanceof Toolbar) {
298                if (mToolbarId != -1) {
299                    // There's a toolbar id set so try and find it...
300                    if (mToolbarId == child.getId()) {
301                        // We found the primary Toolbar, use it
302                        selected = (Toolbar) child;
303                        break;
304                    }
305                    if (fallback == null) {
306                        // We'll record the first Toolbar as our fallback
307                        fallback = (Toolbar) child;
308                    }
309                } else {
310                    // We don't have a id to check for so just use the first we come across
311                    selected = (Toolbar) child;
312                    break;
313                }
314            }
315        }
316
317        if (selected == null) {
318            // If we didn't find a primary Toolbar, use the fallback
319            selected = fallback;
320        }
321
322        mToolbar = selected;
323        updateDummyView();
324        mRefreshToolbar = false;
325    }
326
327    private void updateDummyView() {
328        if (!mCollapsingTitleEnabled && mDummyView != null) {
329            // If we have a dummy view and we have our title disabled, remove it from its parent
330            final ViewParent parent = mDummyView.getParent();
331            if (parent instanceof ViewGroup) {
332                ((ViewGroup) parent).removeView(mDummyView);
333            }
334        }
335        if (mCollapsingTitleEnabled && mToolbar != null) {
336            if (mDummyView == null) {
337                mDummyView = new View(getContext());
338            }
339            if (mDummyView.getParent() == null) {
340                mToolbar.addView(mDummyView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
341            }
342        }
343    }
344
345    @Override
346    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
347        ensureToolbar();
348        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
349    }
350
351    @Override
352    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
353        super.onLayout(changed, left, top, right, bottom);
354
355        // Update the collapsed bounds by getting it's transformed bounds. This needs to be done
356        // before the children are offset below
357        if (mCollapsingTitleEnabled && mDummyView != null) {
358            // We only draw the title if the dummy view is being displayed (Toolbar removes
359            // views if there is no space)
360            mDrawCollapsingTitle = mDummyView.isShown();
361
362            if (mDrawCollapsingTitle) {
363                ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
364                mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left, bottom - mTmpRect.height(),
365                        mTmpRect.right, bottom);
366
367                final boolean isRtl = ViewCompat.getLayoutDirection(this)
368                        == ViewCompat.LAYOUT_DIRECTION_RTL;
369                // Update the expanded bounds
370                mCollapsingTextHelper.setExpandedBounds(
371                        isRtl ? mExpandedMarginEnd : mExpandedMarginStart,
372                        mTmpRect.bottom + mExpandedMarginTop,
373                        right - left - (isRtl ? mExpandedMarginStart : mExpandedMarginEnd),
374                        bottom - top - mExpandedMarginBottom);
375                // Now recalculate using the new bounds
376                mCollapsingTextHelper.recalculate();
377            }
378        }
379
380        // Update our child view offset helpers
381        for (int i = 0, z = getChildCount(); i < z; i++) {
382            final View child = getChildAt(i);
383
384            if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) {
385                final int insetTop = mLastInsets.getSystemWindowInsetTop();
386                if (child.getTop() < insetTop) {
387                    // If the child isn't set to fit system windows but is drawing within the inset
388                    // offset it down
389                    child.offsetTopAndBottom(insetTop);
390                }
391            }
392
393            getViewOffsetHelper(child).onViewLayout();
394        }
395
396        // Finally, set our minimum height to enable proper AppBarLayout collapsing
397        if (mToolbar != null) {
398            if (mCollapsingTitleEnabled && TextUtils.isEmpty(mCollapsingTextHelper.getText())) {
399                // If we do not currently have a title, try and grab it from the Toolbar
400                mCollapsingTextHelper.setText(mToolbar.getTitle());
401            }
402            setMinimumHeight(mToolbar.getHeight());
403        }
404    }
405
406    private static ViewOffsetHelper getViewOffsetHelper(View view) {
407        ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper);
408        if (offsetHelper == null) {
409            offsetHelper = new ViewOffsetHelper(view);
410            view.setTag(R.id.view_offset_helper, offsetHelper);
411        }
412        return offsetHelper;
413    }
414
415    /**
416     * Sets the title to be displayed by this view, if enabled.
417     *
418     * @see #setTitleEnabled(boolean)
419     * @see #getTitle()
420     *
421     * @attr ref R.styleable#CollapsingToolbarLayout_title
422     */
423    public void setTitle(@Nullable CharSequence title) {
424        mCollapsingTextHelper.setText(title);
425    }
426
427    /**
428     * Returns the title currently being displayed by this view. If the title is not enabled, then
429     * this will return {@code null}.
430     *
431     * @attr ref R.styleable#CollapsingToolbarLayout_title
432     */
433    @Nullable
434    public CharSequence getTitle() {
435        return mCollapsingTitleEnabled ? mCollapsingTextHelper.getText() : null;
436    }
437
438    /**
439     * Sets whether this view should display its own title.
440     *
441     * <p>The title displayed by this view will shrink and grow based on the scroll offset.</p>
442     *
443     * @see #setTitle(CharSequence)
444     * @see #isTitleEnabled()
445     *
446     * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled
447     */
448    public void setTitleEnabled(boolean enabled) {
449        if (enabled != mCollapsingTitleEnabled) {
450            mCollapsingTitleEnabled = enabled;
451            updateDummyView();
452            requestLayout();
453        }
454    }
455
456    /**
457     * Returns whether this view is currently displaying its own title.
458     *
459     * @see #setTitleEnabled(boolean)
460     *
461     * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled
462     */
463    public boolean isTitleEnabled() {
464        return mCollapsingTitleEnabled;
465    }
466
467    /**
468     * Set whether the content scrim and/or status bar scrim should be shown or not. Any change
469     * in the vertical scroll may overwrite this value. Any visibility change will be animated if
470     * this view has already been laid out.
471     *
472     * @param shown whether the scrims should be shown
473     *
474     * @see #getStatusBarScrim()
475     * @see #getContentScrim()
476     */
477    public void setScrimsShown(boolean shown) {
478        setScrimsShown(shown, ViewCompat.isLaidOut(this) && !isInEditMode());
479    }
480
481    /**
482     * Set whether the content scrim and/or status bar scrim should be shown or not. Any change
483     * in the vertical scroll may overwrite this value.
484     *
485     * @param shown whether the scrims should be shown
486     * @param animate whether to animate the visibility change
487     *
488     * @see #getStatusBarScrim()
489     * @see #getContentScrim()
490     */
491    public void setScrimsShown(boolean shown, boolean animate) {
492        if (mScrimsAreShown != shown) {
493            if (animate) {
494                animateScrim(shown ? 0xFF : 0x0);
495            } else {
496                setScrimAlpha(shown ? 0xFF : 0x0);
497            }
498            mScrimsAreShown = shown;
499        }
500    }
501
502    private void animateScrim(int targetAlpha) {
503        ensureToolbar();
504        if (mScrimAnimator == null) {
505            mScrimAnimator = ViewUtils.createAnimator();
506            mScrimAnimator.setDuration(SCRIM_ANIMATION_DURATION);
507            mScrimAnimator.setInterpolator(
508                    targetAlpha > mScrimAlpha
509                            ? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR
510                            : AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
511            mScrimAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
512                @Override
513                public void onAnimationUpdate(ValueAnimatorCompat animator) {
514                    setScrimAlpha(animator.getAnimatedIntValue());
515                }
516            });
517        } else if (mScrimAnimator.isRunning()) {
518            mScrimAnimator.cancel();
519        }
520
521        mScrimAnimator.setIntValues(mScrimAlpha, targetAlpha);
522        mScrimAnimator.start();
523    }
524
525    private void setScrimAlpha(int alpha) {
526        if (alpha != mScrimAlpha) {
527            final Drawable contentScrim = mContentScrim;
528            if (contentScrim != null && mToolbar != null) {
529                ViewCompat.postInvalidateOnAnimation(mToolbar);
530            }
531            mScrimAlpha = alpha;
532            ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
533        }
534    }
535
536    /**
537     * Set the drawable to use for the content scrim from resources. Providing null will disable
538     * the scrim functionality.
539     *
540     * @param drawable the drawable to display
541     *
542     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
543     * @see #getContentScrim()
544     */
545    public void setContentScrim(@Nullable Drawable drawable) {
546        if (mContentScrim != drawable) {
547            if (mContentScrim != null) {
548                mContentScrim.setCallback(null);
549            }
550            if (drawable != null) {
551                mContentScrim = drawable.mutate();
552                drawable.setBounds(0, 0, getWidth(), getHeight());
553                drawable.setCallback(this);
554                drawable.setAlpha(mScrimAlpha);
555            } else {
556                mContentScrim = null;
557            }
558            ViewCompat.postInvalidateOnAnimation(this);
559        }
560    }
561
562    /**
563     * Set the color to use for the content scrim.
564     *
565     * @param color the color to display
566     *
567     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
568     * @see #getContentScrim()
569     */
570    public void setContentScrimColor(@ColorInt int color) {
571        setContentScrim(new ColorDrawable(color));
572    }
573
574    /**
575     * Set the drawable to use for the content scrim from resources.
576     *
577     * @param resId drawable resource id
578     *
579     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
580     * @see #getContentScrim()
581     */
582    public void setContentScrimResource(@DrawableRes int resId) {
583        setContentScrim(ContextCompat.getDrawable(getContext(), resId));
584
585    }
586
587    /**
588     * Returns the drawable which is used for the foreground scrim.
589     *
590     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
591     * @see #setContentScrim(Drawable)
592     */
593    public Drawable getContentScrim() {
594        return mContentScrim;
595    }
596
597    /**
598     * Set the drawable to use for the status bar scrim from resources.
599     * Providing null will disable the scrim functionality.
600     *
601     * <p>This scrim is only shown when we have been given a top system inset.</p>
602     *
603     * @param drawable the drawable to display
604     *
605     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
606     * @see #getStatusBarScrim()
607     */
608    public void setStatusBarScrim(@Nullable Drawable drawable) {
609        if (mStatusBarScrim != drawable) {
610            if (mStatusBarScrim != null) {
611                mStatusBarScrim.setCallback(null);
612            }
613
614            mStatusBarScrim = drawable;
615            drawable.setCallback(this);
616            drawable.mutate().setAlpha(mScrimAlpha);
617            ViewCompat.postInvalidateOnAnimation(this);
618        }
619    }
620
621    /**
622     * Set the color to use for the status bar scrim.
623     *
624     * <p>This scrim is only shown when we have been given a top system inset.</p>
625     *
626     * @param color the color to display
627     *
628     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
629     * @see #getStatusBarScrim()
630     */
631    public void setStatusBarScrimColor(@ColorInt int color) {
632        setStatusBarScrim(new ColorDrawable(color));
633    }
634
635    /**
636     * Set the drawable to use for the content scrim from resources.
637     *
638     * @param resId drawable resource id
639     *
640     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
641     * @see #getStatusBarScrim()
642     */
643    public void setStatusBarScrimResource(@DrawableRes int resId) {
644        setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId));
645    }
646
647    /**
648     * Returns the drawable which is used for the status bar scrim.
649     *
650     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
651     * @see #setStatusBarScrim(Drawable)
652     */
653    public Drawable getStatusBarScrim() {
654        return mStatusBarScrim;
655    }
656
657    /**
658     * Sets the text color and size for the collapsed title from the specified
659     * TextAppearance resource.
660     *
661     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance
662     */
663    public void setCollapsedTitleTextAppearance(@StyleRes int resId) {
664        mCollapsingTextHelper.setCollapsedTextAppearance(resId);
665    }
666
667    /**
668     * Sets the text color of the collapsed title.
669     *
670     * @param color The new text color in ARGB format
671     */
672    public void setCollapsedTitleTextColor(@ColorInt int color) {
673        mCollapsingTextHelper.setCollapsedTextColor(color);
674    }
675
676    /**
677     * Sets the horizontal alignment of the collapsed title and the vertical gravity that will
678     * be used when there is extra space in the collapsed bounds beyond what is required for
679     * the title itself.
680     *
681     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity
682     */
683    public void setCollapsedTitleGravity(int gravity) {
684        mCollapsingTextHelper.setCollapsedTextGravity(gravity);
685    }
686
687    /**
688     * Returns the horizontal and vertical alignment for title when collapsed.
689     *
690     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity
691     */
692    public int getCollapsedTitleGravity() {
693        return mCollapsingTextHelper.getCollapsedTextGravity();
694    }
695
696    /**
697     * Sets the text color and size for the expanded title from the specified
698     * TextAppearance resource.
699     *
700     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance
701     */
702    public void setExpandedTitleTextAppearance(@StyleRes int resId) {
703        mCollapsingTextHelper.setExpandedTextAppearance(resId);
704    }
705
706    /**
707     * Sets the text color of the expanded title.
708     *
709     * @param color The new text color in ARGB format
710     */
711    public void setExpandedTitleColor(@ColorInt int color) {
712        mCollapsingTextHelper.setExpandedTextColor(color);
713    }
714
715    /**
716     * Sets the horizontal alignment of the expanded title and the vertical gravity that will
717     * be used when there is extra space in the expanded bounds beyond what is required for
718     * the title itself.
719     *
720     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity
721     */
722    public void setExpandedTitleGravity(int gravity) {
723        mCollapsingTextHelper.setExpandedTextGravity(gravity);
724    }
725
726    /**
727     * Returns the horizontal and vertical alignment for title when expanded.
728     *
729     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity
730     */
731    public int getExpandedTitleGravity() {
732        return mCollapsingTextHelper.getExpandedTextGravity();
733    }
734
735    /**
736     * Set the typeface to use for the collapsed title.
737     *
738     * @param typeface typeface to use, or {@code null} to use the default.
739     */
740    public void setCollapsedTitleTypeface(@Nullable Typeface typeface) {
741        mCollapsingTextHelper.setCollapsedTypeface(typeface);
742    }
743
744    /**
745     * Returns the typeface used for the collapsed title.
746     */
747    @NonNull
748    public Typeface getCollapsedTitleTypeface() {
749        return mCollapsingTextHelper.getCollapsedTypeface();
750    }
751
752    /**
753     * Set the typeface to use for the expanded title.
754     *
755     * @param typeface typeface to use, or {@code null} to use the default.
756     */
757    public void setExpandedTitleTypeface(@Nullable Typeface typeface) {
758        mCollapsingTextHelper.setExpandedTypeface(typeface);
759    }
760
761    /**
762     * Returns the typeface used for the expanded title.
763     */
764    @NonNull
765    public Typeface getExpandedTitleTypeface() {
766        return mCollapsingTextHelper.getExpandedTypeface();
767    }
768
769    /**
770     * Sets the expanded title margins.
771     *
772     * @param start the starting title margin in pixels
773     * @param top the top title margin in pixels
774     * @param end the ending title margin in pixels
775     * @param bottom the bottom title margin in pixels
776     *
777     * @see #getExpandedTitleMarginStart()
778     * @see #getExpandedTitleMarginTop()
779     * @see #getExpandedTitleMarginEnd()
780     * @see #getExpandedTitleMarginBottom()
781     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin
782     */
783    public void setExpandedTitleMargin(int start, int top, int end, int bottom) {
784        mExpandedMarginStart = start;
785        mExpandedMarginTop = top;
786        mExpandedMarginEnd = end;
787        mExpandedMarginBottom = bottom;
788        requestLayout();
789    }
790
791    /**
792     * @return the starting expanded title margin in pixels
793     *
794     * @see #setExpandedTitleMarginStart(int)
795     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
796     */
797    public int getExpandedTitleMarginStart() {
798        return mExpandedMarginStart;
799    }
800
801    /**
802     * Sets the starting expanded title margin in pixels.
803     *
804     * @param margin the starting title margin in pixels
805     * @see #getExpandedTitleMarginStart()
806     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
807     */
808    public void setExpandedTitleMarginStart(int margin) {
809        mExpandedMarginStart = margin;
810        requestLayout();
811    }
812
813    /**
814     * @return the top expanded title margin in pixels
815     * @see #setExpandedTitleMarginTop(int)
816     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
817     */
818    public int getExpandedTitleMarginTop() {
819        return mExpandedMarginTop;
820    }
821
822    /**
823     * Sets the top expanded title margin in pixels.
824     *
825     * @param margin the top title margin in pixels
826     * @see #getExpandedTitleMarginTop()
827     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
828     */
829    public void setExpandedTitleMarginTop(int margin) {
830        mExpandedMarginTop = margin;
831        requestLayout();
832    }
833
834    /**
835     * @return the ending expanded title margin in pixels
836     * @see #setExpandedTitleMarginEnd(int)
837     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
838     */
839    public int getExpandedTitleMarginEnd() {
840        return mExpandedMarginEnd;
841    }
842
843    /**
844     * Sets the ending expanded title margin in pixels.
845     *
846     * @param margin the ending title margin in pixels
847     * @see #getExpandedTitleMarginEnd()
848     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
849     */
850    public void setExpandedTitleMarginEnd(int margin) {
851        mExpandedMarginEnd = margin;
852        requestLayout();
853    }
854
855    /**
856     * @return the bottom expanded title margin in pixels
857     * @see #setExpandedTitleMarginBottom(int)
858     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
859     */
860    public int getExpandedTitleMarginBottom() {
861        return mExpandedMarginBottom;
862    }
863
864    /**
865     * Sets the bottom expanded title margin in pixels.
866     *
867     * @param margin the bottom title margin in pixels
868     * @see #getExpandedTitleMarginBottom()
869     * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
870     */
871    public void setExpandedTitleMarginBottom(int margin) {
872        mExpandedMarginBottom = margin;
873        requestLayout();
874    }
875
876    /**
877     * The additional offset used to define when to trigger the scrim visibility change.
878     */
879    final int getScrimTriggerOffset() {
880        return 2 * ViewCompat.getMinimumHeight(this);
881    }
882
883    @Override
884    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
885        return p instanceof LayoutParams;
886    }
887
888    @Override
889    protected LayoutParams generateDefaultLayoutParams() {
890        return new LayoutParams(super.generateDefaultLayoutParams());
891    }
892
893    @Override
894    public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
895        return new LayoutParams(getContext(), attrs);
896    }
897
898    @Override
899    protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
900        return new LayoutParams(p);
901    }
902
903    public static class LayoutParams extends FrameLayout.LayoutParams {
904
905        private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f;
906
907        /** @hide */
908        @IntDef({
909                COLLAPSE_MODE_OFF,
910                COLLAPSE_MODE_PIN,
911                COLLAPSE_MODE_PARALLAX
912        })
913        @Retention(RetentionPolicy.SOURCE)
914        @interface CollapseMode {}
915
916        /**
917         * The view will act as normal with no collapsing behavior.
918         */
919        public static final int COLLAPSE_MODE_OFF = 0;
920
921        /**
922         * The view will pin in place until it reaches the bottom of the
923         * {@link CollapsingToolbarLayout}.
924         */
925        public static final int COLLAPSE_MODE_PIN = 1;
926
927        /**
928         * The view will scroll in a parallax fashion. See {@link #setParallaxMultiplier(float)}
929         * to change the multiplier used.
930         */
931        public static final int COLLAPSE_MODE_PARALLAX = 2;
932
933        int mCollapseMode = COLLAPSE_MODE_OFF;
934        float mParallaxMult = DEFAULT_PARALLAX_MULTIPLIER;
935
936        public LayoutParams(Context c, AttributeSet attrs) {
937            super(c, attrs);
938
939            TypedArray a = c.obtainStyledAttributes(attrs,
940                    R.styleable.CollapsingAppBarLayout_LayoutParams);
941            mCollapseMode = a.getInt(
942                    R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseMode,
943                    COLLAPSE_MODE_OFF);
944            setParallaxMultiplier(a.getFloat(
945                    R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseParallaxMultiplier,
946                    DEFAULT_PARALLAX_MULTIPLIER));
947            a.recycle();
948        }
949
950        public LayoutParams(int width, int height) {
951            super(width, height);
952        }
953
954        public LayoutParams(int width, int height, int gravity) {
955            super(width, height, gravity);
956        }
957
958        public LayoutParams(ViewGroup.LayoutParams p) {
959            super(p);
960        }
961
962        public LayoutParams(MarginLayoutParams source) {
963            super(source);
964        }
965
966        public LayoutParams(FrameLayout.LayoutParams source) {
967            super(source);
968        }
969
970        /**
971         * Set the collapse mode.
972         *
973         * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN}
974         *                     or {@link #COLLAPSE_MODE_PARALLAX}.
975         */
976        public void setCollapseMode(@CollapseMode int collapseMode) {
977            mCollapseMode = collapseMode;
978        }
979
980        /**
981         * Returns the requested collapse mode.
982         *
983         * @return the current mode. One of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN}
984         * or {@link #COLLAPSE_MODE_PARALLAX}.
985         */
986        @CollapseMode
987        public int getCollapseMode() {
988            return mCollapseMode;
989        }
990
991        /**
992         * Set the parallax scroll multiplier used in conjunction with
993         * {@link #COLLAPSE_MODE_PARALLAX}. A value of {@code 0.0} indicates no movement at all,
994         * {@code 1.0f} indicates normal scroll movement.
995         *
996         * @param multiplier the multiplier.
997         *
998         * @see #getParallaxMultiplier()
999         */
1000        public void setParallaxMultiplier(float multiplier) {
1001            mParallaxMult = multiplier;
1002        }
1003
1004        /**
1005         * Returns the parallax scroll multiplier used in conjunction with
1006         * {@link #COLLAPSE_MODE_PARALLAX}.
1007         *
1008         * @see #setParallaxMultiplier(float)
1009         */
1010        public float getParallaxMultiplier() {
1011            return mParallaxMult;
1012        }
1013    }
1014
1015    private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener {
1016        @Override
1017        public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
1018            mCurrentOffset = verticalOffset;
1019
1020            final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
1021            final int scrollRange = layout.getTotalScrollRange();
1022
1023            for (int i = 0, z = getChildCount(); i < z; i++) {
1024                final View child = getChildAt(i);
1025                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1026                final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
1027
1028                switch (lp.mCollapseMode) {
1029                    case LayoutParams.COLLAPSE_MODE_PIN:
1030                        if (getHeight() - insetTop + verticalOffset >= child.getHeight()) {
1031                            offsetHelper.setTopAndBottomOffset(-verticalOffset);
1032                        }
1033                        break;
1034                    case LayoutParams.COLLAPSE_MODE_PARALLAX:
1035                        offsetHelper.setTopAndBottomOffset(
1036                                Math.round(-verticalOffset * lp.mParallaxMult));
1037                        break;
1038                }
1039            }
1040
1041            // Show or hide the scrims if needed
1042            if (mContentScrim != null || mStatusBarScrim != null) {
1043                setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop);
1044            }
1045
1046            if (mStatusBarScrim != null && insetTop > 0) {
1047                ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
1048            }
1049
1050            // Update the collapsing text's fraction
1051            final int expandRange = getHeight() - ViewCompat.getMinimumHeight(
1052                    CollapsingToolbarLayout.this) - insetTop;
1053            mCollapsingTextHelper.setExpansionFraction(
1054                    Math.abs(verticalOffset) / (float) expandRange);
1055
1056            if (Math.abs(verticalOffset) == scrollRange) {
1057                // If we have some pinned children, and we're offset to only show those views,
1058                // we want to be elevate
1059                ViewCompat.setElevation(layout, layout.getTargetElevation());
1060            } else {
1061                // Otherwise, we're inline with the content
1062                ViewCompat.setElevation(layout, 0f);
1063            }
1064        }
1065    }
1066}
1067