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