19840efe3dbdc7026521da8576574c55120782f6cChris Banes/*
29840efe3dbdc7026521da8576574c55120782f6cChris Banes * Copyright (C) 2015 The Android Open Source Project
39840efe3dbdc7026521da8576574c55120782f6cChris Banes *
49840efe3dbdc7026521da8576574c55120782f6cChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
59840efe3dbdc7026521da8576574c55120782f6cChris Banes * you may not use this file except in compliance with the License.
69840efe3dbdc7026521da8576574c55120782f6cChris Banes * You may obtain a copy of the License at
79840efe3dbdc7026521da8576574c55120782f6cChris Banes *
89840efe3dbdc7026521da8576574c55120782f6cChris Banes *      http://www.apache.org/licenses/LICENSE-2.0
99840efe3dbdc7026521da8576574c55120782f6cChris Banes *
109840efe3dbdc7026521da8576574c55120782f6cChris Banes * Unless required by applicable law or agreed to in writing, software
119840efe3dbdc7026521da8576574c55120782f6cChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
129840efe3dbdc7026521da8576574c55120782f6cChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139840efe3dbdc7026521da8576574c55120782f6cChris Banes * See the License for the specific language governing permissions and
149840efe3dbdc7026521da8576574c55120782f6cChris Banes * limitations under the License.
159840efe3dbdc7026521da8576574c55120782f6cChris Banes */
169840efe3dbdc7026521da8576574c55120782f6cChris Banes
179840efe3dbdc7026521da8576574c55120782f6cChris Banespackage android.support.design.widget;
189840efe3dbdc7026521da8576574c55120782f6cChris Banes
199840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.annotation.TargetApi;
209840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.content.Context;
219840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.content.res.ColorStateList;
22a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banesimport android.content.res.Resources;
239840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.content.res.TypedArray;
249840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.PorterDuff;
259840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.Rect;
269840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.drawable.Drawable;
279840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.os.Build;
287a13c8489daca7915623dd673df49de2d1a0bf30Chris Banesimport android.support.annotation.ColorInt;
291711e8729c1b901b73f530e87b7c9cc9370f33beChris Banesimport android.support.annotation.DrawableRes;
30a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banesimport android.support.annotation.IntDef;
31d9cbe69a6661315238d856abc22578d03666f63bChris Banesimport android.support.annotation.NonNull;
329840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.annotation.Nullable;
339840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.design.R;
340ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Weiimport android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
35a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banesimport android.support.v4.content.res.ConfigurationHelper;
36b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.support.v4.view.ViewCompat;
371711e8729c1b901b73f530e87b7c9cc9370f33beChris Banesimport android.support.v7.widget.AppCompatDrawableManager;
381711e8729c1b901b73f530e87b7c9cc9370f33beChris Banesimport android.support.v7.widget.AppCompatImageHelper;
399840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.util.AttributeSet;
40097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banesimport android.util.Log;
41d5d3213d80be767482a89bea6d073006582e2bccChris Banesimport android.view.MotionEvent;
4214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banesimport android.view.View;
439840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.widget.ImageView;
449840efe3dbdc7026521da8576574c55120782f6cChris Banes
45a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banesimport java.lang.annotation.Retention;
46a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banesimport java.lang.annotation.RetentionPolicy;
47a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport java.util.List;
48b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
499840efe3dbdc7026521da8576574c55120782f6cChris Banes/**
509840efe3dbdc7026521da8576574c55120782f6cChris Banes * Floating action buttons are used for a special type of promoted action. They are distinguished
5114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes * by a circled icon floating above the UI and have special motion behaviors related to morphing,
529840efe3dbdc7026521da8576574c55120782f6cChris Banes * launching, and the transferring anchor point.
539840efe3dbdc7026521da8576574c55120782f6cChris Banes *
549fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>Floating action buttons come in two sizes: the default and the mini. The size can be
559fb154338a62edc2c57dc036895199d6f1769400Chris Banes * controlled with the {@code fabSize} attribute.</p>
569fb154338a62edc2c57dc036895199d6f1769400Chris Banes *
579fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>As this class descends from {@link ImageView}, you can control the icon which is displayed
589fb154338a62edc2c57dc036895199d6f1769400Chris Banes * via {@link #setImageDrawable(Drawable)}.</p>
599fb154338a62edc2c57dc036895199d6f1769400Chris Banes *
609fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>The background color of this view defaults to the your theme's {@code colorAccent}. If you
619fb154338a62edc2c57dc036895199d6f1769400Chris Banes * wish to change this at runtime then you can do so via
629fb154338a62edc2c57dc036895199d6f1769400Chris Banes * {@link #setBackgroundTintList(ColorStateList)}.</p>
639840efe3dbdc7026521da8576574c55120782f6cChris Banes */
64b7f9224b1495db47eb8fd813b5912250e900770aChris Banes@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
65fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banespublic class FloatingActionButton extends VisibilityAwareImageButton {
669840efe3dbdc7026521da8576574c55120782f6cChris Banes
67097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    private static final String LOG_TAG = "FloatingActionButton";
68097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes
690ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    /**
700ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * Callback to be invoked when the visibility of a FloatingActionButton changes.
710ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     */
720ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    public abstract static class OnVisibilityChangedListener {
730ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        /**
740ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * Called when a FloatingActionButton has been
750ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * {@link #show(OnVisibilityChangedListener) shown}.
760ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         *
770ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * @param fab the FloatingActionButton that was shown.
780ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         */
790ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        public void onShown(FloatingActionButton fab) {}
800ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
810ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        /**
820ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * Called when a FloatingActionButton has been
830ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * {@link #hide(OnVisibilityChangedListener) hidden}.
840ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         *
850ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * @param fab the FloatingActionButton that was hidden.
860ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         */
870ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        public void onHidden(FloatingActionButton fab) {}
880ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    }
890ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
909840efe3dbdc7026521da8576574c55120782f6cChris Banes    // These values must match those in the attrs declaration
91a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
92a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    /**
93a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * The mini sized button. Will always been smaller than {@link #SIZE_NORMAL}.
94a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     *
95a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * @see #setSize(int)
96a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     */
97a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    public static final int SIZE_MINI = 1;
98a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
99a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    /**
100a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * The normal sized button. Will always been larger than {@link #SIZE_MINI}.
101a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     *
102a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * @see #setSize(int)
103a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     */
104a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    public static final int SIZE_NORMAL = 0;
105a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
106a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    /**
107a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * Size which will change based on the window size. For small sized windows
108a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * (largest screen dimension < 470dp) this will select a small sized button, and for
109a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * larger sized windows it will select a larger size.
110a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     *
111a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * @see #setSize(int)
112a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     */
113a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    public static final int SIZE_AUTO = -1;
114a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
115a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    /**
116a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * The switch point for the largest screen edge where SIZE_AUTO switches from mini to normal.
117a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     */
118a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    private static final int AUTO_MINI_LARGEST_SCREEN_WIDTH = 470;
119a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
120a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    /** @hide */
121a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    @Retention(RetentionPolicy.SOURCE)
122a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    @IntDef({SIZE_MINI, SIZE_NORMAL, SIZE_AUTO})
123a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    public @interface Size {}
1249840efe3dbdc7026521da8576574c55120782f6cChris Banes
1259840efe3dbdc7026521da8576574c55120782f6cChris Banes    private ColorStateList mBackgroundTint;
1269840efe3dbdc7026521da8576574c55120782f6cChris Banes    private PorterDuff.Mode mBackgroundTintMode;
1279840efe3dbdc7026521da8576574c55120782f6cChris Banes
128cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes    private int mBorderWidth;
1299840efe3dbdc7026521da8576574c55120782f6cChris Banes    private int mRippleColor;
1309840efe3dbdc7026521da8576574c55120782f6cChris Banes    private int mSize;
131d9cbe69a6661315238d856abc22578d03666f63bChris Banes    private int mImagePadding;
132a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    private int mMaxImageSize;
1339840efe3dbdc7026521da8576574c55120782f6cChris Banes
1346d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    private boolean mCompatPadding;
135cdc736866534c604c4015c78371ade52bb6d52dfChris Banes    private final Rect mShadowPadding = new Rect();
136df23c413315751774dd83a1d930ae6e83bc21d55Chris Banes    private final Rect mTouchArea = new Rect();
1379840efe3dbdc7026521da8576574c55120782f6cChris Banes
1381711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes    private AppCompatImageHelper mImageHelper;
1391711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes
140d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes    private FloatingActionButtonImpl mImpl;
1419840efe3dbdc7026521da8576574c55120782f6cChris Banes
1429840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context) {
1439840efe3dbdc7026521da8576574c55120782f6cChris Banes        this(context, null);
1449840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1459840efe3dbdc7026521da8576574c55120782f6cChris Banes
1469840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context, AttributeSet attrs) {
147b3ba94bf8b5cdae24e5a09a83813d72f2e2d8c1aChris Banes        this(context, attrs, 0);
1489840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1499840efe3dbdc7026521da8576574c55120782f6cChris Banes
1509840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
1519840efe3dbdc7026521da8576574c55120782f6cChris Banes        super(context, attrs, defStyleAttr);
1529840efe3dbdc7026521da8576574c55120782f6cChris Banes
153809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes        ThemeUtils.checkAppCompatTheme(context);
154809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes
1559840efe3dbdc7026521da8576574c55120782f6cChris Banes        TypedArray a = context.obtainStyledAttributes(attrs,
1569840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton, defStyleAttr,
1579840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.style.Widget_Design_FloatingActionButton);
1589840efe3dbdc7026521da8576574c55120782f6cChris Banes        mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint);
1599840efe3dbdc7026521da8576574c55120782f6cChris Banes        mBackgroundTintMode = parseTintMode(a.getInt(
1609840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
1619840efe3dbdc7026521da8576574c55120782f6cChris Banes        mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
162a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_AUTO);
163cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes        mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0);
1649840efe3dbdc7026521da8576574c55120782f6cChris Banes        final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
1659840efe3dbdc7026521da8576574c55120782f6cChris Banes        final float pressedTranslationZ = a.getDimension(
1669840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton_pressedTranslationZ, 0f);
1676d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes        mCompatPadding = a.getBoolean(R.styleable.FloatingActionButton_useCompatPadding, false);
1689840efe3dbdc7026521da8576574c55120782f6cChris Banes        a.recycle();
1699840efe3dbdc7026521da8576574c55120782f6cChris Banes
1701711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes        mImageHelper = new AppCompatImageHelper(this, AppCompatDrawableManager.get());
1711711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes        mImageHelper.loadFromAttributes(attrs, defStyleAttr);
1721711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes
173a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        mMaxImageSize = (int) getResources().getDimension(R.dimen.design_fab_image_size);
1749840efe3dbdc7026521da8576574c55120782f6cChris Banes
175d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().setBackgroundDrawable(mBackgroundTint, mBackgroundTintMode,
176097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes                mRippleColor, mBorderWidth);
177d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().setElevation(elevation);
178d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().setPressedTranslationZ(pressedTranslationZ);
1799840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1809840efe3dbdc7026521da8576574c55120782f6cChris Banes
1819840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
1829840efe3dbdc7026521da8576574c55120782f6cChris Banes    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1839840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int preferredSize = getSizeDimension();
1849840efe3dbdc7026521da8576574c55120782f6cChris Banes
185a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        mImagePadding = (preferredSize - mMaxImageSize) / 2;
186a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        getImpl().updatePadding();
187a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
1889840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec);
1899840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec);
1909840efe3dbdc7026521da8576574c55120782f6cChris Banes
1919840efe3dbdc7026521da8576574c55120782f6cChris Banes        // As we want to stay circular, we set both dimensions to be the
1929840efe3dbdc7026521da8576574c55120782f6cChris Banes        // smallest resolved dimension
1939840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int d = Math.min(w, h);
1949840efe3dbdc7026521da8576574c55120782f6cChris Banes
1959840efe3dbdc7026521da8576574c55120782f6cChris Banes        // We add the shadow's padding to the measured dimension
1969840efe3dbdc7026521da8576574c55120782f6cChris Banes        setMeasuredDimension(
1979840efe3dbdc7026521da8576574c55120782f6cChris Banes                d + mShadowPadding.left + mShadowPadding.right,
1989840efe3dbdc7026521da8576574c55120782f6cChris Banes                d + mShadowPadding.top + mShadowPadding.bottom);
1999840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2009840efe3dbdc7026521da8576574c55120782f6cChris Banes
2019840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2029840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Set the ripple color for this {@link FloatingActionButton}.
2039840efe3dbdc7026521da8576574c55120782f6cChris Banes     * <p>
2049840efe3dbdc7026521da8576574c55120782f6cChris Banes     * When running on devices with KitKat or below, we draw a fill rather than a ripple.
2059840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2069840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param color ARGB color to use for the ripple.
2076d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     *
2086d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @attr ref android.support.design.R.styleable#FloatingActionButton_rippleColor
2099840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2107a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes    public void setRippleColor(@ColorInt int color) {
2119840efe3dbdc7026521da8576574c55120782f6cChris Banes        if (mRippleColor != color) {
2129840efe3dbdc7026521da8576574c55120782f6cChris Banes            mRippleColor = color;
213d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes            getImpl().setRippleColor(color);
2149840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
2159840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2169840efe3dbdc7026521da8576574c55120782f6cChris Banes
2179840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2189840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Return the tint applied to the background drawable, if specified.
2199840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2209840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @return the tint applied to the background drawable
2219840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @see #setBackgroundTintList(ColorStateList)
2229840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2239840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Nullable
2249840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
2259840efe3dbdc7026521da8576574c55120782f6cChris Banes    public ColorStateList getBackgroundTintList() {
2269840efe3dbdc7026521da8576574c55120782f6cChris Banes        return mBackgroundTint;
2279840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2289840efe3dbdc7026521da8576574c55120782f6cChris Banes
2299840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2309840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Applies a tint to the background drawable. Does not modify the current tint
2319840efe3dbdc7026521da8576574c55120782f6cChris Banes     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2329840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2339840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param tint the tint to apply, may be {@code null} to clear tint
2349840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2359840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void setBackgroundTintList(@Nullable ColorStateList tint) {
2367a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes        if (mBackgroundTint != tint) {
2377a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes            mBackgroundTint = tint;
238d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes            getImpl().setBackgroundTintList(tint);
2397a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes        }
2409840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2419840efe3dbdc7026521da8576574c55120782f6cChris Banes
2429840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2439840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Return the blending mode used to apply the tint to the background
2449840efe3dbdc7026521da8576574c55120782f6cChris Banes     * drawable, if specified.
2459840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2469840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @return the blending mode used to apply the tint to the background
2479840efe3dbdc7026521da8576574c55120782f6cChris Banes     *         drawable
2489840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @see #setBackgroundTintMode(PorterDuff.Mode)
2499840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2509840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Nullable
2519840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
2529840efe3dbdc7026521da8576574c55120782f6cChris Banes    public PorterDuff.Mode getBackgroundTintMode() {
2539840efe3dbdc7026521da8576574c55120782f6cChris Banes        return mBackgroundTintMode;
2549840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2559840efe3dbdc7026521da8576574c55120782f6cChris Banes
2569840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2579840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Specifies the blending mode used to apply the tint specified by
2589840efe3dbdc7026521da8576574c55120782f6cChris Banes     * {@link #setBackgroundTintList(ColorStateList)}} to the background
2599840efe3dbdc7026521da8576574c55120782f6cChris Banes     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2609840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2619840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param tintMode the blending mode used to apply the tint, may be
2629840efe3dbdc7026521da8576574c55120782f6cChris Banes     *                 {@code null} to clear tint
2639840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2649840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
2657a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes        if (mBackgroundTintMode != tintMode) {
2667a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes            mBackgroundTintMode = tintMode;
267d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes            getImpl().setBackgroundTintMode(tintMode);
2687a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes        }
2699840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2709840efe3dbdc7026521da8576574c55120782f6cChris Banes
2719840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
272097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    public void setBackgroundDrawable(Drawable background) {
273097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes        Log.i(LOG_TAG, "Setting a custom background is not supported.");
274097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    }
275097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes
276097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    @Override
277097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    public void setBackgroundResource(int resid) {
278097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes        Log.i(LOG_TAG, "Setting a custom background is not supported.");
279097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    }
280097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes
281097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    @Override
282097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    public void setBackgroundColor(int color) {
283097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes        Log.i(LOG_TAG, "Setting a custom background is not supported.");
284be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    }
285be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
2861711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes    @Override
2871711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes    public void setImageResource(@DrawableRes int resId) {
2881711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes        // Intercept this call and instead retrieve the Drawable via the image helper
2891711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes        mImageHelper.setImageResource(resId);
2901711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes    }
2911711e8729c1b901b73f530e87b7c9cc9370f33beChris Banes
292be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    /**
293be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     * Shows the button.
2948c05e5f52fbc790b745e768398d9e69d6b9d9ee1Chris Banes     * <p>This method will animate the button show if the view has already been laid out.</p>
295be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     */
296be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    public void show() {
297fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes        show(null);
2980ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    }
2990ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
3000ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    /**
3010ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * Shows the button.
3028c05e5f52fbc790b745e768398d9e69d6b9d9ee1Chris Banes     * <p>This method will animate the button show if the view has already been laid out.</p>
3030ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     *
3040ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * @param listener the listener to notify when this view is shown
3050ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     */
3060ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    public void show(@Nullable final OnVisibilityChangedListener listener) {
307fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes        show(listener, true);
308fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes    }
309fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes
310fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes    private void show(OnVisibilityChangedListener listener, boolean fromUser) {
311d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().show(wrapOnVisibilityChangedListener(listener), fromUser);
312be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    }
313be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
314be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    /**
315be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     * Hides the button.
316be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     * <p>This method will animate the button hide if the view has already been laid out.</p>
317be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     */
318be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    public void hide() {
319fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes        hide(null);
3200ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    }
3210ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
3220ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    /**
3230ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * Hides the button.
3240ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * <p>This method will animate the button hide if the view has already been laid out.</p>
3250ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     *
3260ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * @param listener the listener to notify when this view is hidden
3270ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     */
3280ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    public void hide(@Nullable OnVisibilityChangedListener listener) {
329fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes        hide(listener, true);
330fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes    }
331fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes
332fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes    private void hide(@Nullable OnVisibilityChangedListener listener, boolean fromUser) {
333d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().hide(wrapOnVisibilityChangedListener(listener), fromUser);
3340ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    }
3350ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
3366d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    /**
3376d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * Set whether FloatingActionButton should add inner padding on platforms Lollipop and after,
3386d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * to ensure consistent dimensions on all platforms.
3396d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     *
3406d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @param useCompatPadding true if FloatingActionButton is adding inner padding on platforms
3416d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     *                         Lollipop and after, to ensure consistent dimensions on all platforms.
3426d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     *
3436d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @attr ref android.support.design.R.styleable#FloatingActionButton_useCompatPadding
3446d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @see #getUseCompatPadding()
3456d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     */
3466d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    public void setUseCompatPadding(boolean useCompatPadding) {
3476d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes        if (mCompatPadding != useCompatPadding) {
3486d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes            mCompatPadding = useCompatPadding;
349d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes            getImpl().onCompatShadowChanged();
3506d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes        }
3516d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    }
3526d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes
3536d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    /**
3546d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * Returns whether FloatingActionButton will add inner padding on platforms Lollipop and after.
3556d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     *
3566d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @return true if FloatingActionButton is adding inner padding on platforms Lollipop and after,
3576d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * to ensure consistent dimensions on all platforms.
3586d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     *
3596d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @attr ref android.support.design.R.styleable#FloatingActionButton_useCompatPadding
3606d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @see #setUseCompatPadding(boolean)
3616d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     */
3626d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    public boolean getUseCompatPadding() {
3636d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes        return mCompatPadding;
3646d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    }
3656d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes
366a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    /**
367a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * Sets the size of the button.
368a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     *
369a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * <p>The options relate to the options available on the material design specification.
370a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * {@link #SIZE_NORMAL} is larger than {@link #SIZE_MINI}. {@link #SIZE_AUTO} will choose
371a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * an appropriate size based on the screen size.</p>
372a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     *
373a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * @param size one of {@link #SIZE_NORMAL}, {@link #SIZE_MINI} or {@link #SIZE_AUTO}
374a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     *
375a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize
376a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     */
377a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    public void setSize(@Size int size) {
378a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        if (size != mSize) {
379a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes            mSize = size;
380a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes            requestLayout();
381a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        }
382a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    }
383a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
384a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    /**
385a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * Returns the chosen size for this button.
386a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     *
38756f0ad8d4fa234d2801971042ff1d71aba960e37Chris Banes     * @return one of {@link #SIZE_NORMAL}, {@link #SIZE_MINI} or {@link #SIZE_AUTO}
388a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     * @see #setSize(int)
389a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes     */
390a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    @Size
391a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    public int getSize() {
392a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        return mSize;
393a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    }
394a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
3950ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    @Nullable
3960ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    private InternalVisibilityChangedListener wrapOnVisibilityChangedListener(
3970ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            @Nullable final OnVisibilityChangedListener listener) {
3980ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        if (listener == null) {
3990ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            return null;
4000ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        }
4010ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
4020ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        return new InternalVisibilityChangedListener() {
4030ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            @Override
4040ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            public void onShown() {
4050ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei                listener.onShown(FloatingActionButton.this);
4060ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            }
4070ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
4080ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            @Override
4090ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            public void onHidden() {
4100ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei                listener.onHidden(FloatingActionButton.this);
4110ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            }
4120ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        };
4139840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
4149840efe3dbdc7026521da8576574c55120782f6cChris Banes
415a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    private int getSizeDimension() {
416a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        return getSizeDimension(mSize);
417a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    }
418a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes
419a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes    private int getSizeDimension(@Size final int size) {
420a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        final Resources res = getResources();
421a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes        switch (size) {
422a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes            case SIZE_AUTO:
423a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes                // If we're set to auto, grab the size from resources and refresh
424a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes                final int width = ConfigurationHelper.getScreenWidthDp(res);
425a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes                final int height = ConfigurationHelper.getScreenHeightDp(res);
426a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes                return Math.max(width, height) < AUTO_MINI_LARGEST_SCREEN_WIDTH
427a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes                        ? getSizeDimension(SIZE_MINI)
428a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes                        : getSizeDimension(SIZE_NORMAL);
4299840efe3dbdc7026521da8576574c55120782f6cChris Banes            case SIZE_MINI:
430a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes                return res.getDimensionPixelSize(R.dimen.design_fab_size_mini);
4319840efe3dbdc7026521da8576574c55120782f6cChris Banes            case SIZE_NORMAL:
4329840efe3dbdc7026521da8576574c55120782f6cChris Banes            default:
433a1de3eef9bb5ef90d00a23c65f13e1fc83254455Chris Banes                return res.getDimensionPixelSize(R.dimen.design_fab_size_normal);
4349840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
4359840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
4369840efe3dbdc7026521da8576574c55120782f6cChris Banes
4379840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
438d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    protected void onAttachedToWindow() {
439d9770e12c8ff2d4417700492c6616572be897e93Chris Banes        super.onAttachedToWindow();
440d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().onAttachedToWindow();
441d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    }
442d9770e12c8ff2d4417700492c6616572be897e93Chris Banes
443d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    @Override
444d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    protected void onDetachedFromWindow() {
445d9770e12c8ff2d4417700492c6616572be897e93Chris Banes        super.onDetachedFromWindow();
446d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().onDetachedFromWindow();
447d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    }
448d9770e12c8ff2d4417700492c6616572be897e93Chris Banes
449d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    @Override
4509840efe3dbdc7026521da8576574c55120782f6cChris Banes    protected void drawableStateChanged() {
4519840efe3dbdc7026521da8576574c55120782f6cChris Banes        super.drawableStateChanged();
452d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().onDrawableStateChanged(getDrawableState());
4539840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
4549840efe3dbdc7026521da8576574c55120782f6cChris Banes
4559840efe3dbdc7026521da8576574c55120782f6cChris Banes    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
4569840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
4579840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void jumpDrawablesToCurrentState() {
4589840efe3dbdc7026521da8576574c55120782f6cChris Banes        super.jumpDrawablesToCurrentState();
459d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().jumpDrawableToCurrentState();
4609840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
4619840efe3dbdc7026521da8576574c55120782f6cChris Banes
462d9cbe69a6661315238d856abc22578d03666f63bChris Banes    /**
463d9cbe69a6661315238d856abc22578d03666f63bChris Banes     * Return in {@code rect} the bounds of the actual floating action button content in view-local
464d9cbe69a6661315238d856abc22578d03666f63bChris Banes     * coordinates. This is defined as anything within any visible shadow.
465d9cbe69a6661315238d856abc22578d03666f63bChris Banes     *
466d9cbe69a6661315238d856abc22578d03666f63bChris Banes     * @return true if this view actually has been laid out and has a content rect, else false.
467d9cbe69a6661315238d856abc22578d03666f63bChris Banes     */
468d9cbe69a6661315238d856abc22578d03666f63bChris Banes    public boolean getContentRect(@NonNull Rect rect) {
469d9cbe69a6661315238d856abc22578d03666f63bChris Banes        if (ViewCompat.isLaidOut(this)) {
470d9cbe69a6661315238d856abc22578d03666f63bChris Banes            rect.set(0, 0, getWidth(), getHeight());
471d9cbe69a6661315238d856abc22578d03666f63bChris Banes            rect.left += mShadowPadding.left;
472d9cbe69a6661315238d856abc22578d03666f63bChris Banes            rect.top += mShadowPadding.top;
473d9cbe69a6661315238d856abc22578d03666f63bChris Banes            rect.right -= mShadowPadding.right;
474d9cbe69a6661315238d856abc22578d03666f63bChris Banes            rect.bottom -= mShadowPadding.bottom;
475d9cbe69a6661315238d856abc22578d03666f63bChris Banes            return true;
476d9cbe69a6661315238d856abc22578d03666f63bChris Banes        } else {
477d9cbe69a6661315238d856abc22578d03666f63bChris Banes            return false;
478d9cbe69a6661315238d856abc22578d03666f63bChris Banes        }
479d9cbe69a6661315238d856abc22578d03666f63bChris Banes    }
480d9cbe69a6661315238d856abc22578d03666f63bChris Banes
4813d81c900316412b4130bf40e0dd8b0d3d3a93e78Chris Banes    /**
4823d81c900316412b4130bf40e0dd8b0d3d3a93e78Chris Banes     * Returns the FloatingActionButton's background, minus any compatible shadow implementation.
4833d81c900316412b4130bf40e0dd8b0d3d3a93e78Chris Banes     */
4843d81c900316412b4130bf40e0dd8b0d3d3a93e78Chris Banes    @NonNull
4853d81c900316412b4130bf40e0dd8b0d3d3a93e78Chris Banes    public Drawable getContentBackground() {
486d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        return getImpl().getContentBackground();
4873d81c900316412b4130bf40e0dd8b0d3d3a93e78Chris Banes    }
4883d81c900316412b4130bf40e0dd8b0d3d3a93e78Chris Banes
4899840efe3dbdc7026521da8576574c55120782f6cChris Banes    private static int resolveAdjustedSize(int desiredSize, int measureSpec) {
4909840efe3dbdc7026521da8576574c55120782f6cChris Banes        int result = desiredSize;
4919840efe3dbdc7026521da8576574c55120782f6cChris Banes        int specMode = MeasureSpec.getMode(measureSpec);
4929840efe3dbdc7026521da8576574c55120782f6cChris Banes        int specSize = MeasureSpec.getSize(measureSpec);
4939840efe3dbdc7026521da8576574c55120782f6cChris Banes        switch (specMode) {
4949840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.UNSPECIFIED:
4959840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Parent says we can be as big as we want. Just don't be larger
4969840efe3dbdc7026521da8576574c55120782f6cChris Banes                // than max size imposed on ourselves.
4979840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = desiredSize;
4989840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
4999840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.AT_MOST:
5009840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Parent says we can be as big as we want, up to specSize.
5019840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Don't be larger than specSize, and don't be larger than
5029840efe3dbdc7026521da8576574c55120782f6cChris Banes                // the max size imposed on ourselves.
5039840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = Math.min(desiredSize, specSize);
5049840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
5059840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.EXACTLY:
5069840efe3dbdc7026521da8576574c55120782f6cChris Banes                // No choice. Do what we are told.
5079840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = specSize;
5089840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
5099840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
5109840efe3dbdc7026521da8576574c55120782f6cChris Banes        return result;
5119840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
5129840efe3dbdc7026521da8576574c55120782f6cChris Banes
5139840efe3dbdc7026521da8576574c55120782f6cChris Banes    static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
5149840efe3dbdc7026521da8576574c55120782f6cChris Banes        switch (value) {
5159840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 3:
5169840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_OVER;
5179840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 5:
5189840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_IN;
5199840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 9:
5209840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_ATOP;
5219840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 14:
5229840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.MULTIPLY;
5239840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 15:
5249840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SCREEN;
5259840efe3dbdc7026521da8576574c55120782f6cChris Banes            default:
5269840efe3dbdc7026521da8576574c55120782f6cChris Banes                return defaultMode;
5279840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
5289840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
52914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes
530d5d3213d80be767482a89bea6d073006582e2bccChris Banes    @Override
531d5d3213d80be767482a89bea6d073006582e2bccChris Banes    public boolean onTouchEvent(MotionEvent ev) {
532d5d3213d80be767482a89bea6d073006582e2bccChris Banes        if(getContentRect(mTouchArea) && !mTouchArea.contains((int) ev.getX(), (int) ev.getY())) {
533d5d3213d80be767482a89bea6d073006582e2bccChris Banes            return false;
534d5d3213d80be767482a89bea6d073006582e2bccChris Banes        }
535d5d3213d80be767482a89bea6d073006582e2bccChris Banes
536d5d3213d80be767482a89bea6d073006582e2bccChris Banes        return super.onTouchEvent(ev);
537d5d3213d80be767482a89bea6d073006582e2bccChris Banes    }
538d5d3213d80be767482a89bea6d073006582e2bccChris Banes
53914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes    /**
5401005c226fdfddf30e6ad1ec2bb4c57942918dd8fKirill Grouchnikov     * Behavior designed for use with {@link FloatingActionButton} instances. Its main function
541b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do
542b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * not cover them.
543b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     */
544b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
545b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is
546b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        // because we can use view translation properties which greatly simplifies the code.
547b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
548b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
549a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes        private ValueAnimatorCompat mFabTranslationYAnimator;
55018d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes        private float mFabTranslationY;
551a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private Rect mTmpRect;
552b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
553ea0bce9137a7d61b710f8fe299e4790e8f7f1540Chris Banes        public Behavior() {
554ea0bce9137a7d61b710f8fe299e4790e8f7f1540Chris Banes            super();
555ea0bce9137a7d61b710f8fe299e4790e8f7f1540Chris Banes        }
556ea0bce9137a7d61b710f8fe299e4790e8f7f1540Chris Banes
557ea0bce9137a7d61b710f8fe299e4790e8f7f1540Chris Banes        public Behavior(Context context, AttributeSet attrs) {
558ea0bce9137a7d61b710f8fe299e4790e8f7f1540Chris Banes            super(context, attrs);
559ea0bce9137a7d61b710f8fe299e4790e8f7f1540Chris Banes        }
560ea0bce9137a7d61b710f8fe299e4790e8f7f1540Chris Banes
561b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        @Override
562b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        public boolean layoutDependsOn(CoordinatorLayout parent,
563be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                FloatingActionButton child, View dependency) {
564a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            // We're dependent on all SnackbarLayouts (if enabled)
565a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
566b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
567b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
568b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        @Override
569b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
570a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                View dependency) {
571a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            if (dependency instanceof Snackbar.SnackbarLayout) {
572ae0f778788b739d75f704dd4726c22017e30799eChris Banes                updateFabTranslationForSnackbar(parent, child, true);
573a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            } else if (dependency instanceof AppBarLayout) {
574be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // If we're depending on an AppBarLayout we will show/hide it automatically
575be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // if the FAB is anchored to the AppBarLayout
576be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                updateFabVisibility(parent, (AppBarLayout) dependency, child);
577be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
578be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            return false;
579be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes        }
580a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
581c036550c2328e8489d79c79e88f5de7f88449be6Chris Banes        @Override
582c036550c2328e8489d79c79e88f5de7f88449be6Chris Banes        public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,
583c036550c2328e8489d79c79e88f5de7f88449be6Chris Banes                View dependency) {
584c036550c2328e8489d79c79e88f5de7f88449be6Chris Banes            if (dependency instanceof Snackbar.SnackbarLayout) {
5859cf7823a2b044e550dd5c1ba3a7a91d45b4d9673Chris Banes                updateFabTranslationForSnackbar(parent, child, true);
586c036550c2328e8489d79c79e88f5de7f88449be6Chris Banes            }
587c036550c2328e8489d79c79e88f5de7f88449be6Chris Banes        }
588c036550c2328e8489d79c79e88f5de7f88449be6Chris Banes
589be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes        private boolean updateFabVisibility(CoordinatorLayout parent,
590be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                AppBarLayout appBarLayout, FloatingActionButton child) {
591be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            final CoordinatorLayout.LayoutParams lp =
592be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                    (CoordinatorLayout.LayoutParams) child.getLayoutParams();
593be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            if (lp.getAnchorId() != appBarLayout.getId()) {
594be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // The anchor ID doesn't match the dependency, so we won't automatically
595be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // show/hide the FAB
596be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                return false;
597be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
598a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
599fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes            if (child.getUserSetVisibility() != VISIBLE) {
6001005c226fdfddf30e6ad1ec2bb4c57942918dd8fKirill Grouchnikov                // The view isn't set to be visible so skip changing its visibility
601fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes                return false;
602fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes            }
603fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes
604be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            if (mTmpRect == null) {
605be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                mTmpRect = new Rect();
606a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            }
607be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
608be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            // First, let's get the visible rect of the dependency
609be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            final Rect rect = mTmpRect;
610be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect);
611be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
612be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
613be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // If the anchor's bottom is below the seam, we'll animate our FAB out
614fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes                child.hide(null, false);
615be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            } else {
616be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // Else, we'll animate our FAB back in
617fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes                child.show(null, false);
618be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
619be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            return true;
620b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
621b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
622a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
623ae0f778788b739d75f704dd4726c22017e30799eChris Banes                final FloatingActionButton fab, boolean animationAllowed) {
62418d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
62518d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            if (mFabTranslationY == targetTransY) {
62618d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                // We're already at (or currently animating to) the target value, return...
62718d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                return;
62818d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            }
62918d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes
63018d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            final float currentTransY = ViewCompat.getTranslationY(fab);
63118d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes
632a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes            // Make sure that any current animation is cancelled
633a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes            if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) {
634a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                mFabTranslationYAnimator.cancel();
635a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes            }
636a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes
637ae0f778788b739d75f704dd4726c22017e30799eChris Banes            if (animationAllowed && fab.isShown()
638e33473d0e35315c02243363a7479a2c361765751Chris Banes                    && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
6391005c226fdfddf30e6ad1ec2bb4c57942918dd8fKirill Grouchnikov                // If the FAB will be travelling by more than 2/3 of its height, let's animate
64018d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                // it instead
641a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                if (mFabTranslationYAnimator == null) {
642a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                    mFabTranslationYAnimator = ViewUtils.createAnimator();
643a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                    mFabTranslationYAnimator.setInterpolator(
644a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                            AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
645a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                    mFabTranslationYAnimator.setUpdateListener(
646a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                            new ValueAnimatorCompat.AnimatorUpdateListener() {
647a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                                @Override
648a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                                public void onAnimationUpdate(ValueAnimatorCompat animator) {
649a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                                    ViewCompat.setTranslationY(fab,
650a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                                            animator.getAnimatedFloatValue());
651a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                                }
652a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                            });
653a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                }
654a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY);
655a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes                mFabTranslationYAnimator.start();
65618d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            } else {
65718d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                // Now update the translation Y
65818d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                ViewCompat.setTranslationY(fab, targetTransY);
65918d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            }
660a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes
661a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes            mFabTranslationY = targetTransY;
662b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
663b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
664a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
665a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                FloatingActionButton fab) {
666b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            float minOffset = 0;
667a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            final List<View> dependencies = parent.getDependencies(fab);
668a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            for (int i = 0, z = dependencies.size(); i < z; i++) {
669a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                final View view = dependencies.get(i);
670a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
671a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    minOffset = Math.min(minOffset,
672a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                            ViewCompat.getTranslationY(view) - view.getHeight());
673b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                }
674b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
675a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
676b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            return minOffset;
677b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
678b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
679e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        @Override
680e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
681e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                int layoutDirection) {
6821005c226fdfddf30e6ad1ec2bb4c57942918dd8fKirill Grouchnikov            // First, let's make sure that the visibility of the FAB is consistent
683be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            final List<View> dependencies = parent.getDependencies(child);
684be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            for (int i = 0, count = dependencies.size(); i < count; i++) {
685be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                final View dependency = dependencies.get(i);
686be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                if (dependency instanceof AppBarLayout
687be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                        && updateFabVisibility(parent, (AppBarLayout) dependency, child)) {
688be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                    break;
689be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                }
690be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
691be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            // Now let the CoordinatorLayout lay out the FAB
692e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            parent.onLayoutChild(child, layoutDirection);
693e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            // Now offset it if needed
694e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            offsetIfNeeded(parent, child);
695ae0f778788b739d75f704dd4726c22017e30799eChris Banes            // Make sure we translate the FAB for any displayed Snackbars (without an animation)
696ae0f778788b739d75f704dd4726c22017e30799eChris Banes            updateFabTranslationForSnackbar(parent, child, false);
697e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            return true;
698e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        }
699e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
700e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        /**
701e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes         * Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method
702e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes         * offsets our layout position so that we're positioned correctly if we're on one of
703e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes         * our parent's edges.
704e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes         */
705e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) {
706e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            final Rect padding = fab.mShadowPadding;
707e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
708e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) {
709e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                final CoordinatorLayout.LayoutParams lp =
710e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                        (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
711e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
712e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                int offsetTB = 0, offsetLR = 0;
713e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
714e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                if (fab.getRight() >= parent.getWidth() - lp.rightMargin) {
715e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    // If we're on the left edge, shift it the right
716e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    offsetLR = padding.right;
717e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                } else if (fab.getLeft() <= lp.leftMargin) {
718e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    // If we're on the left edge, shift it the left
719e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    offsetLR = -padding.left;
720e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                }
721e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                if (fab.getBottom() >= parent.getBottom() - lp.bottomMargin) {
722e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    // If we're on the bottom edge, shift it down
723e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    offsetTB = padding.bottom;
724e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                } else if (fab.getTop() <= lp.topMargin) {
725e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    // If we're on the top edge, shift it up
726e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    offsetTB = -padding.top;
727e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                }
728e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
729e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                fab.offsetTopAndBottom(offsetTB);
730e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                fab.offsetLeftAndRight(offsetLR);
731e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            }
732e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        }
733b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
7346d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes
7356d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    /**
7366d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * Returns the backward compatible elevation of the FloatingActionButton.
7376d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     *
7382b1d1d93a93070601d3894f523d6421c64544246Chris Banes     * @return the backward compatible elevation in pixels.
7396d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @attr ref android.support.design.R.styleable#FloatingActionButton_elevation
7401e220ff878c4b9c22aff3a6afc20aa89449c1833Chris Banes     * @see #setCompatElevation(float)
7416d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     */
7421e220ff878c4b9c22aff3a6afc20aa89449c1833Chris Banes    public float getCompatElevation() {
743d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        return getImpl().getElevation();
7446d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    }
7456d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes
7466d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    /**
7476d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * Updates the backward compatible elevation of the FloatingActionButton.
7486d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     *
7496d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @param elevation The backward compatible elevation in pixels.
7506d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @attr ref android.support.design.R.styleable#FloatingActionButton_elevation
7511e220ff878c4b9c22aff3a6afc20aa89449c1833Chris Banes     * @see #getCompatElevation()
7526d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     * @see #setUseCompatPadding(boolean)
7536d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes     */
7541e220ff878c4b9c22aff3a6afc20aa89449c1833Chris Banes    public void setCompatElevation(float elevation) {
755d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        getImpl().setElevation(elevation);
7566d7a9a02765e4cb497081e66dafb5d9fa76f4312Chris Banes    }
757cdc736866534c604c4015c78371ade52bb6d52dfChris Banes
758d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes    private FloatingActionButtonImpl getImpl() {
759d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        if (mImpl == null) {
760d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes            mImpl = createImpl();
761d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        }
762d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes        return mImpl;
763d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes    }
764d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes
765d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes    private FloatingActionButtonImpl createImpl() {
766cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        final int sdk = Build.VERSION.SDK_INT;
767cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        if (sdk >= 21) {
768d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes            return new FloatingActionButtonLollipop(this, new ShadowDelegateImpl());
769cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        } else if (sdk >= 14) {
770d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes            return new FloatingActionButtonIcs(this, new ShadowDelegateImpl());
771cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        } else {
772d95e0bb0271ae59387dd9d2ca402ad6f39d789d2Chris Banes            return new FloatingActionButtonEclairMr1(this, new ShadowDelegateImpl());
773cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        }
774cdc736866534c604c4015c78371ade52bb6d52dfChris Banes    }
775cdc736866534c604c4015c78371ade52bb6d52dfChris Banes
776cdc736866534c604c4015c78371ade52bb6d52dfChris Banes    private class ShadowDelegateImpl implements ShadowViewDelegate {
777cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        @Override
778cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        public float getRadius() {
779cdc736866534c604c4015c78371ade52bb6d52dfChris Banes            return getSizeDimension() / 2f;
780cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        }
781cdc736866534c604c4015c78371ade52bb6d52dfChris Banes
782cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        @Override
783cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        public void setShadowPadding(int left, int top, int right, int bottom) {
784cdc736866534c604c4015c78371ade52bb6d52dfChris Banes            mShadowPadding.set(left, top, right, bottom);
785cdc736866534c604c4015c78371ade52bb6d52dfChris Banes            setPadding(left + mImagePadding, top + mImagePadding,
786cdc736866534c604c4015c78371ade52bb6d52dfChris Banes                    right + mImagePadding, bottom + mImagePadding);
787cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        }
788cdc736866534c604c4015c78371ade52bb6d52dfChris Banes
789cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        @Override
790cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        public void setBackgroundDrawable(Drawable background) {
791cdc736866534c604c4015c78371ade52bb6d52dfChris Banes            FloatingActionButton.super.setBackgroundDrawable(background);
792cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        }
793cdc736866534c604c4015c78371ade52bb6d52dfChris Banes
794cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        @Override
795cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        public boolean isCompatPaddingEnabled() {
796cdc736866534c604c4015c78371ade52bb6d52dfChris Banes            return mCompatPadding;
797cdc736866534c604c4015c78371ade52bb6d52dfChris Banes        }
798cdc736866534c604c4015c78371ade52bb6d52dfChris Banes    }
7999840efe3dbdc7026521da8576574c55120782f6cChris Banes}
800