FloatingActionButton.java revision 097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8
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;
229840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.content.res.TypedArray;
239840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.PorterDuff;
249840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.Rect;
259840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.graphics.drawable.Drawable;
269840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.os.Build;
277a13c8489daca7915623dd673df49de2d1a0bf30Chris Banesimport android.support.annotation.ColorInt;
287a13c8489daca7915623dd673df49de2d1a0bf30Chris Banesimport android.support.annotation.NonNull;
299840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.annotation.Nullable;
309840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.design.R;
310ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Weiimport android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
32b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.support.v4.view.ViewCompat;
339840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.util.AttributeSet;
34097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banesimport android.util.Log;
3514d064edb3e4a16a3b90a4a850560177bea1e60dChris Banesimport android.view.View;
363531cb37189bcbd3039056f959e0916dff645d0aChris Banesimport android.widget.ImageButton;
379840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.widget.ImageView;
389840efe3dbdc7026521da8576574c55120782f6cChris Banes
39a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport java.util.List;
40b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
419840efe3dbdc7026521da8576574c55120782f6cChris Banes/**
429840efe3dbdc7026521da8576574c55120782f6cChris Banes * Floating action buttons are used for a special type of promoted action. They are distinguished
4314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes * by a circled icon floating above the UI and have special motion behaviors related to morphing,
449840efe3dbdc7026521da8576574c55120782f6cChris Banes * launching, and the transferring anchor point.
459840efe3dbdc7026521da8576574c55120782f6cChris Banes *
469fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>Floating action buttons come in two sizes: the default and the mini. The size can be
479fb154338a62edc2c57dc036895199d6f1769400Chris Banes * controlled with the {@code fabSize} attribute.</p>
489fb154338a62edc2c57dc036895199d6f1769400Chris Banes *
499fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>As this class descends from {@link ImageView}, you can control the icon which is displayed
509fb154338a62edc2c57dc036895199d6f1769400Chris Banes * via {@link #setImageDrawable(Drawable)}.</p>
519fb154338a62edc2c57dc036895199d6f1769400Chris Banes *
529fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>The background color of this view defaults to the your theme's {@code colorAccent}. If you
539fb154338a62edc2c57dc036895199d6f1769400Chris Banes * wish to change this at runtime then you can do so via
549fb154338a62edc2c57dc036895199d6f1769400Chris Banes * {@link #setBackgroundTintList(ColorStateList)}.</p>
559fb154338a62edc2c57dc036895199d6f1769400Chris Banes *
569fb154338a62edc2c57dc036895199d6f1769400Chris Banes * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize
579840efe3dbdc7026521da8576574c55120782f6cChris Banes */
58b7f9224b1495db47eb8fd813b5912250e900770aChris Banes@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
593531cb37189bcbd3039056f959e0916dff645d0aChris Banespublic class FloatingActionButton extends ImageButton {
609840efe3dbdc7026521da8576574c55120782f6cChris Banes
61097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    private static final String LOG_TAG = "FloatingActionButton";
62097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes
630ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    /**
640ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * Callback to be invoked when the visibility of a FloatingActionButton changes.
650ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     */
660ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    public abstract static class OnVisibilityChangedListener {
670ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        /**
680ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * Called when a FloatingActionButton has been
690ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * {@link #show(OnVisibilityChangedListener) shown}.
700ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         *
710ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * @param fab the FloatingActionButton that was shown.
720ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         */
730ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        public void onShown(FloatingActionButton fab) {}
740ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
750ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        /**
760ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * Called when a FloatingActionButton has been
770ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * {@link #hide(OnVisibilityChangedListener) hidden}.
780ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         *
790ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         * @param fab the FloatingActionButton that was hidden.
800ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei         */
810ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        public void onHidden(FloatingActionButton fab) {}
820ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    }
830ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
849840efe3dbdc7026521da8576574c55120782f6cChris Banes    // These values must match those in the attrs declaration
859840efe3dbdc7026521da8576574c55120782f6cChris Banes    private static final int SIZE_MINI = 1;
869840efe3dbdc7026521da8576574c55120782f6cChris Banes    private static final int SIZE_NORMAL = 0;
879840efe3dbdc7026521da8576574c55120782f6cChris Banes
889840efe3dbdc7026521da8576574c55120782f6cChris Banes    private ColorStateList mBackgroundTint;
899840efe3dbdc7026521da8576574c55120782f6cChris Banes    private PorterDuff.Mode mBackgroundTintMode;
909840efe3dbdc7026521da8576574c55120782f6cChris Banes
91cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes    private int mBorderWidth;
929840efe3dbdc7026521da8576574c55120782f6cChris Banes    private int mRippleColor;
939840efe3dbdc7026521da8576574c55120782f6cChris Banes    private int mSize;
949840efe3dbdc7026521da8576574c55120782f6cChris Banes    private int mContentPadding;
959840efe3dbdc7026521da8576574c55120782f6cChris Banes
969840efe3dbdc7026521da8576574c55120782f6cChris Banes    private final Rect mShadowPadding;
979840efe3dbdc7026521da8576574c55120782f6cChris Banes
989840efe3dbdc7026521da8576574c55120782f6cChris Banes    private final FloatingActionButtonImpl mImpl;
999840efe3dbdc7026521da8576574c55120782f6cChris Banes
1009840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context) {
1019840efe3dbdc7026521da8576574c55120782f6cChris Banes        this(context, null);
1029840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1039840efe3dbdc7026521da8576574c55120782f6cChris Banes
1049840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context, AttributeSet attrs) {
1059840efe3dbdc7026521da8576574c55120782f6cChris Banes        this(context, attrs, 0);
1069840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1079840efe3dbdc7026521da8576574c55120782f6cChris Banes
1089840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
1099840efe3dbdc7026521da8576574c55120782f6cChris Banes        super(context, attrs, defStyleAttr);
1109840efe3dbdc7026521da8576574c55120782f6cChris Banes
111809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes        ThemeUtils.checkAppCompatTheme(context);
112809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes
1139840efe3dbdc7026521da8576574c55120782f6cChris Banes        mShadowPadding = new Rect();
1149840efe3dbdc7026521da8576574c55120782f6cChris Banes
1159840efe3dbdc7026521da8576574c55120782f6cChris Banes        TypedArray a = context.obtainStyledAttributes(attrs,
1169840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton, defStyleAttr,
1179840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.style.Widget_Design_FloatingActionButton);
1189840efe3dbdc7026521da8576574c55120782f6cChris Banes        mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint);
1199840efe3dbdc7026521da8576574c55120782f6cChris Banes        mBackgroundTintMode = parseTintMode(a.getInt(
1209840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
1219840efe3dbdc7026521da8576574c55120782f6cChris Banes        mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
1229840efe3dbdc7026521da8576574c55120782f6cChris Banes        mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL);
123cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes        mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0);
1249840efe3dbdc7026521da8576574c55120782f6cChris Banes        final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
1259840efe3dbdc7026521da8576574c55120782f6cChris Banes        final float pressedTranslationZ = a.getDimension(
1269840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton_pressedTranslationZ, 0f);
1279840efe3dbdc7026521da8576574c55120782f6cChris Banes        a.recycle();
1289840efe3dbdc7026521da8576574c55120782f6cChris Banes
1299840efe3dbdc7026521da8576574c55120782f6cChris Banes        final ShadowViewDelegate delegate = new ShadowViewDelegate() {
1309840efe3dbdc7026521da8576574c55120782f6cChris Banes            @Override
1319840efe3dbdc7026521da8576574c55120782f6cChris Banes            public float getRadius() {
1329840efe3dbdc7026521da8576574c55120782f6cChris Banes                return getSizeDimension() / 2f;
1339840efe3dbdc7026521da8576574c55120782f6cChris Banes            }
1349840efe3dbdc7026521da8576574c55120782f6cChris Banes
1359840efe3dbdc7026521da8576574c55120782f6cChris Banes            @Override
1369840efe3dbdc7026521da8576574c55120782f6cChris Banes            public void setShadowPadding(int left, int top, int right, int bottom) {
1379840efe3dbdc7026521da8576574c55120782f6cChris Banes                mShadowPadding.set(left, top, right, bottom);
1389840efe3dbdc7026521da8576574c55120782f6cChris Banes
1399840efe3dbdc7026521da8576574c55120782f6cChris Banes                setPadding(left + mContentPadding, top + mContentPadding,
1409840efe3dbdc7026521da8576574c55120782f6cChris Banes                        right + mContentPadding, bottom + mContentPadding);
1419840efe3dbdc7026521da8576574c55120782f6cChris Banes            }
1429840efe3dbdc7026521da8576574c55120782f6cChris Banes
1439840efe3dbdc7026521da8576574c55120782f6cChris Banes            @Override
1449840efe3dbdc7026521da8576574c55120782f6cChris Banes            public void setBackgroundDrawable(Drawable background) {
1459840efe3dbdc7026521da8576574c55120782f6cChris Banes                FloatingActionButton.super.setBackgroundDrawable(background);
1469840efe3dbdc7026521da8576574c55120782f6cChris Banes            }
1479840efe3dbdc7026521da8576574c55120782f6cChris Banes        };
1489840efe3dbdc7026521da8576574c55120782f6cChris Banes
149be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes        final int sdk = Build.VERSION.SDK_INT;
150be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes        if (sdk >= 21) {
1519840efe3dbdc7026521da8576574c55120782f6cChris Banes            mImpl = new FloatingActionButtonLollipop(this, delegate);
1529ec922c215ed95a4bbd8bd7940e81dcfe6926893Chris Banes        } else if (sdk >= 14) {
1539ec922c215ed95a4bbd8bd7940e81dcfe6926893Chris Banes            mImpl = new FloatingActionButtonIcs(this, delegate);
1549840efe3dbdc7026521da8576574c55120782f6cChris Banes        } else {
1559840efe3dbdc7026521da8576574c55120782f6cChris Banes            mImpl = new FloatingActionButtonEclairMr1(this, delegate);
1569840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
1579840efe3dbdc7026521da8576574c55120782f6cChris Banes
158a577676a64e5353b8ec927117151aa6be84adf66Chris Banes        final int maxContentSize = (int) getResources().getDimension(
159a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                R.dimen.design_fab_content_size);
1609840efe3dbdc7026521da8576574c55120782f6cChris Banes        mContentPadding = (getSizeDimension() - maxContentSize) / 2;
1619840efe3dbdc7026521da8576574c55120782f6cChris Banes
162097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes        mImpl.setBackgroundDrawable(mBackgroundTint, mBackgroundTintMode,
163097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes                mRippleColor, mBorderWidth);
1649840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.setElevation(elevation);
1659840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.setPressedTranslationZ(pressedTranslationZ);
1669840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1679840efe3dbdc7026521da8576574c55120782f6cChris Banes
1689840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
1699840efe3dbdc7026521da8576574c55120782f6cChris Banes    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1709840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int preferredSize = getSizeDimension();
1719840efe3dbdc7026521da8576574c55120782f6cChris Banes
1729840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec);
1739840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec);
1749840efe3dbdc7026521da8576574c55120782f6cChris Banes
1759840efe3dbdc7026521da8576574c55120782f6cChris Banes        // As we want to stay circular, we set both dimensions to be the
1769840efe3dbdc7026521da8576574c55120782f6cChris Banes        // smallest resolved dimension
1779840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int d = Math.min(w, h);
1789840efe3dbdc7026521da8576574c55120782f6cChris Banes
1799840efe3dbdc7026521da8576574c55120782f6cChris Banes        // We add the shadow's padding to the measured dimension
1809840efe3dbdc7026521da8576574c55120782f6cChris Banes        setMeasuredDimension(
1819840efe3dbdc7026521da8576574c55120782f6cChris Banes                d + mShadowPadding.left + mShadowPadding.right,
1829840efe3dbdc7026521da8576574c55120782f6cChris Banes                d + mShadowPadding.top + mShadowPadding.bottom);
1839840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1849840efe3dbdc7026521da8576574c55120782f6cChris Banes
1859840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
1869840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Set the ripple color for this {@link FloatingActionButton}.
1879840efe3dbdc7026521da8576574c55120782f6cChris Banes     * <p>
1889840efe3dbdc7026521da8576574c55120782f6cChris Banes     * When running on devices with KitKat or below, we draw a fill rather than a ripple.
1899840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
1909840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param color ARGB color to use for the ripple.
1919840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
1927a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes    public void setRippleColor(@ColorInt int color) {
1939840efe3dbdc7026521da8576574c55120782f6cChris Banes        if (mRippleColor != color) {
1949840efe3dbdc7026521da8576574c55120782f6cChris Banes            mRippleColor = color;
1959840efe3dbdc7026521da8576574c55120782f6cChris Banes            mImpl.setRippleColor(color);
1969840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
1979840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1989840efe3dbdc7026521da8576574c55120782f6cChris Banes
1999840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2009840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Return the tint applied to the background drawable, if specified.
2019840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2029840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @return the tint applied to the background drawable
2039840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @see #setBackgroundTintList(ColorStateList)
2049840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2059840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Nullable
2069840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
2079840efe3dbdc7026521da8576574c55120782f6cChris Banes    public ColorStateList getBackgroundTintList() {
2089840efe3dbdc7026521da8576574c55120782f6cChris Banes        return mBackgroundTint;
2099840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2109840efe3dbdc7026521da8576574c55120782f6cChris Banes
2119840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2129840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Applies a tint to the background drawable. Does not modify the current tint
2139840efe3dbdc7026521da8576574c55120782f6cChris Banes     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2149840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2159840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param tint the tint to apply, may be {@code null} to clear tint
2169840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2179840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void setBackgroundTintList(@Nullable ColorStateList tint) {
2187a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes        if (mBackgroundTint != tint) {
2197a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes            mBackgroundTint = tint;
2207a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes            mImpl.setBackgroundTintList(tint);
2217a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes        }
2229840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2239840efe3dbdc7026521da8576574c55120782f6cChris Banes
2249840efe3dbdc7026521da8576574c55120782f6cChris Banes
2259840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2269840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Return the blending mode used to apply the tint to the background
2279840efe3dbdc7026521da8576574c55120782f6cChris Banes     * drawable, if specified.
2289840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2299840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @return the blending mode used to apply the tint to the background
2309840efe3dbdc7026521da8576574c55120782f6cChris Banes     *         drawable
2319840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @see #setBackgroundTintMode(PorterDuff.Mode)
2329840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2339840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Nullable
2349840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
2359840efe3dbdc7026521da8576574c55120782f6cChris Banes    public PorterDuff.Mode getBackgroundTintMode() {
2369840efe3dbdc7026521da8576574c55120782f6cChris Banes        return mBackgroundTintMode;
2379840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2389840efe3dbdc7026521da8576574c55120782f6cChris Banes
2399840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2409840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Specifies the blending mode used to apply the tint specified by
2419840efe3dbdc7026521da8576574c55120782f6cChris Banes     * {@link #setBackgroundTintList(ColorStateList)}} to the background
2429840efe3dbdc7026521da8576574c55120782f6cChris Banes     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2439840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2449840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param tintMode the blending mode used to apply the tint, may be
2459840efe3dbdc7026521da8576574c55120782f6cChris Banes     *                 {@code null} to clear tint
2469840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2479840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
2487a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes        if (mBackgroundTintMode != tintMode) {
2497a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes            mBackgroundTintMode = tintMode;
2507a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes            mImpl.setBackgroundTintMode(tintMode);
2517a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes        }
2529840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2539840efe3dbdc7026521da8576574c55120782f6cChris Banes
2549840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
255097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    public void setBackgroundDrawable(Drawable background) {
256097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes        Log.i(LOG_TAG, "Setting a custom background is not supported.");
257097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    }
258097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes
259097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    @Override
260097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    public void setBackgroundResource(int resid) {
261097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes        Log.i(LOG_TAG, "Setting a custom background is not supported.");
262097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    }
263097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes
264097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    @Override
265097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes    public void setBackgroundColor(int color) {
266097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes        Log.i(LOG_TAG, "Setting a custom background is not supported.");
267be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    }
268be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
269be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    /**
270be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     * Shows the button.
2718c05e5f52fbc790b745e768398d9e69d6b9d9ee1Chris Banes     * <p>This method will animate the button show if the view has already been laid out.</p>
272be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     */
273be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    public void show() {
2740ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        mImpl.show(null);
2750ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    }
2760ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
2770ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    /**
2780ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * Shows the button.
2798c05e5f52fbc790b745e768398d9e69d6b9d9ee1Chris Banes     * <p>This method will animate the button show if the view has already been laid out.</p>
2800ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     *
2810ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * @param listener the listener to notify when this view is shown
2820ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     */
2830ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    public void show(@Nullable final OnVisibilityChangedListener listener) {
2840ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        mImpl.show(wrapOnVisibilityChangedListener(listener));
285be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    }
286be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
287be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    /**
288be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     * Hides the button.
289be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     * <p>This method will animate the button hide if the view has already been laid out.</p>
290be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes     */
291be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes    public void hide() {
2920ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        mImpl.hide(null);
2930ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    }
2940ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
2950ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    /**
2960ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * Hides the button.
2970ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * <p>This method will animate the button hide if the view has already been laid out.</p>
2980ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     *
2990ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     * @param listener the listener to notify when this view is hidden
3000ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei     */
3010ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    public void hide(@Nullable OnVisibilityChangedListener listener) {
3020ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        mImpl.hide(wrapOnVisibilityChangedListener(listener));
3030ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    }
3040ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
3050ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    @Nullable
3060ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei    private InternalVisibilityChangedListener wrapOnVisibilityChangedListener(
3070ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            @Nullable final OnVisibilityChangedListener listener) {
3080ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        if (listener == null) {
3090ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            return null;
3100ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        }
3110ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
3120ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        return new InternalVisibilityChangedListener() {
3130ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            @Override
3140ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            public void onShown() {
3150ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei                listener.onShown(FloatingActionButton.this);
3160ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            }
3170ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei
3180ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            @Override
3190ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            public void onHidden() {
3200ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei                listener.onHidden(FloatingActionButton.this);
3210ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei            }
3220ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei        };
3239840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
3249840efe3dbdc7026521da8576574c55120782f6cChris Banes
3259840efe3dbdc7026521da8576574c55120782f6cChris Banes    final int getSizeDimension() {
3269840efe3dbdc7026521da8576574c55120782f6cChris Banes        switch (mSize) {
3279840efe3dbdc7026521da8576574c55120782f6cChris Banes            case SIZE_MINI:
328a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                return getResources().getDimensionPixelSize(R.dimen.design_fab_size_mini);
3299840efe3dbdc7026521da8576574c55120782f6cChris Banes            case SIZE_NORMAL:
3309840efe3dbdc7026521da8576574c55120782f6cChris Banes            default:
331a577676a64e5353b8ec927117151aa6be84adf66Chris Banes                return getResources().getDimensionPixelSize(R.dimen.design_fab_size_normal);
3329840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
3339840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
3349840efe3dbdc7026521da8576574c55120782f6cChris Banes
3359840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
336d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    protected void onAttachedToWindow() {
337d9770e12c8ff2d4417700492c6616572be897e93Chris Banes        super.onAttachedToWindow();
338d9770e12c8ff2d4417700492c6616572be897e93Chris Banes        mImpl.onAttachedToWindow();
339d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    }
340d9770e12c8ff2d4417700492c6616572be897e93Chris Banes
341d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    @Override
342d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    protected void onDetachedFromWindow() {
343d9770e12c8ff2d4417700492c6616572be897e93Chris Banes        super.onDetachedFromWindow();
344d9770e12c8ff2d4417700492c6616572be897e93Chris Banes        mImpl.onDetachedFromWindow();
345d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    }
346d9770e12c8ff2d4417700492c6616572be897e93Chris Banes
347d9770e12c8ff2d4417700492c6616572be897e93Chris Banes    @Override
3489840efe3dbdc7026521da8576574c55120782f6cChris Banes    protected void drawableStateChanged() {
3499840efe3dbdc7026521da8576574c55120782f6cChris Banes        super.drawableStateChanged();
3509840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.onDrawableStateChanged(getDrawableState());
3519840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
3529840efe3dbdc7026521da8576574c55120782f6cChris Banes
3539840efe3dbdc7026521da8576574c55120782f6cChris Banes    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
3549840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
3559840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void jumpDrawablesToCurrentState() {
3569840efe3dbdc7026521da8576574c55120782f6cChris Banes        super.jumpDrawablesToCurrentState();
3579840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.jumpDrawableToCurrentState();
3589840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
3599840efe3dbdc7026521da8576574c55120782f6cChris Banes
3609840efe3dbdc7026521da8576574c55120782f6cChris Banes    private static int resolveAdjustedSize(int desiredSize, int measureSpec) {
3619840efe3dbdc7026521da8576574c55120782f6cChris Banes        int result = desiredSize;
3629840efe3dbdc7026521da8576574c55120782f6cChris Banes        int specMode = MeasureSpec.getMode(measureSpec);
3639840efe3dbdc7026521da8576574c55120782f6cChris Banes        int specSize = MeasureSpec.getSize(measureSpec);
3649840efe3dbdc7026521da8576574c55120782f6cChris Banes        switch (specMode) {
3659840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.UNSPECIFIED:
3669840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Parent says we can be as big as we want. Just don't be larger
3679840efe3dbdc7026521da8576574c55120782f6cChris Banes                // than max size imposed on ourselves.
3689840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = desiredSize;
3699840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
3709840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.AT_MOST:
3719840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Parent says we can be as big as we want, up to specSize.
3729840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Don't be larger than specSize, and don't be larger than
3739840efe3dbdc7026521da8576574c55120782f6cChris Banes                // the max size imposed on ourselves.
3749840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = Math.min(desiredSize, specSize);
3759840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
3769840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.EXACTLY:
3779840efe3dbdc7026521da8576574c55120782f6cChris Banes                // No choice. Do what we are told.
3789840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = specSize;
3799840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
3809840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
3819840efe3dbdc7026521da8576574c55120782f6cChris Banes        return result;
3829840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
3839840efe3dbdc7026521da8576574c55120782f6cChris Banes
3849840efe3dbdc7026521da8576574c55120782f6cChris Banes    static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
3859840efe3dbdc7026521da8576574c55120782f6cChris Banes        switch (value) {
3869840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 3:
3879840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_OVER;
3889840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 5:
3899840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_IN;
3909840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 9:
3919840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_ATOP;
3929840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 14:
3939840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.MULTIPLY;
3949840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 15:
3959840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SCREEN;
3969840efe3dbdc7026521da8576574c55120782f6cChris Banes            default:
3979840efe3dbdc7026521da8576574c55120782f6cChris Banes                return defaultMode;
3989840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
3999840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
40014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes
40114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes    /**
402b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * Behavior designed for use with {@link FloatingActionButton} instances. It's main function
403b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do
404b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * not cover them.
405b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     */
406b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
407b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is
408b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        // because we can use view translation properties which greatly simplifies the code.
409b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
410b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
41118d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes        private float mFabTranslationY;
412a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private Rect mTmpRect;
413b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
414b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        @Override
415b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        public boolean layoutDependsOn(CoordinatorLayout parent,
416be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                FloatingActionButton child, View dependency) {
417a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            // We're dependent on all SnackbarLayouts (if enabled)
418a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
419b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
420b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
421b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        @Override
422b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
423a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                View dependency) {
424a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            if (dependency instanceof Snackbar.SnackbarLayout) {
425a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                updateFabTranslationForSnackbar(parent, child, dependency);
426a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            } else if (dependency instanceof AppBarLayout) {
427be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // If we're depending on an AppBarLayout we will show/hide it automatically
428be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // if the FAB is anchored to the AppBarLayout
429be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                updateFabVisibility(parent, (AppBarLayout) dependency, child);
430be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
431be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            return false;
432be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes        }
433a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
434be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes        private boolean updateFabVisibility(CoordinatorLayout parent,
435be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                AppBarLayout appBarLayout, FloatingActionButton child) {
436be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            final CoordinatorLayout.LayoutParams lp =
437be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                    (CoordinatorLayout.LayoutParams) child.getLayoutParams();
438be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            if (lp.getAnchorId() != appBarLayout.getId()) {
439be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // The anchor ID doesn't match the dependency, so we won't automatically
440be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // show/hide the FAB
441be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                return false;
442be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
443a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
444be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            if (mTmpRect == null) {
445be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                mTmpRect = new Rect();
446a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            }
447be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
448be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            // First, let's get the visible rect of the dependency
449be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            final Rect rect = mTmpRect;
450be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect);
451be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
452be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
453be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // If the anchor's bottom is below the seam, we'll animate our FAB out
454be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                child.hide();
455be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            } else {
456be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                // Else, we'll animate our FAB back in
457be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                child.show();
458be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
459be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            return true;
460b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
461b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
462a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
463a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                FloatingActionButton fab, View snackbar) {
464be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            if (fab.getVisibility() != View.VISIBLE) {
465be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                return;
466be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
467be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes
46818d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
46918d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            if (mFabTranslationY == targetTransY) {
47018d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                // We're already at (or currently animating to) the target value, return...
47118d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                return;
47218d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            }
47318d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes
47418d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            mFabTranslationY = targetTransY;
47518d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            final float currentTransY = ViewCompat.getTranslationY(fab);
47618d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            final float dy = currentTransY - targetTransY;
47718d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes
47818d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            if (Math.abs(dy) > (fab.getHeight() * 0.667f)) {
47918d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                // If the FAB will be travelling by more than 2/3 of it's height, let's animate
48018d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                // it instead
48118d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                ViewCompat.animate(fab)
48218d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                        .translationY(targetTransY)
48318d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                        .scaleX(1f)
48418d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                        .scaleY(1f)
48518d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                        .alpha(1f)
48618d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                        .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
48718d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                        .setListener(null);
48818d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            } else {
48918d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                // Make sure that any current animation is cancelled
49018d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                ViewCompat.animate(fab).cancel();
49118d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                // Now update the translation Y
49218d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes                ViewCompat.setTranslationY(fab, targetTransY);
49318d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes            }
494b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
495b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
496a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
497a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                FloatingActionButton fab) {
498b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            float minOffset = 0;
499a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            final List<View> dependencies = parent.getDependencies(fab);
500a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            for (int i = 0, z = dependencies.size(); i < z; i++) {
501a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                final View view = dependencies.get(i);
502a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
503a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    minOffset = Math.min(minOffset,
504a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                            ViewCompat.getTranslationY(view) - view.getHeight());
505b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                }
506b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
507a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
508b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            return minOffset;
509b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
510b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
511e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        @Override
512e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
513e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                int layoutDirection) {
514be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            // First, lets make sure that the visibility of the FAB is consistent
515be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            final List<View> dependencies = parent.getDependencies(child);
516be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            for (int i = 0, count = dependencies.size(); i < count; i++) {
517be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                final View dependency = dependencies.get(i);
518be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                if (dependency instanceof AppBarLayout
519be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                        && updateFabVisibility(parent, (AppBarLayout) dependency, child)) {
520be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                    break;
521be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes                }
522be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            }
523be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes            // Now let the CoordinatorLayout lay out the FAB
524e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            parent.onLayoutChild(child, layoutDirection);
525e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            // Now offset it if needed
526e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            offsetIfNeeded(parent, child);
527e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            return true;
528e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        }
529e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
530e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        /**
531e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes         * Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method
532e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes         * offsets our layout position so that we're positioned correctly if we're on one of
533e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes         * our parent's edges.
534e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes         */
535e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) {
536e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            final Rect padding = fab.mShadowPadding;
537e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
538e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) {
539e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                final CoordinatorLayout.LayoutParams lp =
540e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                        (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
541e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
542e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                int offsetTB = 0, offsetLR = 0;
543e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
544e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                if (fab.getRight() >= parent.getWidth() - lp.rightMargin) {
545e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    // If we're on the left edge, shift it the right
546e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    offsetLR = padding.right;
547e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                } else if (fab.getLeft() <= lp.leftMargin) {
548e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    // If we're on the left edge, shift it the left
549e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    offsetLR = -padding.left;
550e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                }
551e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                if (fab.getBottom() >= parent.getBottom() - lp.bottomMargin) {
552e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    // If we're on the bottom edge, shift it down
553e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    offsetTB = padding.bottom;
554e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                } else if (fab.getTop() <= lp.topMargin) {
555e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    // If we're on the top edge, shift it up
556e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                    offsetTB = -padding.top;
557e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                }
558e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes
559e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                fab.offsetTopAndBottom(offsetTB);
560e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes                fab.offsetLeftAndRight(offsetLR);
561e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes            }
562e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes        }
563b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
5649840efe3dbdc7026521da8576574c55120782f6cChris Banes}
565