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