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
198a40d691aa783c2e85298e895a3e51e98606aa85Chris Banesimport android.content.res.ColorStateList;
20c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.content.res.TypedArray;
21c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Bitmap;
22c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Canvas;
23c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Color;
24c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Paint;
25c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.graphics.Rect;
265c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banesimport android.graphics.RectF;
27388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banesimport android.graphics.Typeface;
28c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.os.Build;
298a40d691aa783c2e85298e895a3e51e98606aa85Chris Banesimport android.support.annotation.ColorInt;
3012e0a308d1ab838cb05c395baa6f1251fcbb2363Aurimas Liutikasimport android.support.v4.math.MathUtils;
31fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banesimport android.support.v4.text.TextDirectionHeuristicsCompat;
325c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banesimport android.support.v4.view.GravityCompat;
33c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.support.v4.view.ViewCompat;
348a40d691aa783c2e85298e895a3e51e98606aa85Chris Banesimport android.support.v7.widget.TintTypedArray;
35c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.text.TextPaint;
36c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.text.TextUtils;
37c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.view.Gravity;
38c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.view.View;
39c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesimport android.view.animation.Interpolator;
40c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
41c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banesfinal class CollapsingTextHelper {
42c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
43c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    // Pre-JB-MR2 doesn't support HW accelerated canvas scaled text so we will workaround it
44c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    // by using our own texture
45c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static final boolean USE_SCALING_TEXTURE = Build.VERSION.SDK_INT < 18;
46c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
47c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static final boolean DEBUG_DRAW = false;
48c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static final Paint DEBUG_DRAW_PAINT;
49c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    static {
50c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        DEBUG_DRAW_PAINT = DEBUG_DRAW ? new Paint() : null;
51c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (DEBUG_DRAW_PAINT != null) {
52c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            DEBUG_DRAW_PAINT.setAntiAlias(true);
53c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            DEBUG_DRAW_PAINT.setColor(Color.MAGENTA);
54c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
55c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
56c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
57c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private final View mView;
58c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
5966a875e525700de5eed715a52a3329ab5a310d96Chris Banes    private boolean mDrawTitle;
60c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private float mExpandedFraction;
61c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
62c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private final Rect mExpandedBounds;
63c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private final Rect mCollapsedBounds;
645c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private final RectF mCurrentBounds;
655c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private int mExpandedTextGravity = Gravity.CENTER_VERTICAL;
665c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private int mCollapsedTextGravity = Gravity.CENTER_VERTICAL;
679934230aa2959ee9f1802aa81fd21e25030b0d10Chris Banes    private float mExpandedTextSize = 15;
689934230aa2959ee9f1802aa81fd21e25030b0d10Chris Banes    private float mCollapsedTextSize = 15;
698a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    private ColorStateList mExpandedTextColor;
708a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    private ColorStateList mCollapsedTextColor;
71c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
725c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mExpandedDrawY;
735c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mCollapsedDrawY;
745c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mExpandedDrawX;
755c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mCollapsedDrawX;
765c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mCurrentDrawX;
775c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private float mCurrentDrawY;
78d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    private Typeface mCollapsedTypeface;
79d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    private Typeface mExpandedTypeface;
80d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    private Typeface mCurrentTypeface;
81c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
82c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private CharSequence mText;
83c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private CharSequence mTextToDraw;
84fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes    private boolean mIsRtl;
85c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
86c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private boolean mUseTexture;
87c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private Bitmap mExpandedTitleTexture;
88c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private Paint mTexturePaint;
89c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private float mTextureAscent;
90c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private float mTextureDescent;
91c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
92c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private float mScale;
93a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes    private float mCurrentTextSize;
94c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
958a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    private int[] mState;
968a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes
979995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes    private boolean mBoundsChanged;
989995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes
99c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private final TextPaint mTextPaint;
100c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
101c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private Interpolator mPositionInterpolator;
102c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private Interpolator mTextSizeInterpolator;
103c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
10423caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private float mCollapsedShadowRadius, mCollapsedShadowDx, mCollapsedShadowDy;
10523caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private int mCollapsedShadowColor;
10623caabcc1fc96e3756d0c354b49290467080de6aChris Banes
10723caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private float mExpandedShadowRadius, mExpandedShadowDx, mExpandedShadowDy;
10823caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private int mExpandedShadowColor;
10923caabcc1fc96e3756d0c354b49290467080de6aChris Banes
110c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    public CollapsingTextHelper(View view) {
111c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mView = view;
112c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
113973218c60005eb672a27b027403c9231d5d21303Chris Banes        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
114c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
115c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mCollapsedBounds = new Rect();
116c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mExpandedBounds = new Rect();
1175c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds = new RectF();
118c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
119c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
120c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setTextSizeInterpolator(Interpolator interpolator) {
121c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mTextSizeInterpolator = interpolator;
122c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        recalculate();
123c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
124c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
125c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setPositionInterpolator(Interpolator interpolator) {
126c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mPositionInterpolator = interpolator;
127c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        recalculate();
128c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
129c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
130c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpandedTextSize(float textSize) {
131c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mExpandedTextSize != textSize) {
132c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTextSize = textSize;
133c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
134c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
135c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
136c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
137c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setCollapsedTextSize(float textSize) {
138c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mCollapsedTextSize != textSize) {
139c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mCollapsedTextSize = textSize;
140c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
141c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
142c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
143c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
1448a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    void setCollapsedTextColor(ColorStateList textColor) {
145c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mCollapsedTextColor != textColor) {
146c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mCollapsedTextColor = textColor;
147c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
148c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
149c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
150c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
1518a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    void setExpandedTextColor(ColorStateList textColor) {
152c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mExpandedTextColor != textColor) {
153c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTextColor = textColor;
154c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
155c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
156c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
157c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
158c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpandedBounds(int left, int top, int right, int bottom) {
1599995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        if (!rectEquals(mExpandedBounds, left, top, right, bottom)) {
1609995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mExpandedBounds.set(left, top, right, bottom);
1619995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mBoundsChanged = true;
16266a875e525700de5eed715a52a3329ab5a310d96Chris Banes            onBoundsChanged();
1639995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        }
164c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
165c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
166c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setCollapsedBounds(int left, int top, int right, int bottom) {
1679995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        if (!rectEquals(mCollapsedBounds, left, top, right, bottom)) {
1689995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mCollapsedBounds.set(left, top, right, bottom);
1699995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mBoundsChanged = true;
17066a875e525700de5eed715a52a3329ab5a310d96Chris Banes            onBoundsChanged();
1719995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        }
172c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
173c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
17466a875e525700de5eed715a52a3329ab5a310d96Chris Banes    void onBoundsChanged() {
17566a875e525700de5eed715a52a3329ab5a310d96Chris Banes        mDrawTitle = mCollapsedBounds.width() > 0 && mCollapsedBounds.height() > 0
17666a875e525700de5eed715a52a3329ab5a310d96Chris Banes                && mExpandedBounds.width() > 0 && mExpandedBounds.height() > 0;
17766a875e525700de5eed715a52a3329ab5a310d96Chris Banes    }
17866a875e525700de5eed715a52a3329ab5a310d96Chris Banes
1795c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    void setExpandedTextGravity(int gravity) {
1805c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        if (mExpandedTextGravity != gravity) {
1815c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            mExpandedTextGravity = gravity;
182c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
183c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
184c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
185c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
1865c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    int getExpandedTextGravity() {
1875c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        return mExpandedTextGravity;
1885c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    }
189c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
1905c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    void setCollapsedTextGravity(int gravity) {
1915c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        if (mCollapsedTextGravity != gravity) {
1925c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            mCollapsedTextGravity = gravity;
193c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
194c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
195c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
196c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
1975c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    int getCollapsedTextGravity() {
1985c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        return mCollapsedTextGravity;
1995c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    }
2005c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes
201c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setCollapsedTextAppearance(int resId) {
2028a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), resId,
2036759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance);
2046759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) {
2058a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            mCollapsedTextColor = a.getColorStateList(
2068a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
207c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
2086759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) {
209c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mCollapsedTextSize = a.getDimensionPixelSize(
2106759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
2116759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    (int) mCollapsedTextSize);
212c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
2136759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mCollapsedShadowColor = a.getInt(
2146759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0);
2156759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mCollapsedShadowDx = a.getFloat(
2166759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
2176759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mCollapsedShadowDy = a.getFloat(
2186759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
2196759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mCollapsedShadowRadius = a.getFloat(
2206759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
221c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        a.recycle();
222c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
223d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        if (Build.VERSION.SDK_INT >= 16) {
224d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mCollapsedTypeface = readFontFamilyTypeface(resId);
225d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        }
226d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
227c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        recalculate();
228c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
229c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
230c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpandedTextAppearance(int resId) {
2318a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), resId,
2326759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance);
2336759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) {
2348a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            mExpandedTextColor = a.getColorStateList(
2358a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
236c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
2376759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) {
238c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTextSize = a.getDimensionPixelSize(
2396759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
2406759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                    (int) mExpandedTextSize);
241c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
2426759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mExpandedShadowColor = a.getInt(
2436759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0);
2446759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mExpandedShadowDx = a.getFloat(
2456759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
2466759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mExpandedShadowDy = a.getFloat(
2476759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
2486759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski        mExpandedShadowRadius = a.getFloat(
2496759b1021d8198ad1d239bb30e5a102b99624bceAdam Lesinski                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
250c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        a.recycle();
251c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
252d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        if (Build.VERSION.SDK_INT >= 16) {
253d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mExpandedTypeface = readFontFamilyTypeface(resId);
254d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        }
255d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
256c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        recalculate();
257c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
258c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
259d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    private Typeface readFontFamilyTypeface(int resId) {
260d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        final TypedArray a = mView.getContext().obtainStyledAttributes(resId,
261d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                new int[]{android.R.attr.fontFamily});
262d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        try {
263d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            final String family = a.getString(0);
264d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            if (family != null) {
265d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                return Typeface.create(family, Typeface.NORMAL);
266d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            }
267d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        } finally {
268d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            a.recycle();
269d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        }
270d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        return null;
271d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    }
272d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
273d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    void setCollapsedTypeface(Typeface typeface) {
274ad1b0e82100ee31e70040d77bfa4d847b2bf0864Aurimas Liutikas        if (areTypefacesDifferent(mCollapsedTypeface, typeface)) {
275d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mCollapsedTypeface = typeface;
276d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            recalculate();
277388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes        }
278d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    }
279d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
280d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    void setExpandedTypeface(Typeface typeface) {
281ad1b0e82100ee31e70040d77bfa4d847b2bf0864Aurimas Liutikas        if (areTypefacesDifferent(mExpandedTypeface, typeface)) {
282d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mExpandedTypeface = typeface;
283388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes            recalculate();
284388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes        }
285388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes    }
286388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes
287d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    void setTypefaces(Typeface typeface) {
288d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        mCollapsedTypeface = mExpandedTypeface = typeface;
289d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        recalculate();
290d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    }
291d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
292d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    Typeface getCollapsedTypeface() {
293d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        return mCollapsedTypeface != null ? mCollapsedTypeface : Typeface.DEFAULT;
294d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    }
295d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes
296d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes    Typeface getExpandedTypeface() {
297d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes        return mExpandedTypeface != null ? mExpandedTypeface : Typeface.DEFAULT;
298388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes    }
299388841ca2f07d0da9bbdc66c51abe789e05cf17bChris Banes
300c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    /**
301c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * Set the value indicating the current scroll value. This decides how much of the
302c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * background will be displayed, as well as the title metrics/positioning.
303c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     *
304c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * A value of {@code 0.0} indicates that the layout is fully expanded.
305c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * A value of {@code 1.0} indicates that the layout is fully collapsed.
306c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     */
307c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setExpansionFraction(float fraction) {
30812e0a308d1ab838cb05c395baa6f1251fcbb2363Aurimas Liutikas        fraction = MathUtils.clamp(fraction, 0f, 1f);
309a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
310c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (fraction != mExpandedFraction) {
311c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedFraction = fraction;
3125c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            calculateCurrentOffsets();
313c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
314c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
315c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
3168a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    final boolean setState(final int[] state) {
3178a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        mState = state;
3188a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes
3198a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        if (isStateful()) {
3208a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            recalculate();
3218a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            return true;
3228a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        }
3238a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes
3248a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        return false;
3258a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    }
3268a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes
3278a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    final boolean isStateful() {
3288a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        return (mCollapsedTextColor != null && mCollapsedTextColor.isStateful())
3298a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes                || (mExpandedTextColor != null && mExpandedTextColor.isStateful());
3308a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    }
3318a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes
332c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    float getExpansionFraction() {
333c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mExpandedFraction;
334c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
335c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
336c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    float getCollapsedTextSize() {
337c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mCollapsedTextSize;
338c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
339c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
340c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    float getExpandedTextSize() {
341c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mExpandedTextSize;
342c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
343c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
3445c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private void calculateCurrentOffsets() {
34523caabcc1fc96e3756d0c354b49290467080de6aChris Banes        calculateOffsets(mExpandedFraction);
34623caabcc1fc96e3756d0c354b49290467080de6aChris Banes    }
347c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
34823caabcc1fc96e3756d0c354b49290467080de6aChris Banes    private void calculateOffsets(final float fraction) {
3495c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        interpolateBounds(fraction);
3505c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentDrawX = lerp(mExpandedDrawX, mCollapsedDrawX, fraction,
3515c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mPositionInterpolator);
3525c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentDrawY = lerp(mExpandedDrawY, mCollapsedDrawY, fraction,
3535c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mPositionInterpolator);
3545c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes
3555c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        setInterpolatedTextSize(lerp(mExpandedTextSize, mCollapsedTextSize,
356c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                fraction, mTextSizeInterpolator));
357c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
358c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mCollapsedTextColor != mExpandedTextColor) {
359c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // If the collapsed and expanded text colors are different, blend them based on the
360c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // fraction
3618a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            mTextPaint.setColor(blendColors(
3628a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes                    getCurrentExpandedTextColor(), getCurrentCollapsedTextColor(), fraction));
363c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        } else {
3648a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            mTextPaint.setColor(getCurrentCollapsedTextColor());
365c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
366c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
36723caabcc1fc96e3756d0c354b49290467080de6aChris Banes        mTextPaint.setShadowLayer(
36823caabcc1fc96e3756d0c354b49290467080de6aChris Banes                lerp(mExpandedShadowRadius, mCollapsedShadowRadius, fraction, null),
36923caabcc1fc96e3756d0c354b49290467080de6aChris Banes                lerp(mExpandedShadowDx, mCollapsedShadowDx, fraction, null),
37023caabcc1fc96e3756d0c354b49290467080de6aChris Banes                lerp(mExpandedShadowDy, mCollapsedShadowDy, fraction, null),
37123caabcc1fc96e3756d0c354b49290467080de6aChris Banes                blendColors(mExpandedShadowColor, mCollapsedShadowColor, fraction));
37223caabcc1fc96e3756d0c354b49290467080de6aChris Banes
373c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        ViewCompat.postInvalidateOnAnimation(mView);
374c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
375c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
3768a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    @ColorInt
3778a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    private int getCurrentExpandedTextColor() {
3788a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        if (mState != null) {
3798a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            return mExpandedTextColor.getColorForState(mState, 0);
3808a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        } else {
3818a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            return mExpandedTextColor.getDefaultColor();
3828a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        }
3838a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    }
3848a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes
3858a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    @ColorInt
3868a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    private int getCurrentCollapsedTextColor() {
3878a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        if (mState != null) {
3888a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            return mCollapsedTextColor.getColorForState(mState, 0);
3898a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        } else {
3908a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes            return mCollapsedTextColor.getDefaultColor();
3918a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes        }
3928a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    }
3938a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes
3945c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private void calculateBaseOffsets() {
39537430e835e22a791b83b01befab44ead852bf555Chris Banes        final float currentTextSize = mCurrentTextSize;
39637430e835e22a791b83b01befab44ead852bf555Chris Banes
397c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        // We then calculate the collapsed text size, using the same logic
39837430e835e22a791b83b01befab44ead852bf555Chris Banes        calculateUsingTextSize(mCollapsedTextSize);
3995c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        float width = mTextToDraw != null ?
4005c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0;
4015c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        final int collapsedAbsGravity = GravityCompat.getAbsoluteGravity(mCollapsedTextGravity,
4025c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR);
4035c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        switch (collapsedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
404c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.BOTTOM:
4055c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawY = mCollapsedBounds.bottom;
406c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
407c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.TOP:
4085c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawY = mCollapsedBounds.top - mTextPaint.ascent();
409c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
410c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.CENTER_VERTICAL:
411c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            default:
412c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
413c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                float textOffset = (textHeight / 2) - mTextPaint.descent();
4145c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawY = mCollapsedBounds.centerY() + textOffset;
4155c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4165c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        }
417f828412cf077314f63e9d989c889f69275a28b19Chris Banes        switch (collapsedAbsGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
4185c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.CENTER_HORIZONTAL:
4195c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawX = mCollapsedBounds.centerX() - (width / 2);
4205c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4215c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.RIGHT:
4225c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawX = mCollapsedBounds.right - width;
4235c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4245c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.LEFT:
4255c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            default:
4265c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mCollapsedDrawX = mCollapsedBounds.left;
427c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
428c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
429c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
43037430e835e22a791b83b01befab44ead852bf555Chris Banes        calculateUsingTextSize(mExpandedTextSize);
4315c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        width = mTextToDraw != null
4325c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                ? mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()) : 0;
4335c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        final int expandedAbsGravity = GravityCompat.getAbsoluteGravity(mExpandedTextGravity,
4345c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mIsRtl ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR);
4355c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        switch (expandedAbsGravity & Gravity.VERTICAL_GRAVITY_MASK) {
436c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.BOTTOM:
4375c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawY = mExpandedBounds.bottom;
438c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
439c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.TOP:
4405c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawY = mExpandedBounds.top - mTextPaint.ascent();
441c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
442c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            case Gravity.CENTER_VERTICAL:
443c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            default:
444c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                float textHeight = mTextPaint.descent() - mTextPaint.ascent();
445c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                float textOffset = (textHeight / 2) - mTextPaint.descent();
4465c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawY = mExpandedBounds.centerY() + textOffset;
4475c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4485c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        }
449f828412cf077314f63e9d989c889f69275a28b19Chris Banes        switch (expandedAbsGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
4505c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.CENTER_HORIZONTAL:
4515c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawX = mExpandedBounds.centerX() - (width / 2);
4525c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4535c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.RIGHT:
4545c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawX = mExpandedBounds.right - width;
4555c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                break;
4565c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            case Gravity.LEFT:
4575c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            default:
4585c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                mExpandedDrawX = mExpandedBounds.left;
459c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                break;
460c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
461c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
462c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        // The bounds have changed so we need to clear the texture
463c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        clearTexture();
46437430e835e22a791b83b01befab44ead852bf555Chris Banes        // Now reset the text size back to the original
46537430e835e22a791b83b01befab44ead852bf555Chris Banes        setInterpolatedTextSize(currentTextSize);
466c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
467c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
4685c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private void interpolateBounds(float fraction) {
4695c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds.left = lerp(mExpandedBounds.left, mCollapsedBounds.left,
4705c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                fraction, mPositionInterpolator);
4715c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds.top = lerp(mExpandedDrawY, mCollapsedDrawY,
4725c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                fraction, mPositionInterpolator);
4735c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds.right = lerp(mExpandedBounds.right, mCollapsedBounds.right,
4745c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                fraction, mPositionInterpolator);
4755c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mCurrentBounds.bottom = lerp(mExpandedBounds.bottom, mCollapsedBounds.bottom,
4765c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                fraction, mPositionInterpolator);
4775c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    }
4785c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes
479c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    public void draw(Canvas canvas) {
480c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        final int saveCount = canvas.save();
481c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
48266a875e525700de5eed715a52a3329ab5a310d96Chris Banes        if (mTextToDraw != null && mDrawTitle) {
4835c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            float x = mCurrentDrawX;
4845c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            float y = mCurrentDrawY;
485c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
486c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            final boolean drawTexture = mUseTexture && mExpandedTitleTexture != null;
487c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
488c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            final float ascent;
489c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            final float descent;
490c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (drawTexture) {
491c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                ascent = mTextureAscent * mScale;
492c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                descent = mTextureDescent * mScale;
493c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            } else {
494c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                ascent = mTextPaint.ascent() * mScale;
495c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                descent = mTextPaint.descent() * mScale;
496c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
497c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
498c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (DEBUG_DRAW) {
499c9a859537b0871f84afeeb706a5b425fe3f2b4ddAurimas Liutikas                // Just a debug tool, which drawn a magenta rect in the text bounds
5005c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes                canvas.drawRect(mCurrentBounds.left, y + ascent, mCurrentBounds.right, y + descent,
501c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                        DEBUG_DRAW_PAINT);
502c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
503c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
504c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (drawTexture) {
505c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                y += ascent;
506c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
507c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
508c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (mScale != 1f) {
509c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                canvas.scale(mScale, mScale, x, y);
510c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
511c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
512c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (drawTexture) {
513c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                // If we should use a texture, draw it instead of text
514c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                canvas.drawBitmap(mExpandedTitleTexture, x, y, mTexturePaint);
515c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            } else {
516c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                canvas.drawText(mTextToDraw, 0, mTextToDraw.length(), x, y, mTextPaint);
517c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
518c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
519c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
520c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        canvas.restoreToCount(saveCount);
521c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
522c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
523fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes    private boolean calculateIsRtl(CharSequence text) {
524fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes        final boolean defaultIsRtl = ViewCompat.getLayoutDirection(mView)
525fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes                == ViewCompat.LAYOUT_DIRECTION_RTL;
526fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes        return (defaultIsRtl
527fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes                ? TextDirectionHeuristicsCompat.FIRSTSTRONG_RTL
528fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes                : TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR).isRtl(text, 0, text.length());
529fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes    }
530fef6dbf449d6e2992872a725170c5fcb4aa16975Chris Banes
53137430e835e22a791b83b01befab44ead852bf555Chris Banes    private void setInterpolatedTextSize(float textSize) {
53237430e835e22a791b83b01befab44ead852bf555Chris Banes        calculateUsingTextSize(textSize);
53337430e835e22a791b83b01befab44ead852bf555Chris Banes
53437430e835e22a791b83b01befab44ead852bf555Chris Banes        // Use our texture if the scale isn't 1.0
53537430e835e22a791b83b01befab44ead852bf555Chris Banes        mUseTexture = USE_SCALING_TEXTURE && mScale != 1f;
53637430e835e22a791b83b01befab44ead852bf555Chris Banes
53737430e835e22a791b83b01befab44ead852bf555Chris Banes        if (mUseTexture) {
53837430e835e22a791b83b01befab44ead852bf555Chris Banes            // Make sure we have an expanded texture if needed
53937430e835e22a791b83b01befab44ead852bf555Chris Banes            ensureExpandedTexture();
54037430e835e22a791b83b01befab44ead852bf555Chris Banes        }
54137430e835e22a791b83b01befab44ead852bf555Chris Banes
54237430e835e22a791b83b01befab44ead852bf555Chris Banes        ViewCompat.postInvalidateOnAnimation(mView);
54337430e835e22a791b83b01befab44ead852bf555Chris Banes    }
54437430e835e22a791b83b01befab44ead852bf555Chris Banes
545ad1b0e82100ee31e70040d77bfa4d847b2bf0864Aurimas Liutikas    private boolean areTypefacesDifferent(Typeface first, Typeface second) {
546ad1b0e82100ee31e70040d77bfa4d847b2bf0864Aurimas Liutikas        return (first != null && !first.equals(second)) || (first == null && second != null);
547ad1b0e82100ee31e70040d77bfa4d847b2bf0864Aurimas Liutikas    }
548ad1b0e82100ee31e70040d77bfa4d847b2bf0864Aurimas Liutikas
54937430e835e22a791b83b01befab44ead852bf555Chris Banes    private void calculateUsingTextSize(final float textSize) {
550c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mText == null) return;
551c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
552b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes        final float collapsedWidth = mCollapsedBounds.width();
553b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes        final float expandedWidth = mExpandedBounds.width();
554b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes
555a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        final float availableWidth;
556a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        final float newTextSize;
557a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        boolean updateDrawText = false;
558c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
559c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (isClose(textSize, mCollapsedTextSize)) {
560a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            newTextSize = mCollapsedTextSize;
561a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            mScale = 1f;
562ad1b0e82100ee31e70040d77bfa4d847b2bf0864Aurimas Liutikas            if (areTypefacesDifferent(mCurrentTypeface, mCollapsedTypeface)) {
563d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                mCurrentTypeface = mCollapsedTypeface;
564d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                updateDrawText = true;
565d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            }
566b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes            availableWidth = collapsedWidth;
567c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        } else {
568a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            newTextSize = mExpandedTextSize;
569ad1b0e82100ee31e70040d77bfa4d847b2bf0864Aurimas Liutikas            if (areTypefacesDifferent(mCurrentTypeface, mExpandedTypeface)) {
570d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                mCurrentTypeface = mExpandedTypeface;
571d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes                updateDrawText = true;
572d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            }
573c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            if (isClose(textSize, mExpandedTextSize)) {
574c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                // If we're close to the expanded text size, snap to it and use a scale of 1
575c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                mScale = 1f;
576c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            } else {
577c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                // Else, we'll scale down from the expanded text size
578c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                mScale = textSize / mExpandedTextSize;
579c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
580b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes
581b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes            final float textSizeRatio = mCollapsedTextSize / mExpandedTextSize;
582b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes            // This is the size of the expanded bounds when it is scaled to match the
583b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes            // collapsed text size
584b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes            final float scaledDownWidth = expandedWidth * textSizeRatio;
585b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes
586b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes            if (scaledDownWidth > collapsedWidth) {
587b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes                // If the scaled down size is larger than the actual collapsed width, we need to
588b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes                // cap the available width so that when the expanded text scales down, it matches
589b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes                // the collapsed width
590b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes                availableWidth = Math.min(collapsedWidth / textSizeRatio, expandedWidth);
591b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes            } else {
592b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes                // Otherwise we'll just use the expanded width
593b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes                availableWidth = expandedWidth;
594b2535ce06945e0b3b95590819b8e6467ea3ee29dChris Banes            }
595c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
596c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
597a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        if (availableWidth > 0) {
598d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            updateDrawText = (mCurrentTextSize != newTextSize) || mBoundsChanged || updateDrawText;
599a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            mCurrentTextSize = newTextSize;
6009995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes            mBoundsChanged = false;
601a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        }
602a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
603a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        if (mTextToDraw == null || updateDrawText) {
604a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            mTextPaint.setTextSize(mCurrentTextSize);
605d29e79fa09f428c510d1d9ca758dbda35a323f71Chris Banes            mTextPaint.setTypeface(mCurrentTypeface);
606973218c60005eb672a27b027403c9231d5d21303Chris Banes            // Use linear text scaling if we're scaling the canvas
607973218c60005eb672a27b027403c9231d5d21303Chris Banes            mTextPaint.setLinearText(mScale != 1f);
608a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
609c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // If we don't currently have text to draw, or the text size has changed, ellipsize...
610c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            final CharSequence title = TextUtils.ellipsize(mText, mTextPaint,
611c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                    availableWidth, TextUtils.TruncateAt.END);
61237430e835e22a791b83b01befab44ead852bf555Chris Banes            if (!TextUtils.equals(title, mTextToDraw)) {
613c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                mTextToDraw = title;
61437430e835e22a791b83b01befab44ead852bf555Chris Banes                mIsRtl = calculateIsRtl(mTextToDraw);
615c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            }
616c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
617c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
618c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
619c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private void ensureExpandedTexture() {
620c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mExpandedTitleTexture != null || mExpandedBounds.isEmpty()
621c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes                || TextUtils.isEmpty(mTextToDraw)) {
622c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            return;
623c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
624c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
62523caabcc1fc96e3756d0c354b49290467080de6aChris Banes        calculateOffsets(0f);
6265c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mTextureAscent = mTextPaint.ascent();
6275c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        mTextureDescent = mTextPaint.descent();
628c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
629c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        final int w = Math.round(mTextPaint.measureText(mTextToDraw, 0, mTextToDraw.length()));
6305c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes        final int h = Math.round(mTextureDescent - mTextureAscent);
631c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
6326fa3c92dcaf584ac56bff4c0b52b7b1ac379592eChris Banes        if (w <= 0 || h <= 0) {
633c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            return; // If the width or height are 0, return
634c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
635c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
636c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        mExpandedTitleTexture = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
637c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
638c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        Canvas c = new Canvas(mExpandedTitleTexture);
639c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        c.drawText(mTextToDraw, 0, mTextToDraw.length(), 0, h - mTextPaint.descent(), mTextPaint);
640c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
641c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mTexturePaint == null) {
642c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // Make sure we have a paint
6435c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            mTexturePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
644c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
645c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
646c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
6471bf6093c6dab532bc57c75767470afa3cbbe0384Chris Banes    public void recalculate() {
6481bf6093c6dab532bc57c75767470afa3cbbe0384Chris Banes        if (mView.getHeight() > 0 && mView.getWidth() > 0) {
649c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // If we've already been laid out, calculate everything now otherwise we'll wait
650c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            // until a layout
6515c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            calculateBaseOffsets();
6525c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes            calculateCurrentOffsets();
653c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
654c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
655c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
656c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    /**
657c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * Set the title to display
658c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     *
659c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * @param text
660c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     */
661c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    void setText(CharSequence text) {
662c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (text == null || !text.equals(mText)) {
663c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mText = text;
664d9d3addb4628537f68348d40346765b7f407cf79Chris Banes            mTextToDraw = null;
665c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            clearTexture();
666c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            recalculate();
667c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
668c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
669c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
670c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    CharSequence getText() {
671c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mText;
672c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
673c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
674c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private void clearTexture() {
675c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (mExpandedTitleTexture != null) {
676c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTitleTexture.recycle();
677c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            mExpandedTitleTexture = null;
678c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
679c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
680c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
681c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    /**
682c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * Returns true if {@code value} is 'close' to it's closest decimal value. Close is currently
683c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * defined as it's difference being < 0.001.
684c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     */
685c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static boolean isClose(float value, float targetValue) {
686c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return Math.abs(value - targetValue) < 0.001f;
687c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
688c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
6898a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    ColorStateList getExpandedTextColor() {
690c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mExpandedTextColor;
691c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
692c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
6938a40d691aa783c2e85298e895a3e51e98606aa85Chris Banes    ColorStateList getCollapsedTextColor() {
694c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return mCollapsedTextColor;
695c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
696c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
697c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    /**
698c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * Blend {@code color1} and {@code color2} using the given ratio.
699c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     *
700c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     * @param ratio of which to blend. 0.0 will return {@code color1}, 0.5 will give an even blend,
701c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     *              1.0 will return {@code color2}.
702c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes     */
703c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    private static int blendColors(int color1, int color2, float ratio) {
704c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        final float inverseRatio = 1f - ratio;
705c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio);
706c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio);
707c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio);
708c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio);
709c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return Color.argb((int) a, (int) r, (int) g, (int) b);
710c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
711c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes
7125c7d7bbceeaebc46dc2e4b2135cc330414f716d4Chris Banes    private static float lerp(float startValue, float endValue, float fraction,
713c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            Interpolator interpolator) {
714c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        if (interpolator != null) {
715c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes            fraction = interpolator.getInterpolation(fraction);
716c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        }
717c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes        return AnimationUtils.lerp(startValue, endValue, fraction);
718c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes    }
7199995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes
7209995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes    private static boolean rectEquals(Rect r, int left, int top, int right, int bottom) {
7219995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes        return !(r.left != left || r.top != top || r.right != right || r.bottom != bottom);
7229995f7bdb02dbe16155661545fd64046ad3d56c2Chris Banes    }
723c5b63021dfda1dd155775a0b4845c5ca3c70b1b0Chris Banes}
724