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