1c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes/*
2c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * Copyright (C) 2015 The Android Open Source Project
3c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes *
4c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * you may not use this file except in compliance with the License.
6c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * You may obtain a copy of the License at
7c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes *
8c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes *      http://www.apache.org/licenses/LICENSE-2.0
9c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes *
10c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * Unless required by applicable law or agreed to in writing, software
11c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * See the License for the specific language governing permissions and
14c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes * limitations under the License.
15c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes */
16c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
17c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banespackage android.support.design.widget;
18c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
19c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.content.res.TypedArray;
20c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Bitmap;
21c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Canvas;
22c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Color;
23c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Paint;
24c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Rect;
255c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banesimport android.graphics.RectF;
26388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banesimport android.graphics.Typeface;
27c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.os.Build;
28c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.support.design.R;
29fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banesimport android.support.v4.text.TextDirectionHeuristicsCompat;
305c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banesimport android.support.v4.view.GravityCompat;
31c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.support.v4.view.ViewCompat;
32c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.text.TextPaint;
33c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.text.TextUtils;
34c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.view.Gravity;
35c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.view.View;
36c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.view.animation.Interpolator;
37c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
38c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesfinal class CollapsingTextHelper {
39c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
40c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    // Pre-JB-MR2 doesn't support HW accelerated canvas scaled text so we will workaround it
41c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    // by using our own texture
42c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static final boolean USE_SCALING_TEXTURE = Build.VERSION.SDK_INT < 18;
43c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
44c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static final boolean DEBUG_DRAW = false;
45c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static final Paint DEBUG_DRAW_PAINT;
46c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    static {
47c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        DEBUG_DRAW_PAINT = DEBUG_DRAW ? new Paint() : null;
48c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (DEBUG_DRAW_PAINT != null) {
49c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            DEBUG_DRAW_PAINT.setAntiAlias(true);
50c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            DEBUG_DRAW_PAINT.setColor(Color.MAGENTA);
51c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
52c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
53c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
54c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private final View mView;
55c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
5666a875e525700de5eed715a52a3329ab5a310d96Chris Banes    private boolean mDrawTitle;
57c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private float mExpandedFraction;
58c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
59c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private final Rect mExpandedBounds;
60c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private final Rect mCollapsedBounds;
615c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private final RectF mCurrentBounds;
625c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private int mExpandedTextGravity = Gravity.CENTER_VERTICAL;
635c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private int mCollapsedTextGravity = Gravity.CENTER_VERTICAL;
649934230aa2959ee9f1802aa81fd21e25030b0d10Chris Banes    private float mExpandedTextSize = 15;
659934230aa2959ee9f1802aa81fd21e25030b0d10Chris Banes    private float mCollapsedTextSize = 15;
66c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private int mExpandedTextColor;
67c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private int mCollapsedTextColor;
68c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
695c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mExpandedDrawY;
705c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mCollapsedDrawY;
715c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mExpandedDrawX;
725c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mCollapsedDrawX;
735c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mCurrentDrawX;
745c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mCurrentDrawY;
75d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    private Typeface mCollapsedTypeface;
76d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    private Typeface mExpandedTypeface;
77d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    private Typeface mCurrentTypeface;
78c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
79c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private CharSequence mText;
80c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private CharSequence mTextToDraw;
81fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes    private boolean mIsRtl;
82c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
83c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private boolean mUseTexture;
84c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private Bitmap mExpandedTitleTexture;
85c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private Paint mTexturePaint;
86c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private float mTextureAscent;
87c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private float mTextureDescent;
88c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
89c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private float mScale;
90a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes    private float mCurrentTextSize;
91c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
929995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes    private boolean mBoundsChanged;
939995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes
94c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private final TextPaint mTextPaint;
95c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
96c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private Interpolator mPositionInterpolator;
97c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private Interpolator mTextSizeInterpolator;
98c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
9923caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private float mCollapsedShadowRadius, mCollapsedShadowDx, mCollapsedShadowDy;
10023caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private int mCollapsedShadowColor;
10123caabcc1fc96e3756d0c354b49290467080de6aChris Banes
10223caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private float mExpandedShadowRadius, mExpandedShadowDx, mExpandedShadowDy;
10323caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private int mExpandedShadowColor;
10423caabcc1fc96e3756d0c354b49290467080de6aChris Banes
105c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    public CollapsingTextHelper(View view) {
106c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mView = view;
107c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
108973218c60005eb672a27b027403c9231d5d21303Chris Banes        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
109c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
110c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mCollapsedBounds = new Rect();
111c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mExpandedBounds = new Rect();
1125c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds = new RectF();
113c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
114c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
115c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setTextSizeInterpolator(Interpolator interpolator) {
116c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mTextSizeInterpolator = interpolator;
117c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        recalculate();
118c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
119c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
120c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setPositionInterpolator(Interpolator interpolator) {
121c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mPositionInterpolator = interpolator;
122c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        recalculate();
123c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
124c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
125c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpandedTextSize(float textSize) {
126c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mExpandedTextSize != textSize) {
127c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTextSize = textSize;
128c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
129c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
130c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
131c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
132c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setCollapsedTextSize(float textSize) {
133c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mCollapsedTextSize != textSize) {
134c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mCollapsedTextSize = textSize;
135c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
136c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
137c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
138c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
139c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setCollapsedTextColor(int textColor) {
140c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mCollapsedTextColor != textColor) {
141c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mCollapsedTextColor = textColor;
142c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
143c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
144c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
145c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
146c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpandedTextColor(int textColor) {
147c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mExpandedTextColor != textColor) {
148c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTextColor = textColor;
149c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
150c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
151c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
152c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
153c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpandedBounds(int left, int top, int right, int bottom) {
1549995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        if (!rectEquals(mExpandedBounds, left, top, right, bottom)) {
1559995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mExpandedBounds.set(left, top, right, bottom);
1569995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mBoundsChanged = true;
15766a875e525700de5eed715a52a3329ab5a310d96Chris Banes            onBoundsChanged();
1589995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        }
159c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
160c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
161c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setCollapsedBounds(int left, int top, int right, int bottom) {
1629995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        if (!rectEquals(mCollapsedBounds, left, top, right, bottom)) {
1639995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mCollapsedBounds.set(left, top, right, bottom);
1649995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mBoundsChanged = true;
16566a875e525700de5eed715a52a3329ab5a310d96Chris Banes            onBoundsChanged();
1669995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        }
167c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
168c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
16966a875e525700de5eed715a52a3329ab5a310d96Chris Banes    void onBoundsChanged() {
17066a875e525700de5eed715a52a3329ab5a310d96Chris Banes        mDrawTitle = mCollapsedBounds.width() > 0 && mCollapsedBounds.height() > 0
17166a875e525700de5eed715a52a3329ab5a310d96Chris Banes                && mExpandedBounds.width() > 0 && mExpandedBounds.height() > 0;
17266a875e525700de5eed715a52a3329ab5a310d96Chris Banes    }
17366a875e525700de5eed715a52a3329ab5a310d96Chris Banes
1745c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    void setExpandedTextGravity(int gravity) {
1755c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        if (mExpandedTextGravity != gravity) {
1765c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            mExpandedTextGravity = gravity;
177c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
178c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
179c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
180c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
1815c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    int getExpandedTextGravity() {
1825c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        return mExpandedTextGravity;
1835c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    }
184c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
1855c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    void setCollapsedTextGravity(int gravity) {
1865c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        if (mCollapsedTextGravity != gravity) {
1875c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            mCollapsedTextGravity = gravity;
188c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
189c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
190c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
191c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
1925c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    int getCollapsedTextGravity() {
1935c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        return mCollapsedTextGravity;
1945c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    }
1955c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes
196c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setCollapsedTextAppearance(int resId) {
1976759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        TypedArray a = mView.getContext().obtainStyledAttributes(resId,
1986759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance);
1996759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) {
2009934230aa2959ee9f1802aa81fd21e25030b0d10Chris Banes            mCollapsedTextColor = a.getColor(
2016759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor,
2026759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    mCollapsedTextColor);
203c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
2046759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) {
205c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mCollapsedTextSize = a.getDimensionPixelSize(
2066759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
2076759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    (int) mCollapsedTextSize);
208c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
2096759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mCollapsedShadowColor = a.getInt(
2106759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0);
2116759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mCollapsedShadowDx = a.getFloat(
2126759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
2136759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mCollapsedShadowDy = a.getFloat(
2146759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
2156759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mCollapsedShadowRadius = a.getFloat(
2166759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
217c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        a.recycle();
218c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
219d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        if (Build.VERSION.SDK_INT >= 16) {
220d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mCollapsedTypeface = readFontFamilyTypeface(resId);
221d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        }
222d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
223c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        recalculate();
224c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
225c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
226c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpandedTextAppearance(int resId) {
2276759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        TypedArray a = mView.getContext().obtainStyledAttributes(resId,
2286759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance);
2296759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) {
2309934230aa2959ee9f1802aa81fd21e25030b0d10Chris Banes            mExpandedTextColor = a.getColor(
2316759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor,
2326759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    mExpandedTextColor);
233c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
2346759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) {
235c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTextSize = a.getDimensionPixelSize(
2366759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
2376759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    (int) mExpandedTextSize);
238c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
2396759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mExpandedShadowColor = a.getInt(
2406759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0);
2416759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mExpandedShadowDx = a.getFloat(
2426759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
2436759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mExpandedShadowDy = a.getFloat(
2446759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
2456759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mExpandedShadowRadius = a.getFloat(
2466759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
247c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        a.recycle();
248c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
249d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        if (Build.VERSION.SDK_INT >= 16) {
250d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mExpandedTypeface = readFontFamilyTypeface(resId);
251d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        }
252d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
253c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        recalculate();
254c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
255c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
256d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    private Typeface readFontFamilyTypeface(int resId) {
257d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        final TypedArray a = mView.getContext().obtainStyledAttributes(resId,
258d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                new int[]{android.R.attr.fontFamily});
259d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        try {
260d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            final String family = a.getString(0);
261d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            if (family != null) {
262d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                return Typeface.create(family, Typeface.NORMAL);
263d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            }
264d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        } finally {
265d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            a.recycle();
266d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        }
267d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        return null;
268d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    }
269d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
270d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    void setCollapsedTypeface(Typeface typeface) {
271d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        if (mCollapsedTypeface != typeface) {
272d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mCollapsedTypeface = typeface;
273d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            recalculate();
274388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes        }
275d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    }
276d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
277d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    void setExpandedTypeface(Typeface typeface) {
278d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        if (mExpandedTypeface != typeface) {
279d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mExpandedTypeface = typeface;
280388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes            recalculate();
281388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes        }
282388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes    }
283388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes
284d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    void setTypefaces(Typeface typeface) {
285d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        mCollapsedTypeface = mExpandedTypeface = typeface;
286d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        recalculate();
287d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    }
288d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
289d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    Typeface getCollapsedTypeface() {
290d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        return mCollapsedTypeface != null ? mCollapsedTypeface : Typeface.DEFAULT;
291d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    }
292d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
293d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    Typeface getExpandedTypeface() {
294d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        return mExpandedTypeface != null ? mExpandedTypeface : Typeface.DEFAULT;
295388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes    }
296388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes
297c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    /**
298c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * Set the value indicating the current scroll value. This decides how much of the
299c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * background will be displayed, as well as the title metrics/positioning.
300c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     *
301c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * A value of {@code 0.0} indicates that the layout is fully expanded.
302c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * A value of {@code 1.0} indicates that the layout is fully collapsed.
303c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     */
304c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpansionFraction(float fraction) {
305a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        fraction = MathUtils.constrain(fraction, 0f, 1f);
306a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
307c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (fraction != mExpandedFraction) {
308c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedFraction = fraction;
3095c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            calculateCurrentOffsets();
310c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
311c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
312c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
313c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    float getExpansionFraction() {
314c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mExpandedFraction;
315c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
316c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
317c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    float getCollapsedTextSize() {
318c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mCollapsedTextSize;
319c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
320c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
321c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    float getExpandedTextSize() {
322c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mExpandedTextSize;
323c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
324c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
3255c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private void calculateCurrentOffsets() {
32623caabcc1fc96e3756d0c354b49290467080de6aChris Banes        calculateOffsets(mExpandedFraction);
32723caabcc1fc96e3756d0c354b49290467080de6aChris Banes    }
328c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
32923caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private void calculateOffsets(final float fraction) {
3305c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        interpolateBounds(fraction);
3315c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentDrawX = lerp(mExpandedDrawX, mCollapsedDrawX, fraction,
3325c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mPositionInterpolator);
3335c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentDrawY = lerp(mExpandedDrawY, mCollapsedDrawY, fraction,
3345c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mPositionInterpolator);
3355c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes
3365c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        setInterpolatedTextSize(lerp(mExpandedTextSize, mCollapsedTextSize,
337c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                fraction, mTextSizeInterpolator));
338c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
339c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mCollapsedTextColor != mExpandedTextColor) {
340c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // If the collapsed and expanded text colors are different, blend them based on the
341c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // fraction
342c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mTextPaint.setColor(blendColors(mExpandedTextColor, mCollapsedTextColor, fraction));
343c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        } else {
344c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mTextPaint.setColor(mCollapsedTextColor);
345c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
346c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
34723caabcc1fc96e3756d0c354b49290467080de6aChris Banes        mTextPaint.setShadowLayer(
34823caabcc1fc96e3756d0c354b49290467080de6aChris Banes                lerp(mExpandedShadowRadius, mCollapsedShadowRadius, fraction, null),
34923caabcc1fc96e3756d0c354b49290467080de6aChris Banes                lerp(mExpandedShadowDx, mCollapsedShadowDx, fraction, null),
35023caabcc1fc96e3756d0c354b49290467080de6aChris Banes                lerp(mExpandedShadowDy, mCollapsedShadowDy, fraction, null),
35123caabcc1fc96e3756d0c354b49290467080de6aChris Banes                blendColors(mExpandedShadowColor, mCollapsedShadowColor, fraction));
35223caabcc1fc96e3756d0c354b49290467080de6aChris Banes
353c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        ViewCompat.postInvalidateOnAnimation(mView);
354c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
355c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
3565c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private void calculateBaseOffsets() {
35737430e835e22a791b83b01befab44ead852bf555Chris Banes        final float currentTextSize = mCurrentTextSize;
35837430e835e22a791b83b01befab44ead852bf555Chris Banes
359c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        // We then calculate the collapsed text size, using the same logic
36037430e835e22a791b83b01befab44ead852bf555Chris Banes        calculateUsingTextSize(mCollapsedTextSize);
3615c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        float width = mTextToDraw != null ?
3625c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0;
3635c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        final int collapsedAbsGravity = GravityCompat.getAbsoluteGravity(mCollapsedTextGravity,
3645c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR);
3655c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        switch (collapsedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
366c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.BOTTOM:
3675c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawY = mCollapsedBounds.bottom;
368c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
369c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.TOP:
3705c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawY = mCollapsedBounds.top - mTextPaint.ascent();
371c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
372c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.CENTER_VERTICAL:
373c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            default:
374c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
375c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                float textOffset = (textHeight / 2) - mTextPaint.descent();
3765c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawY = mCollapsedBounds.centerY() + textOffset;
3775c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
3785c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        }
379f828412cf077314f63e9d989c889f69275a28b19Chris Banes        switch (collapsedAbsGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
3805c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.CENTER_HORIZONTAL:
3815c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawX = mCollapsedBounds.centerX() - (width / 2);
3825c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
3835c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.RIGHT:
3845c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawX = mCollapsedBounds.right - width;
3855c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
3865c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.LEFT:
3875c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            default:
3885c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawX = mCollapsedBounds.left;
389c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
390c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
391c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
39237430e835e22a791b83b01befab44ead852bf555Chris Banes        calculateUsingTextSize(mExpandedTextSize);
3935c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        width = mTextToDraw != null
3945c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                ? mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0;
3955c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        final int expandedAbsGravity = GravityCompat.getAbsoluteGravity(mExpandedTextGravity,
3965c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR);
3975c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        switch (expandedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
398c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.BOTTOM:
3995c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawY = mExpandedBounds.bottom;
400c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
401c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.TOP:
4025c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawY = mExpandedBounds.top - mTextPaint.ascent();
403c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
404c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.CENTER_VERTICAL:
405c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            default:
406c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
407c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                float textOffset = (textHeight / 2) - mTextPaint.descent();
4085c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawY = mExpandedBounds.centerY() + textOffset;
4095c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4105c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        }
411f828412cf077314f63e9d989c889f69275a28b19Chris Banes        switch (expandedAbsGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
4125c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.CENTER_HORIZONTAL:
4135c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawX = mExpandedBounds.centerX() - (width / 2);
4145c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4155c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.RIGHT:
4165c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawX = mExpandedBounds.right - width;
4175c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4185c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.LEFT:
4195c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            default:
4205c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawX = mExpandedBounds.left;
421c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
422c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
423c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
424c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        // The bounds have changed so we need to clear the texture
425c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        clearTexture();
42637430e835e22a791b83b01befab44ead852bf555Chris Banes        // Now reset the text size back to the original
42737430e835e22a791b83b01befab44ead852bf555Chris Banes        setInterpolatedTextSize(currentTextSize);
428c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
429c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
4305c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private void interpolateBounds(float fraction) {
4315c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds.left = lerp(mExpandedBounds.left, mCollapsedBounds.left,
4325c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                fraction, mPositionInterpolator);
4335c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds.top = lerp(mExpandedDrawY, mCollapsedDrawY,
4345c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                fraction, mPositionInterpolator);
4355c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds.right = lerp(mExpandedBounds.right, mCollapsedBounds.right,
4365c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                fraction, mPositionInterpolator);
4375c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds.bottom = lerp(mExpandedBounds.bottom, mCollapsedBounds.bottom,
4385c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                fraction, mPositionInterpolator);
4395c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    }
4405c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes
441c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    public void draw(Canvas canvas) {
442c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        final int saveCount = canvas.save();
443c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
44466a875e525700de5eed715a52a3329ab5a310d96Chris Banes        if (mTextToDraw != null && mDrawTitle) {
4455c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            float x = mCurrentDrawX;
4465c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            float y = mCurrentDrawY;
447c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
448c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            final boolean drawTexture = mUseTexture && mExpandedTitleTexture != null;
449c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
450c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            final float ascent;
451c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            final float descent;
452c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (drawTexture) {
453c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                ascent = mTextureAscent * mScale;
454c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                descent = mTextureDescent * mScale;
455c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            } else {
456c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                ascent = mTextPaint.ascent() * mScale;
457c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                descent = mTextPaint.descent() * mScale;
458c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
459c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
460c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (DEBUG_DRAW) {
461c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                // Just a debug tool, which drawn a Magneta rect in the text bounds
4625c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                canvas.drawRect(mCurrentBounds.left, y + ascent, mCurrentBounds.right, y + descent,
463c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                        DEBUG_DRAW_PAINT);
464c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
465c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
466c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (drawTexture) {
467c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                y += ascent;
468c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
469c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
470c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (mScale != 1f) {
471c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                canvas.scale(mScale, mScale, x, y);
472c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
473c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
474c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (drawTexture) {
475c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                // If we should use a texture, draw it instead of text
476c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                canvas.drawBitmap(mExpandedTitleTexture, x, y, mTexturePaint);
477c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            } else {
478c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                canvas.drawText(mTextToDraw, 0, mTextToDraw.length(), x, y, mTextPaint);
479c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
480c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
481c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
482c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        canvas.restoreToCount(saveCount);
483c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
484c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
485fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes    private boolean calculateIsRtl(CharSequence text) {
486fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes        final boolean defaultIsRtl = ViewCompat.getLayoutDirection(mView)
487fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes                == ViewCompat.LAYOUT_DIRECTION_RTL;
488fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes        return (defaultIsRtl
489fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes                ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL
490fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes                : TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR).isRtl(text, 0, text.length());
491fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes    }
492fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes
49337430e835e22a791b83b01befab44ead852bf555Chris Banes    private void setInterpolatedTextSize(float textSize) {
49437430e835e22a791b83b01befab44ead852bf555Chris Banes        calculateUsingTextSize(textSize);
49537430e835e22a791b83b01befab44ead852bf555Chris Banes
49637430e835e22a791b83b01befab44ead852bf555Chris Banes        // Use our texture if the scale isn't 1.0
49737430e835e22a791b83b01befab44ead852bf555Chris Banes        mUseTexture = USE_SCALING_TEXTURE && mScale != 1f;
49837430e835e22a791b83b01befab44ead852bf555Chris Banes
49937430e835e22a791b83b01befab44ead852bf555Chris Banes        if (mUseTexture) {
50037430e835e22a791b83b01befab44ead852bf555Chris Banes            // Make sure we have an expanded texture if needed
50137430e835e22a791b83b01befab44ead852bf555Chris Banes            ensureExpandedTexture();
50237430e835e22a791b83b01befab44ead852bf555Chris Banes        }
50337430e835e22a791b83b01befab44ead852bf555Chris Banes
50437430e835e22a791b83b01befab44ead852bf555Chris Banes        ViewCompat.postInvalidateOnAnimation(mView);
50537430e835e22a791b83b01befab44ead852bf555Chris Banes    }
50637430e835e22a791b83b01befab44ead852bf555Chris Banes
50737430e835e22a791b83b01befab44ead852bf555Chris Banes    private void calculateUsingTextSize(final float textSize) {
508c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mText == null) return;
509c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
510a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        final float availableWidth;
511a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        final float newTextSize;
512a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        boolean updateDrawText = false;
513c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
514c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (isClose(textSize, mCollapsedTextSize)) {
515c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            availableWidth = mCollapsedBounds.width();
516a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            newTextSize = mCollapsedTextSize;
517a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            mScale = 1f;
518d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            if (mCurrentTypeface != mCollapsedTypeface) {
519d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                mCurrentTypeface = mCollapsedTypeface;
520d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                updateDrawText = true;
521d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            }
522c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        } else {
523a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            availableWidth = mExpandedBounds.width();
524a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            newTextSize = mExpandedTextSize;
525d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            if (mCurrentTypeface != mExpandedTypeface) {
526d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                mCurrentTypeface = mExpandedTypeface;
527d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                updateDrawText = true;
528d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            }
529c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
530c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (isClose(textSize, mExpandedTextSize)) {
531c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                // If we're close to the expanded text size, snap to it and use a scale of 1
532c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                mScale = 1f;
533c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            } else {
534c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                // Else, we'll scale down from the expanded text size
535c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                mScale = textSize / mExpandedTextSize;
536c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
537c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
538c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
539a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        if (availableWidth > 0) {
540d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            updateDrawText = (mCurrentTextSize != newTextSize) || mBoundsChanged || updateDrawText;
541a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            mCurrentTextSize = newTextSize;
5429995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mBoundsChanged = false;
543a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        }
544a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
545a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        if (mTextToDraw == null || updateDrawText) {
546a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            mTextPaint.setTextSize(mCurrentTextSize);
547d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mTextPaint.setTypeface(mCurrentTypeface);
548973218c60005eb672a27b027403c9231d5d21303Chris Banes            // Use linear text scaling if we're scaling the canvas
549973218c60005eb672a27b027403c9231d5d21303Chris Banes            mTextPaint.setLinearText(mScale != 1f);
550a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
551c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // If we don't currently have text to draw, or the text size has changed, ellipsize...
552c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            final CharSequence title = TextUtils.ellipsize(mText, mTextPaint,
553c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                    availableWidth, TextUtils.TruncateAt.END);
55437430e835e22a791b83b01befab44ead852bf555Chris Banes            if (!TextUtils.equals(title, mTextToDraw)) {
555c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                mTextToDraw = title;
55637430e835e22a791b83b01befab44ead852bf555Chris Banes                mIsRtl = calculateIsRtl(mTextToDraw);
557c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
558c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
559c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
560c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
561c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private void ensureExpandedTexture() {
562c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mExpandedTitleTexture != null || mExpandedBounds.isEmpty()
563c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                || TextUtils.isEmpty(mTextToDraw)) {
564c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            return;
565c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
566c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
56723caabcc1fc96e3756d0c354b49290467080de6aChris Banes        calculateOffsets(0f);
5685c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mTextureAscent = mTextPaint.ascent();
5695c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mTextureDescent = mTextPaint.descent();
570c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
571c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        final int w = Math.round(mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()));
5725c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        final int h = Math.round(mTextureDescent - mTextureAscent);
573c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
5746fa3c92dcaf584ac56bff4c0b52b7b1ac379592eChris Banes        if (w <= 0 || h <= 0) {
575c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            return; // If the width or height are 0, return
576c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
577c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
578c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mExpandedTitleTexture = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
579c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
580c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        Canvas c = new Canvas(mExpandedTitleTexture);
581c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        c.drawText(mTextToDraw, 0, mTextToDraw.length(), 0, h - mTextPaint.descent(), mTextPaint);
582c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
583c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mTexturePaint == null) {
584c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // Make sure we have a paint
5855c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            mTexturePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
586c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
587c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
588c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
5891bf6093c6dab532bc57c75767470afa3cbbe0384Chris Banes    public void recalculate() {
5901bf6093c6dab532bc57c75767470afa3cbbe0384Chris Banes        if (mView.getHeight() > 0 && mView.getWidth() > 0) {
591c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // If we've already been laid out, calculate everything now otherwise we'll wait
592c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // until a layout
5935c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            calculateBaseOffsets();
5945c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            calculateCurrentOffsets();
595c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
596c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
597c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
598c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    /**
599c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * Set the title to display
600c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     *
601c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * @param text
602c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     */
603c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setText(CharSequence text) {
604c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (text == null || !text.equals(mText)) {
605c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mText = text;
606d9d3addb4628537f68348d40346765b7f407cf79Chris Banes            mTextToDraw = null;
607c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            clearTexture();
608c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
609c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
610c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
611c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
612c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    CharSequence getText() {
613c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mText;
614c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
615c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
616c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private void clearTexture() {
617c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mExpandedTitleTexture != null) {
618c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTitleTexture.recycle();
619c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTitleTexture = null;
620c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
621c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
622c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
623c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    /**
624c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * Returns true if {@code value} is 'close' to it's closest decimal value. Close is currently
625c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * defined as it's difference being < 0.001.
626c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     */
627c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static boolean isClose(float value, float targetValue) {
628c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return Math.abs(value - targetValue) < 0.001f;
629c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
630c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
631c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    int getExpandedTextColor() {
632c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mExpandedTextColor;
633c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
634c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
635c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    int getCollapsedTextColor() {
636c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mCollapsedTextColor;
637c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
638c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
639c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    /**
640c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * Blend {@code color1} and {@code color2} using the given ratio.
641c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     *
642c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * @param ratio of which to blend. 0.0 will return {@code color1}, 0.5 will give an even blend,
643c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     *              1.0 will return {@code color2}.
644c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     */
645c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static int blendColors(int color1, int color2, float ratio) {
646c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        final float inverseRatio = 1f - ratio;
647c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio);
648c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio);
649c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio);
650c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio);
651c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return Color.argb((int) a, (int) r, (int) g, (int) b);
652c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
653c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
6545c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private static float lerp(float startValue, float endValue, float fraction,
655c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            Interpolator interpolator) {
656c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (interpolator != null) {
657c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            fraction = interpolator.getInterpolation(fraction);
658c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
659c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return AnimationUtils.lerp(startValue, endValue, fraction);
660c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
6619995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes
6629995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes    private static boolean rectEquals(Rect r, int left, int top, int right, int bottom) {
6639995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        return !(r.left != left || r.top != top || r.right != right || r.bottom != bottom);
6649995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes    }
665c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes}
666