FloatingActionButton.java revision cd78954a2b32d9c22686f12c194fac7e49566cf6
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;
279840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.annotation.Nullable;
289840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.design.R;
29b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.support.v4.view.ViewCompat;
30a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport android.support.v4.view.ViewPropertyAnimatorListener;
319840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.util.AttributeSet;
3214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banesimport android.view.View;
33631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banesimport android.view.animation.Animation;
349840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.widget.ImageView;
359840efe3dbdc7026521da8576574c55120782f6cChris Banes
36a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport java.util.List;
37b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
389840efe3dbdc7026521da8576574c55120782f6cChris Banes/**
399840efe3dbdc7026521da8576574c55120782f6cChris Banes * Floating action buttons are used for a special type of promoted action. They are distinguished
4014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes * by a circled icon floating above the UI and have special motion behaviors related to morphing,
419840efe3dbdc7026521da8576574c55120782f6cChris Banes * launching, and the transferring anchor point.
429840efe3dbdc7026521da8576574c55120782f6cChris Banes *
439840efe3dbdc7026521da8576574c55120782f6cChris Banes * Floating action buttons come in two sizes: the default, which should be used in most cases, and
449840efe3dbdc7026521da8576574c55120782f6cChris Banes * the mini, which should only be used to create visual continuity with other elements on the
459840efe3dbdc7026521da8576574c55120782f6cChris Banes * screen.
469840efe3dbdc7026521da8576574c55120782f6cChris Banes */
47b7f9224b1495db47eb8fd813b5912250e900770aChris Banes@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
489840efe3dbdc7026521da8576574c55120782f6cChris Banespublic class FloatingActionButton extends ImageView {
499840efe3dbdc7026521da8576574c55120782f6cChris Banes
509840efe3dbdc7026521da8576574c55120782f6cChris Banes    // These values must match those in the attrs declaration
519840efe3dbdc7026521da8576574c55120782f6cChris Banes    private static final int SIZE_MINI = 1;
529840efe3dbdc7026521da8576574c55120782f6cChris Banes    private static final int SIZE_NORMAL = 0;
539840efe3dbdc7026521da8576574c55120782f6cChris Banes
549840efe3dbdc7026521da8576574c55120782f6cChris Banes    private ColorStateList mBackgroundTint;
559840efe3dbdc7026521da8576574c55120782f6cChris Banes    private PorterDuff.Mode mBackgroundTintMode;
569840efe3dbdc7026521da8576574c55120782f6cChris Banes
57cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes    private int mBorderWidth;
589840efe3dbdc7026521da8576574c55120782f6cChris Banes    private int mRippleColor;
599840efe3dbdc7026521da8576574c55120782f6cChris Banes    private int mSize;
609840efe3dbdc7026521da8576574c55120782f6cChris Banes    private int mContentPadding;
619840efe3dbdc7026521da8576574c55120782f6cChris Banes
629840efe3dbdc7026521da8576574c55120782f6cChris Banes    private final Rect mShadowPadding;
639840efe3dbdc7026521da8576574c55120782f6cChris Banes
649840efe3dbdc7026521da8576574c55120782f6cChris Banes    private final FloatingActionButtonImpl mImpl;
659840efe3dbdc7026521da8576574c55120782f6cChris Banes
669840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context) {
679840efe3dbdc7026521da8576574c55120782f6cChris Banes        this(context, null);
689840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
699840efe3dbdc7026521da8576574c55120782f6cChris Banes
709840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context, AttributeSet attrs) {
719840efe3dbdc7026521da8576574c55120782f6cChris Banes        this(context, attrs, 0);
729840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
739840efe3dbdc7026521da8576574c55120782f6cChris Banes
749840efe3dbdc7026521da8576574c55120782f6cChris Banes    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
759840efe3dbdc7026521da8576574c55120782f6cChris Banes        super(context, attrs, defStyleAttr);
769840efe3dbdc7026521da8576574c55120782f6cChris Banes
779840efe3dbdc7026521da8576574c55120782f6cChris Banes        mShadowPadding = new Rect();
789840efe3dbdc7026521da8576574c55120782f6cChris Banes
799840efe3dbdc7026521da8576574c55120782f6cChris Banes        TypedArray a = context.obtainStyledAttributes(attrs,
809840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton, defStyleAttr,
819840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.style.Widget_Design_FloatingActionButton);
829840efe3dbdc7026521da8576574c55120782f6cChris Banes        Drawable background = a.getDrawable(R.styleable.FloatingActionButton_android_background);
839840efe3dbdc7026521da8576574c55120782f6cChris Banes        mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint);
849840efe3dbdc7026521da8576574c55120782f6cChris Banes        mBackgroundTintMode = parseTintMode(a.getInt(
859840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
869840efe3dbdc7026521da8576574c55120782f6cChris Banes        mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
879840efe3dbdc7026521da8576574c55120782f6cChris Banes        mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL);
88cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes        mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0);
899840efe3dbdc7026521da8576574c55120782f6cChris Banes        final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
909840efe3dbdc7026521da8576574c55120782f6cChris Banes        final float pressedTranslationZ = a.getDimension(
919840efe3dbdc7026521da8576574c55120782f6cChris Banes                R.styleable.FloatingActionButton_pressedTranslationZ, 0f);
929840efe3dbdc7026521da8576574c55120782f6cChris Banes        a.recycle();
939840efe3dbdc7026521da8576574c55120782f6cChris Banes
949840efe3dbdc7026521da8576574c55120782f6cChris Banes        final ShadowViewDelegate delegate = new ShadowViewDelegate() {
959840efe3dbdc7026521da8576574c55120782f6cChris Banes            @Override
969840efe3dbdc7026521da8576574c55120782f6cChris Banes            public float getRadius() {
979840efe3dbdc7026521da8576574c55120782f6cChris Banes                return getSizeDimension() / 2f;
989840efe3dbdc7026521da8576574c55120782f6cChris Banes            }
999840efe3dbdc7026521da8576574c55120782f6cChris Banes
1009840efe3dbdc7026521da8576574c55120782f6cChris Banes            @Override
1019840efe3dbdc7026521da8576574c55120782f6cChris Banes            public void setShadowPadding(int left, int top, int right, int bottom) {
1029840efe3dbdc7026521da8576574c55120782f6cChris Banes                mShadowPadding.set(left, top, right, bottom);
1039840efe3dbdc7026521da8576574c55120782f6cChris Banes
1049840efe3dbdc7026521da8576574c55120782f6cChris Banes                setPadding(left + mContentPadding, top + mContentPadding,
1059840efe3dbdc7026521da8576574c55120782f6cChris Banes                        right + mContentPadding, bottom + mContentPadding);
1069840efe3dbdc7026521da8576574c55120782f6cChris Banes            }
1079840efe3dbdc7026521da8576574c55120782f6cChris Banes
1089840efe3dbdc7026521da8576574c55120782f6cChris Banes            @Override
1099840efe3dbdc7026521da8576574c55120782f6cChris Banes            public void setBackgroundDrawable(Drawable background) {
1109840efe3dbdc7026521da8576574c55120782f6cChris Banes                FloatingActionButton.super.setBackgroundDrawable(background);
1119840efe3dbdc7026521da8576574c55120782f6cChris Banes            }
1129840efe3dbdc7026521da8576574c55120782f6cChris Banes        };
1139840efe3dbdc7026521da8576574c55120782f6cChris Banes
1149840efe3dbdc7026521da8576574c55120782f6cChris Banes        if (Build.VERSION.SDK_INT >= 21) {
1159840efe3dbdc7026521da8576574c55120782f6cChris Banes            mImpl = new FloatingActionButtonLollipop(this, delegate);
1169840efe3dbdc7026521da8576574c55120782f6cChris Banes        } else {
1179840efe3dbdc7026521da8576574c55120782f6cChris Banes            mImpl = new FloatingActionButtonEclairMr1(this, delegate);
1189840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
1199840efe3dbdc7026521da8576574c55120782f6cChris Banes
1209840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int maxContentSize = (int) getResources().getDimension(R.dimen.fab_content_size);
1219840efe3dbdc7026521da8576574c55120782f6cChris Banes        mContentPadding = (getSizeDimension() - maxContentSize) / 2;
1229840efe3dbdc7026521da8576574c55120782f6cChris Banes
1239840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.setBackgroundDrawable(background, mBackgroundTint,
124cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes                mBackgroundTintMode, mRippleColor, mBorderWidth);
1259840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.setElevation(elevation);
1269840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.setPressedTranslationZ(pressedTranslationZ);
127d039e3555848f678a2e5363e99026df322d02044Taeho Kim
128d039e3555848f678a2e5363e99026df322d02044Taeho Kim        setClickable(true);
1299840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1309840efe3dbdc7026521da8576574c55120782f6cChris Banes
1319840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
1329840efe3dbdc7026521da8576574c55120782f6cChris Banes    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1339840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int preferredSize = getSizeDimension();
1349840efe3dbdc7026521da8576574c55120782f6cChris Banes
1359840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec);
1369840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec);
1379840efe3dbdc7026521da8576574c55120782f6cChris Banes
1389840efe3dbdc7026521da8576574c55120782f6cChris Banes        // As we want to stay circular, we set both dimensions to be the
1399840efe3dbdc7026521da8576574c55120782f6cChris Banes        // smallest resolved dimension
1409840efe3dbdc7026521da8576574c55120782f6cChris Banes        final int d = Math.min(w, h);
1419840efe3dbdc7026521da8576574c55120782f6cChris Banes
1429840efe3dbdc7026521da8576574c55120782f6cChris Banes        // We add the shadow's padding to the measured dimension
1439840efe3dbdc7026521da8576574c55120782f6cChris Banes        setMeasuredDimension(
1449840efe3dbdc7026521da8576574c55120782f6cChris Banes                d + mShadowPadding.left + mShadowPadding.right,
1459840efe3dbdc7026521da8576574c55120782f6cChris Banes                d + mShadowPadding.top + mShadowPadding.bottom);
1469840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1479840efe3dbdc7026521da8576574c55120782f6cChris Banes
1489840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
1499840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Set the ripple color for this {@link FloatingActionButton}.
1509840efe3dbdc7026521da8576574c55120782f6cChris Banes     * <p>
1519840efe3dbdc7026521da8576574c55120782f6cChris Banes     * When running on devices with KitKat or below, we draw a fill rather than a ripple.
1529840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
1539840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param color ARGB color to use for the ripple.
1549840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
1559840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void setRippleColor(int color) {
1569840efe3dbdc7026521da8576574c55120782f6cChris Banes        if (mRippleColor != color) {
1579840efe3dbdc7026521da8576574c55120782f6cChris Banes            mRippleColor = color;
1589840efe3dbdc7026521da8576574c55120782f6cChris Banes            mImpl.setRippleColor(color);
1599840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
1609840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1619840efe3dbdc7026521da8576574c55120782f6cChris Banes
1629840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
1639840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Return the tint applied to the background drawable, if specified.
1649840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
1659840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @return the tint applied to the background drawable
1669840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @see #setBackgroundTintList(ColorStateList)
1679840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
1689840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Nullable
1699840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
1709840efe3dbdc7026521da8576574c55120782f6cChris Banes    public ColorStateList getBackgroundTintList() {
1719840efe3dbdc7026521da8576574c55120782f6cChris Banes        return mBackgroundTint;
1729840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1739840efe3dbdc7026521da8576574c55120782f6cChris Banes
1749840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
1759840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Applies a tint to the background drawable. Does not modify the current tint
1769840efe3dbdc7026521da8576574c55120782f6cChris Banes     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
1779840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
1789840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param tint the tint to apply, may be {@code null} to clear tint
1799840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
1809840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void setBackgroundTintList(@Nullable ColorStateList tint) {
1819840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.setBackgroundTintList(tint);
1829840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1839840efe3dbdc7026521da8576574c55120782f6cChris Banes
1849840efe3dbdc7026521da8576574c55120782f6cChris Banes
1859840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
1869840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Return the blending mode used to apply the tint to the background
1879840efe3dbdc7026521da8576574c55120782f6cChris Banes     * drawable, if specified.
1889840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
1899840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @return the blending mode used to apply the tint to the background
1909840efe3dbdc7026521da8576574c55120782f6cChris Banes     *         drawable
1919840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @see #setBackgroundTintMode(PorterDuff.Mode)
1929840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
1939840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Nullable
1949840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
1959840efe3dbdc7026521da8576574c55120782f6cChris Banes    public PorterDuff.Mode getBackgroundTintMode() {
1969840efe3dbdc7026521da8576574c55120782f6cChris Banes        return mBackgroundTintMode;
1979840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
1989840efe3dbdc7026521da8576574c55120782f6cChris Banes
1999840efe3dbdc7026521da8576574c55120782f6cChris Banes    /**
2009840efe3dbdc7026521da8576574c55120782f6cChris Banes     * Specifies the blending mode used to apply the tint specified by
2019840efe3dbdc7026521da8576574c55120782f6cChris Banes     * {@link #setBackgroundTintList(ColorStateList)}} to the background
2029840efe3dbdc7026521da8576574c55120782f6cChris Banes     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2039840efe3dbdc7026521da8576574c55120782f6cChris Banes     *
2049840efe3dbdc7026521da8576574c55120782f6cChris Banes     * @param tintMode the blending mode used to apply the tint, may be
2059840efe3dbdc7026521da8576574c55120782f6cChris Banes     *                 {@code null} to clear tint
2069840efe3dbdc7026521da8576574c55120782f6cChris Banes     */
2079840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
2089840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.setBackgroundTintMode(tintMode);
2099840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2109840efe3dbdc7026521da8576574c55120782f6cChris Banes
2119840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
2129840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void setBackgroundDrawable(Drawable background) {
213d039e3555848f678a2e5363e99026df322d02044Taeho Kim        if (mImpl != null) {
214d039e3555848f678a2e5363e99026df322d02044Taeho Kim            mImpl.setBackgroundDrawable(
215cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes                background, mBackgroundTint, mBackgroundTintMode, mRippleColor, mBorderWidth);
216d039e3555848f678a2e5363e99026df322d02044Taeho Kim        }
2179840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2189840efe3dbdc7026521da8576574c55120782f6cChris Banes
2199840efe3dbdc7026521da8576574c55120782f6cChris Banes    final int getSizeDimension() {
2209840efe3dbdc7026521da8576574c55120782f6cChris Banes        switch (mSize) {
2219840efe3dbdc7026521da8576574c55120782f6cChris Banes            case SIZE_MINI:
2229840efe3dbdc7026521da8576574c55120782f6cChris Banes                return getResources().getDimensionPixelSize(R.dimen.fab_size_mini);
2239840efe3dbdc7026521da8576574c55120782f6cChris Banes            case SIZE_NORMAL:
2249840efe3dbdc7026521da8576574c55120782f6cChris Banes            default:
2259840efe3dbdc7026521da8576574c55120782f6cChris Banes                return getResources().getDimensionPixelSize(R.dimen.fab_size_normal);
2269840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
2279840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2289840efe3dbdc7026521da8576574c55120782f6cChris Banes
2299840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
2309840efe3dbdc7026521da8576574c55120782f6cChris Banes    protected void drawableStateChanged() {
2319840efe3dbdc7026521da8576574c55120782f6cChris Banes        super.drawableStateChanged();
2329840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.onDrawableStateChanged(getDrawableState());
2339840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2349840efe3dbdc7026521da8576574c55120782f6cChris Banes
2359840efe3dbdc7026521da8576574c55120782f6cChris Banes    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
2369840efe3dbdc7026521da8576574c55120782f6cChris Banes    @Override
2379840efe3dbdc7026521da8576574c55120782f6cChris Banes    public void jumpDrawablesToCurrentState() {
2389840efe3dbdc7026521da8576574c55120782f6cChris Banes        super.jumpDrawablesToCurrentState();
2399840efe3dbdc7026521da8576574c55120782f6cChris Banes        mImpl.jumpDrawableToCurrentState();
2409840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2419840efe3dbdc7026521da8576574c55120782f6cChris Banes
2429840efe3dbdc7026521da8576574c55120782f6cChris Banes    private static int resolveAdjustedSize(int desiredSize, int measureSpec) {
2439840efe3dbdc7026521da8576574c55120782f6cChris Banes        int result = desiredSize;
2449840efe3dbdc7026521da8576574c55120782f6cChris Banes        int specMode = MeasureSpec.getMode(measureSpec);
2459840efe3dbdc7026521da8576574c55120782f6cChris Banes        int specSize = MeasureSpec.getSize(measureSpec);
2469840efe3dbdc7026521da8576574c55120782f6cChris Banes        switch (specMode) {
2479840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.UNSPECIFIED:
2489840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Parent says we can be as big as we want. Just don't be larger
2499840efe3dbdc7026521da8576574c55120782f6cChris Banes                // than max size imposed on ourselves.
2509840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = desiredSize;
2519840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
2529840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.AT_MOST:
2539840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Parent says we can be as big as we want, up to specSize.
2549840efe3dbdc7026521da8576574c55120782f6cChris Banes                // Don't be larger than specSize, and don't be larger than
2559840efe3dbdc7026521da8576574c55120782f6cChris Banes                // the max size imposed on ourselves.
2569840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = Math.min(desiredSize, specSize);
2579840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
2589840efe3dbdc7026521da8576574c55120782f6cChris Banes            case MeasureSpec.EXACTLY:
2599840efe3dbdc7026521da8576574c55120782f6cChris Banes                // No choice. Do what we are told.
2609840efe3dbdc7026521da8576574c55120782f6cChris Banes                result = specSize;
2619840efe3dbdc7026521da8576574c55120782f6cChris Banes                break;
2629840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
2639840efe3dbdc7026521da8576574c55120782f6cChris Banes        return result;
2649840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
2659840efe3dbdc7026521da8576574c55120782f6cChris Banes
2669840efe3dbdc7026521da8576574c55120782f6cChris Banes    static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
2679840efe3dbdc7026521da8576574c55120782f6cChris Banes        switch (value) {
2689840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 3:
2699840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_OVER;
2709840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 5:
2719840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_IN;
2729840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 9:
2739840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SRC_ATOP;
2749840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 14:
2759840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.MULTIPLY;
2769840efe3dbdc7026521da8576574c55120782f6cChris Banes            case 15:
2779840efe3dbdc7026521da8576574c55120782f6cChris Banes                return PorterDuff.Mode.SCREEN;
2789840efe3dbdc7026521da8576574c55120782f6cChris Banes            default:
2799840efe3dbdc7026521da8576574c55120782f6cChris Banes                return defaultMode;
2809840efe3dbdc7026521da8576574c55120782f6cChris Banes        }
2819840efe3dbdc7026521da8576574c55120782f6cChris Banes    }
28214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes
28314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes    /**
284b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * Behavior designed for use with {@link FloatingActionButton} instances. It's main function
285b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do
286b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     * not cover them.
287b7f9224b1495db47eb8fd813b5912250e900770aChris Banes     */
288b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
289b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is
290b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        // because we can use view translation properties which greatly simplifies the code.
291b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
292b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
293a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private Rect mTmpRect;
294a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private boolean mIsAnimatingOut;
295b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        private float mTranslationY;
296b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
297b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        @Override
298b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        public boolean layoutDependsOn(CoordinatorLayout parent,
299b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                FloatingActionButton child,
300b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                View dependency) {
301a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            // We're dependent on all SnackbarLayouts (if enabled)
302a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
303b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
304b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
305b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        @Override
306b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
307a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                View dependency) {
308a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            if (dependency instanceof Snackbar.SnackbarLayout) {
309a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                updateFabTranslationForSnackbar(parent, child, dependency);
310a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            } else if (dependency instanceof AppBarLayout) {
311a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                final AppBarLayout appBarLayout = (AppBarLayout) dependency;
312a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                if (mTmpRect == null) {
313a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    mTmpRect = new Rect();
314a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                }
315a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
316a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                // First, let's get the visible rect of the dependency
317a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                final Rect rect = mTmpRect;
318a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                ViewGroupUtils.getDescendantRect(parent, dependency, rect);
319a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
320a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
321a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    // If the anchor's bottom is below the seam, we'll animate our FAB out
322a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    if (!mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
323a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                        animateOut(child);
324a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    }
325a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                } else {
326a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    // Else, we'll animate our FAB back in
327a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    if (child.getVisibility() != View.VISIBLE) {
328a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                        animateIn(child);
329a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    }
330a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                }
331a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            }
332b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            return false;
333b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
334b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
335a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
336a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                FloatingActionButton fab, View snackbar) {
337a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            final float translationY = getFabTranslationYForSnackbar(parent, fab);
338b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            if (translationY != mTranslationY) {
339b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                // First, cancel any current animation
340b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                ViewCompat.animate(fab).cancel();
341b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
342b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                if (Math.abs(translationY - mTranslationY) == snackbar.getHeight()) {
343b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                    // If we're travelling by the height of the Snackbar then we probably need to
344b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                    // animate to the value
345a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    ViewCompat.animate(fab)
346a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                            .translationY(translationY)
347a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                            .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
348a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                            .setListener(null);
349b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                } else {
350b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                    // Else we'll set use setTranslationY
351b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                    ViewCompat.setTranslationY(fab, translationY);
352b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                }
353b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                mTranslationY = translationY;
354b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
355b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
356b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
357a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
358a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                FloatingActionButton fab) {
359b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            float minOffset = 0;
360a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            final List<View> dependencies = parent.getDependencies(fab);
361a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            for (int i = 0, z = dependencies.size(); i < z; i++) {
362a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                final View view = dependencies.get(i);
363a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
364a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                    minOffset = Math.min(minOffset,
365a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes                            ViewCompat.getTranslationY(view) - view.getHeight());
366b7f9224b1495db47eb8fd813b5912250e900770aChris Banes                }
367b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            }
368a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
369b7f9224b1495db47eb8fd813b5912250e900770aChris Banes            return minOffset;
370b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
371b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
372a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes        private void animateIn(FloatingActionButton button) {
373a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes            button.setVisibility(View.VISIBLE);
374a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes
375631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes            if (Build.VERSION.SDK_INT >= 14) {
376631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                ViewCompat.animate(button)
377631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .scaleX(1f)
378631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .scaleY(1f)
379631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .alpha(1f)
380631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
381631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .withLayer()
382631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .setListener(null)
383631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .start();
384631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes            } else {
385631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                Animation anim = android.view.animation.AnimationUtils.loadAnimation(
386631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        button.getContext(), R.anim.fab_in);
387631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                anim.setDuration(200);
388631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
389631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                button.startAnimation(anim);
390631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes            }
391b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
392b7f9224b1495db47eb8fd813b5912250e900770aChris Banes
393631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes        private void animateOut(final FloatingActionButton button) {
394631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes            if (Build.VERSION.SDK_INT >= 14) {
395631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                ViewCompat.animate(button)
396631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .scaleX(0f)
397631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .scaleY(0f)
398631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .alpha(0f)
399631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
400631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .withLayer()
401631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        .setListener(new ViewPropertyAnimatorListener() {
402631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            @Override
403631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            public void onAnimationStart(View view) {
404631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                                mIsAnimatingOut = true;
405631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            }
406631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes
407631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            @Override
408631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            public void onAnimationCancel(View view) {
409631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                                mIsAnimatingOut = false;
410631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            }
411631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes
412631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            @Override
413631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            public void onAnimationEnd(View view) {
414631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                                mIsAnimatingOut = false;
415631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                                view.setVisibility(View.GONE);
416631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                            }
417631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        }).start();
418631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes            } else {
419631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                Animation anim = android.view.animation.AnimationUtils.loadAnimation(
420631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        button.getContext(), R.anim.fab_out);
421631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
422631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                anim.setDuration(200);
423631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() {
424631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                    @Override
425631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                    public void onAnimationStart(Animation animation) {
426631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        mIsAnimatingOut = true;
427631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                    }
428631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes
429631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                    @Override
430631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                    public void onAnimationEnd(Animation animation) {
431631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        mIsAnimatingOut = false;
432631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                        button.setVisibility(View.GONE);
433631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                    }
434631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                });
435631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes                button.startAnimation(anim);
436631f64ec9c6255f38a7f746d7949b6a537c1180fChris Banes            }
437b7f9224b1495db47eb8fd813b5912250e900770aChris Banes        }
438b7f9224b1495db47eb8fd813b5912250e900770aChris Banes    }
4399840efe3dbdc7026521da8576574c55120782f6cChris Banes}
440