FloatingActionButton.java revision a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9
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; 289840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.annotation.Nullable; 299840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.support.design.R; 300ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Weiimport android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener; 31b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport android.support.v4.view.ViewCompat; 329840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.util.AttributeSet; 33097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banesimport android.util.Log; 3414d064edb3e4a16a3b90a4a850560177bea1e60dChris Banesimport android.view.View; 359840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.widget.ImageView; 369840efe3dbdc7026521da8576574c55120782f6cChris Banes 37a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banesimport java.util.List; 38b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 399840efe3dbdc7026521da8576574c55120782f6cChris Banes/** 409840efe3dbdc7026521da8576574c55120782f6cChris Banes * Floating action buttons are used for a special type of promoted action. They are distinguished 4114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes * by a circled icon floating above the UI and have special motion behaviors related to morphing, 429840efe3dbdc7026521da8576574c55120782f6cChris Banes * launching, and the transferring anchor point. 439840efe3dbdc7026521da8576574c55120782f6cChris Banes * 449fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>Floating action buttons come in two sizes: the default and the mini. The size can be 459fb154338a62edc2c57dc036895199d6f1769400Chris Banes * controlled with the {@code fabSize} attribute.</p> 469fb154338a62edc2c57dc036895199d6f1769400Chris Banes * 479fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>As this class descends from {@link ImageView}, you can control the icon which is displayed 489fb154338a62edc2c57dc036895199d6f1769400Chris Banes * via {@link #setImageDrawable(Drawable)}.</p> 499fb154338a62edc2c57dc036895199d6f1769400Chris Banes * 509fb154338a62edc2c57dc036895199d6f1769400Chris Banes * <p>The background color of this view defaults to the your theme's {@code colorAccent}. If you 519fb154338a62edc2c57dc036895199d6f1769400Chris Banes * wish to change this at runtime then you can do so via 529fb154338a62edc2c57dc036895199d6f1769400Chris Banes * {@link #setBackgroundTintList(ColorStateList)}.</p> 539fb154338a62edc2c57dc036895199d6f1769400Chris Banes * 549fb154338a62edc2c57dc036895199d6f1769400Chris Banes * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize 559840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 56b7f9224b1495db47eb8fd813b5912250e900770aChris Banes@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class) 57fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banespublic class FloatingActionButton extends VisibilityAwareImageButton { 589840efe3dbdc7026521da8576574c55120782f6cChris Banes 59097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes private static final String LOG_TAG = "FloatingActionButton"; 60097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes 610ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei /** 620ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * Callback to be invoked when the visibility of a FloatingActionButton changes. 630ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei */ 640ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei public abstract static class OnVisibilityChangedListener { 650ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei /** 660ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * Called when a FloatingActionButton has been 670ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * {@link #show(OnVisibilityChangedListener) shown}. 680ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * 690ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * @param fab the FloatingActionButton that was shown. 700ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei */ 710ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei public void onShown(FloatingActionButton fab) {} 720ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei 730ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei /** 740ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * Called when a FloatingActionButton has been 750ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * {@link #hide(OnVisibilityChangedListener) hidden}. 760ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * 770ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * @param fab the FloatingActionButton that was hidden. 780ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei */ 790ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei public void onHidden(FloatingActionButton fab) {} 800ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei } 810ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei 829840efe3dbdc7026521da8576574c55120782f6cChris Banes // These values must match those in the attrs declaration 839840efe3dbdc7026521da8576574c55120782f6cChris Banes private static final int SIZE_MINI = 1; 849840efe3dbdc7026521da8576574c55120782f6cChris Banes private static final int SIZE_NORMAL = 0; 859840efe3dbdc7026521da8576574c55120782f6cChris Banes 869840efe3dbdc7026521da8576574c55120782f6cChris Banes private ColorStateList mBackgroundTint; 879840efe3dbdc7026521da8576574c55120782f6cChris Banes private PorterDuff.Mode mBackgroundTintMode; 889840efe3dbdc7026521da8576574c55120782f6cChris Banes 89cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes private int mBorderWidth; 909840efe3dbdc7026521da8576574c55120782f6cChris Banes private int mRippleColor; 919840efe3dbdc7026521da8576574c55120782f6cChris Banes private int mSize; 929840efe3dbdc7026521da8576574c55120782f6cChris Banes private int mContentPadding; 939840efe3dbdc7026521da8576574c55120782f6cChris Banes 949840efe3dbdc7026521da8576574c55120782f6cChris Banes private final Rect mShadowPadding; 959840efe3dbdc7026521da8576574c55120782f6cChris Banes 969840efe3dbdc7026521da8576574c55120782f6cChris Banes private final FloatingActionButtonImpl mImpl; 979840efe3dbdc7026521da8576574c55120782f6cChris Banes 989840efe3dbdc7026521da8576574c55120782f6cChris Banes public FloatingActionButton(Context context) { 999840efe3dbdc7026521da8576574c55120782f6cChris Banes this(context, null); 1009840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1019840efe3dbdc7026521da8576574c55120782f6cChris Banes 1029840efe3dbdc7026521da8576574c55120782f6cChris Banes public FloatingActionButton(Context context, AttributeSet attrs) { 1039840efe3dbdc7026521da8576574c55120782f6cChris Banes this(context, attrs, 0); 1049840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1059840efe3dbdc7026521da8576574c55120782f6cChris Banes 1069840efe3dbdc7026521da8576574c55120782f6cChris Banes public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { 1079840efe3dbdc7026521da8576574c55120782f6cChris Banes super(context, attrs, defStyleAttr); 1089840efe3dbdc7026521da8576574c55120782f6cChris Banes 109809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes ThemeUtils.checkAppCompatTheme(context); 110809bb62055ad42b88f3a69308be222801b89fbd9Chris Banes 1119840efe3dbdc7026521da8576574c55120782f6cChris Banes mShadowPadding = new Rect(); 1129840efe3dbdc7026521da8576574c55120782f6cChris Banes 1139840efe3dbdc7026521da8576574c55120782f6cChris Banes TypedArray a = context.obtainStyledAttributes(attrs, 1149840efe3dbdc7026521da8576574c55120782f6cChris Banes R.styleable.FloatingActionButton, defStyleAttr, 1159840efe3dbdc7026521da8576574c55120782f6cChris Banes R.style.Widget_Design_FloatingActionButton); 1169840efe3dbdc7026521da8576574c55120782f6cChris Banes mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint); 1179840efe3dbdc7026521da8576574c55120782f6cChris Banes mBackgroundTintMode = parseTintMode(a.getInt( 1189840efe3dbdc7026521da8576574c55120782f6cChris Banes R.styleable.FloatingActionButton_backgroundTintMode, -1), null); 1199840efe3dbdc7026521da8576574c55120782f6cChris Banes mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0); 1209840efe3dbdc7026521da8576574c55120782f6cChris Banes mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL); 121cd78954a2b32d9c22686f12c194fac7e49566cf6Chris Banes mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0); 1229840efe3dbdc7026521da8576574c55120782f6cChris Banes final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f); 1239840efe3dbdc7026521da8576574c55120782f6cChris Banes final float pressedTranslationZ = a.getDimension( 1249840efe3dbdc7026521da8576574c55120782f6cChris Banes R.styleable.FloatingActionButton_pressedTranslationZ, 0f); 1259840efe3dbdc7026521da8576574c55120782f6cChris Banes a.recycle(); 1269840efe3dbdc7026521da8576574c55120782f6cChris Banes 1279840efe3dbdc7026521da8576574c55120782f6cChris Banes final ShadowViewDelegate delegate = new ShadowViewDelegate() { 1289840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1299840efe3dbdc7026521da8576574c55120782f6cChris Banes public float getRadius() { 1309840efe3dbdc7026521da8576574c55120782f6cChris Banes return getSizeDimension() / 2f; 1319840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1329840efe3dbdc7026521da8576574c55120782f6cChris Banes 1339840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1349840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setShadowPadding(int left, int top, int right, int bottom) { 1359840efe3dbdc7026521da8576574c55120782f6cChris Banes mShadowPadding.set(left, top, right, bottom); 1369840efe3dbdc7026521da8576574c55120782f6cChris Banes 1379840efe3dbdc7026521da8576574c55120782f6cChris Banes setPadding(left + mContentPadding, top + mContentPadding, 1389840efe3dbdc7026521da8576574c55120782f6cChris Banes right + mContentPadding, bottom + mContentPadding); 1399840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1409840efe3dbdc7026521da8576574c55120782f6cChris Banes 1419840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1429840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setBackgroundDrawable(Drawable background) { 1439840efe3dbdc7026521da8576574c55120782f6cChris Banes FloatingActionButton.super.setBackgroundDrawable(background); 1449840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1459840efe3dbdc7026521da8576574c55120782f6cChris Banes }; 1469840efe3dbdc7026521da8576574c55120782f6cChris Banes 147be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes final int sdk = Build.VERSION.SDK_INT; 148be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes if (sdk >= 21) { 1499840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl = new FloatingActionButtonLollipop(this, delegate); 1509ec922c215ed95a4bbd8bd7940e81dcfe6926893Chris Banes } else if (sdk >= 14) { 1519ec922c215ed95a4bbd8bd7940e81dcfe6926893Chris Banes mImpl = new FloatingActionButtonIcs(this, delegate); 1529840efe3dbdc7026521da8576574c55120782f6cChris Banes } else { 1539840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl = new FloatingActionButtonEclairMr1(this, delegate); 1549840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1559840efe3dbdc7026521da8576574c55120782f6cChris Banes 156a577676a64e5353b8ec927117151aa6be84adf66Chris Banes final int maxContentSize = (int) getResources().getDimension( 157a577676a64e5353b8ec927117151aa6be84adf66Chris Banes R.dimen.design_fab_content_size); 1589840efe3dbdc7026521da8576574c55120782f6cChris Banes mContentPadding = (getSizeDimension() - maxContentSize) / 2; 1599840efe3dbdc7026521da8576574c55120782f6cChris Banes 160097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes mImpl.setBackgroundDrawable(mBackgroundTint, mBackgroundTintMode, 161097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes mRippleColor, mBorderWidth); 1629840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setElevation(elevation); 1639840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setPressedTranslationZ(pressedTranslationZ); 1649840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1659840efe3dbdc7026521da8576574c55120782f6cChris Banes 1669840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1679840efe3dbdc7026521da8576574c55120782f6cChris Banes protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1689840efe3dbdc7026521da8576574c55120782f6cChris Banes final int preferredSize = getSizeDimension(); 1699840efe3dbdc7026521da8576574c55120782f6cChris Banes 1709840efe3dbdc7026521da8576574c55120782f6cChris Banes final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec); 1719840efe3dbdc7026521da8576574c55120782f6cChris Banes final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec); 1729840efe3dbdc7026521da8576574c55120782f6cChris Banes 1739840efe3dbdc7026521da8576574c55120782f6cChris Banes // As we want to stay circular, we set both dimensions to be the 1749840efe3dbdc7026521da8576574c55120782f6cChris Banes // smallest resolved dimension 1759840efe3dbdc7026521da8576574c55120782f6cChris Banes final int d = Math.min(w, h); 1769840efe3dbdc7026521da8576574c55120782f6cChris Banes 1779840efe3dbdc7026521da8576574c55120782f6cChris Banes // We add the shadow's padding to the measured dimension 1789840efe3dbdc7026521da8576574c55120782f6cChris Banes setMeasuredDimension( 1799840efe3dbdc7026521da8576574c55120782f6cChris Banes d + mShadowPadding.left + mShadowPadding.right, 1809840efe3dbdc7026521da8576574c55120782f6cChris Banes d + mShadowPadding.top + mShadowPadding.bottom); 1819840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1829840efe3dbdc7026521da8576574c55120782f6cChris Banes 1839840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 1849840efe3dbdc7026521da8576574c55120782f6cChris Banes * Set the ripple color for this {@link FloatingActionButton}. 1859840efe3dbdc7026521da8576574c55120782f6cChris Banes * <p> 1869840efe3dbdc7026521da8576574c55120782f6cChris Banes * When running on devices with KitKat or below, we draw a fill rather than a ripple. 1879840efe3dbdc7026521da8576574c55120782f6cChris Banes * 1889840efe3dbdc7026521da8576574c55120782f6cChris Banes * @param color ARGB color to use for the ripple. 1899840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 1907a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes public void setRippleColor(@ColorInt int color) { 1919840efe3dbdc7026521da8576574c55120782f6cChris Banes if (mRippleColor != color) { 1929840efe3dbdc7026521da8576574c55120782f6cChris Banes mRippleColor = color; 1939840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setRippleColor(color); 1949840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1959840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1969840efe3dbdc7026521da8576574c55120782f6cChris Banes 1979840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 1989840efe3dbdc7026521da8576574c55120782f6cChris Banes * Return the tint applied to the background drawable, if specified. 1999840efe3dbdc7026521da8576574c55120782f6cChris Banes * 2009840efe3dbdc7026521da8576574c55120782f6cChris Banes * @return the tint applied to the background drawable 2019840efe3dbdc7026521da8576574c55120782f6cChris Banes * @see #setBackgroundTintList(ColorStateList) 2029840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 2039840efe3dbdc7026521da8576574c55120782f6cChris Banes @Nullable 2049840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 2059840efe3dbdc7026521da8576574c55120782f6cChris Banes public ColorStateList getBackgroundTintList() { 2069840efe3dbdc7026521da8576574c55120782f6cChris Banes return mBackgroundTint; 2079840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2089840efe3dbdc7026521da8576574c55120782f6cChris Banes 2099840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 2109840efe3dbdc7026521da8576574c55120782f6cChris Banes * Applies a tint to the background drawable. Does not modify the current tint 2119840efe3dbdc7026521da8576574c55120782f6cChris Banes * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 2129840efe3dbdc7026521da8576574c55120782f6cChris Banes * 2139840efe3dbdc7026521da8576574c55120782f6cChris Banes * @param tint the tint to apply, may be {@code null} to clear tint 2149840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 2159840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setBackgroundTintList(@Nullable ColorStateList tint) { 2167a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes if (mBackgroundTint != tint) { 2177a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes mBackgroundTint = tint; 2187a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes mImpl.setBackgroundTintList(tint); 2197a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes } 2209840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2219840efe3dbdc7026521da8576574c55120782f6cChris Banes 2229840efe3dbdc7026521da8576574c55120782f6cChris Banes 2239840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 2249840efe3dbdc7026521da8576574c55120782f6cChris Banes * Return the blending mode used to apply the tint to the background 2259840efe3dbdc7026521da8576574c55120782f6cChris Banes * drawable, if specified. 2269840efe3dbdc7026521da8576574c55120782f6cChris Banes * 2279840efe3dbdc7026521da8576574c55120782f6cChris Banes * @return the blending mode used to apply the tint to the background 2289840efe3dbdc7026521da8576574c55120782f6cChris Banes * drawable 2299840efe3dbdc7026521da8576574c55120782f6cChris Banes * @see #setBackgroundTintMode(PorterDuff.Mode) 2309840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 2319840efe3dbdc7026521da8576574c55120782f6cChris Banes @Nullable 2329840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 2339840efe3dbdc7026521da8576574c55120782f6cChris Banes public PorterDuff.Mode getBackgroundTintMode() { 2349840efe3dbdc7026521da8576574c55120782f6cChris Banes return mBackgroundTintMode; 2359840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2369840efe3dbdc7026521da8576574c55120782f6cChris Banes 2379840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 2389840efe3dbdc7026521da8576574c55120782f6cChris Banes * Specifies the blending mode used to apply the tint specified by 2399840efe3dbdc7026521da8576574c55120782f6cChris Banes * {@link #setBackgroundTintList(ColorStateList)}} to the background 2409840efe3dbdc7026521da8576574c55120782f6cChris Banes * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. 2419840efe3dbdc7026521da8576574c55120782f6cChris Banes * 2429840efe3dbdc7026521da8576574c55120782f6cChris Banes * @param tintMode the blending mode used to apply the tint, may be 2439840efe3dbdc7026521da8576574c55120782f6cChris Banes * {@code null} to clear tint 2449840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 2459840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { 2467a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes if (mBackgroundTintMode != tintMode) { 2477a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes mBackgroundTintMode = tintMode; 2487a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes mImpl.setBackgroundTintMode(tintMode); 2497a13c8489daca7915623dd673df49de2d1a0bf30Chris Banes } 2509840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2519840efe3dbdc7026521da8576574c55120782f6cChris Banes 2529840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 253097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes public void setBackgroundDrawable(Drawable background) { 254097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes Log.i(LOG_TAG, "Setting a custom background is not supported."); 255097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes } 256097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes 257097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes @Override 258097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes public void setBackgroundResource(int resid) { 259097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes Log.i(LOG_TAG, "Setting a custom background is not supported."); 260097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes } 261097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes 262097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes @Override 263097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes public void setBackgroundColor(int color) { 264097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8Chris Banes Log.i(LOG_TAG, "Setting a custom background is not supported."); 265be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 266be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes 267be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes /** 268be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes * Shows the button. 2698c05e5f52fbc790b745e768398d9e69d6b9d9ee1Chris Banes * <p>This method will animate the button show if the view has already been laid out.</p> 270be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes */ 271be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes public void show() { 272fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes show(null); 2730ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei } 2740ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei 2750ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei /** 2760ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * Shows the button. 2778c05e5f52fbc790b745e768398d9e69d6b9d9ee1Chris Banes * <p>This method will animate the button show if the view has already been laid out.</p> 2780ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * 2790ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * @param listener the listener to notify when this view is shown 2800ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei */ 2810ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei public void show(@Nullable final OnVisibilityChangedListener listener) { 282fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes show(listener, true); 283fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes } 284fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes 285fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes private void show(OnVisibilityChangedListener listener, boolean fromUser) { 286fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes mImpl.show(wrapOnVisibilityChangedListener(listener), fromUser); 287be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 288be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes 289be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes /** 290be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes * Hides the button. 291be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes * <p>This method will animate the button hide if the view has already been laid out.</p> 292be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes */ 293be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes public void hide() { 294fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes hide(null); 2950ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei } 2960ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei 2970ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei /** 2980ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * Hides the button. 2990ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * <p>This method will animate the button hide if the view has already been laid out.</p> 3000ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * 3010ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei * @param listener the listener to notify when this view is hidden 3020ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei */ 3030ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei public void hide(@Nullable OnVisibilityChangedListener listener) { 304fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes hide(listener, true); 305fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes } 306fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes 307fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes private void hide(@Nullable OnVisibilityChangedListener listener, boolean fromUser) { 308fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes mImpl.hide(wrapOnVisibilityChangedListener(listener), fromUser); 3090ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei } 3100ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei 3110ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei @Nullable 3120ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei private InternalVisibilityChangedListener wrapOnVisibilityChangedListener( 3130ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei @Nullable final OnVisibilityChangedListener listener) { 3140ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei if (listener == null) { 3150ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei return null; 3160ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei } 3170ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei 3180ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei return new InternalVisibilityChangedListener() { 3190ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei @Override 3200ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei public void onShown() { 3210ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei listener.onShown(FloatingActionButton.this); 3220ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei } 3230ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei 3240ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei @Override 3250ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei public void onHidden() { 3260ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei listener.onHidden(FloatingActionButton.this); 3270ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei } 3280ad7ef59b28d8ffafd551d2756b5a8ec47c90682Mark Wei }; 3299840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3309840efe3dbdc7026521da8576574c55120782f6cChris Banes 3319840efe3dbdc7026521da8576574c55120782f6cChris Banes final int getSizeDimension() { 3329840efe3dbdc7026521da8576574c55120782f6cChris Banes switch (mSize) { 3339840efe3dbdc7026521da8576574c55120782f6cChris Banes case SIZE_MINI: 334a577676a64e5353b8ec927117151aa6be84adf66Chris Banes return getResources().getDimensionPixelSize(R.dimen.design_fab_size_mini); 3359840efe3dbdc7026521da8576574c55120782f6cChris Banes case SIZE_NORMAL: 3369840efe3dbdc7026521da8576574c55120782f6cChris Banes default: 337a577676a64e5353b8ec927117151aa6be84adf66Chris Banes return getResources().getDimensionPixelSize(R.dimen.design_fab_size_normal); 3389840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3399840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3409840efe3dbdc7026521da8576574c55120782f6cChris Banes 3419840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 342d9770e12c8ff2d4417700492c6616572be897e93Chris Banes protected void onAttachedToWindow() { 343d9770e12c8ff2d4417700492c6616572be897e93Chris Banes super.onAttachedToWindow(); 344d9770e12c8ff2d4417700492c6616572be897e93Chris Banes mImpl.onAttachedToWindow(); 345d9770e12c8ff2d4417700492c6616572be897e93Chris Banes } 346d9770e12c8ff2d4417700492c6616572be897e93Chris Banes 347d9770e12c8ff2d4417700492c6616572be897e93Chris Banes @Override 348d9770e12c8ff2d4417700492c6616572be897e93Chris Banes protected void onDetachedFromWindow() { 349d9770e12c8ff2d4417700492c6616572be897e93Chris Banes super.onDetachedFromWindow(); 350d9770e12c8ff2d4417700492c6616572be897e93Chris Banes mImpl.onDetachedFromWindow(); 351d9770e12c8ff2d4417700492c6616572be897e93Chris Banes } 352d9770e12c8ff2d4417700492c6616572be897e93Chris Banes 353d9770e12c8ff2d4417700492c6616572be897e93Chris Banes @Override 3549840efe3dbdc7026521da8576574c55120782f6cChris Banes protected void drawableStateChanged() { 3559840efe3dbdc7026521da8576574c55120782f6cChris Banes super.drawableStateChanged(); 3569840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.onDrawableStateChanged(getDrawableState()); 3579840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3589840efe3dbdc7026521da8576574c55120782f6cChris Banes 3599840efe3dbdc7026521da8576574c55120782f6cChris Banes @TargetApi(Build.VERSION_CODES.HONEYCOMB) 3609840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 3619840efe3dbdc7026521da8576574c55120782f6cChris Banes public void jumpDrawablesToCurrentState() { 3629840efe3dbdc7026521da8576574c55120782f6cChris Banes super.jumpDrawablesToCurrentState(); 3639840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.jumpDrawableToCurrentState(); 3649840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3659840efe3dbdc7026521da8576574c55120782f6cChris Banes 3669840efe3dbdc7026521da8576574c55120782f6cChris Banes private static int resolveAdjustedSize(int desiredSize, int measureSpec) { 3679840efe3dbdc7026521da8576574c55120782f6cChris Banes int result = desiredSize; 3689840efe3dbdc7026521da8576574c55120782f6cChris Banes int specMode = MeasureSpec.getMode(measureSpec); 3699840efe3dbdc7026521da8576574c55120782f6cChris Banes int specSize = MeasureSpec.getSize(measureSpec); 3709840efe3dbdc7026521da8576574c55120782f6cChris Banes switch (specMode) { 3719840efe3dbdc7026521da8576574c55120782f6cChris Banes case MeasureSpec.UNSPECIFIED: 3729840efe3dbdc7026521da8576574c55120782f6cChris Banes // Parent says we can be as big as we want. Just don't be larger 3739840efe3dbdc7026521da8576574c55120782f6cChris Banes // than max size imposed on ourselves. 3749840efe3dbdc7026521da8576574c55120782f6cChris Banes result = desiredSize; 3759840efe3dbdc7026521da8576574c55120782f6cChris Banes break; 3769840efe3dbdc7026521da8576574c55120782f6cChris Banes case MeasureSpec.AT_MOST: 3779840efe3dbdc7026521da8576574c55120782f6cChris Banes // Parent says we can be as big as we want, up to specSize. 3789840efe3dbdc7026521da8576574c55120782f6cChris Banes // Don't be larger than specSize, and don't be larger than 3799840efe3dbdc7026521da8576574c55120782f6cChris Banes // the max size imposed on ourselves. 3809840efe3dbdc7026521da8576574c55120782f6cChris Banes result = Math.min(desiredSize, specSize); 3819840efe3dbdc7026521da8576574c55120782f6cChris Banes break; 3829840efe3dbdc7026521da8576574c55120782f6cChris Banes case MeasureSpec.EXACTLY: 3839840efe3dbdc7026521da8576574c55120782f6cChris Banes // No choice. Do what we are told. 3849840efe3dbdc7026521da8576574c55120782f6cChris Banes result = specSize; 3859840efe3dbdc7026521da8576574c55120782f6cChris Banes break; 3869840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3879840efe3dbdc7026521da8576574c55120782f6cChris Banes return result; 3889840efe3dbdc7026521da8576574c55120782f6cChris Banes } 3899840efe3dbdc7026521da8576574c55120782f6cChris Banes 3909840efe3dbdc7026521da8576574c55120782f6cChris Banes static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) { 3919840efe3dbdc7026521da8576574c55120782f6cChris Banes switch (value) { 3929840efe3dbdc7026521da8576574c55120782f6cChris Banes case 3: 3939840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.SRC_OVER; 3949840efe3dbdc7026521da8576574c55120782f6cChris Banes case 5: 3959840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.SRC_IN; 3969840efe3dbdc7026521da8576574c55120782f6cChris Banes case 9: 3979840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.SRC_ATOP; 3989840efe3dbdc7026521da8576574c55120782f6cChris Banes case 14: 3999840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.MULTIPLY; 4009840efe3dbdc7026521da8576574c55120782f6cChris Banes case 15: 4019840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.SCREEN; 4029840efe3dbdc7026521da8576574c55120782f6cChris Banes default: 4039840efe3dbdc7026521da8576574c55120782f6cChris Banes return defaultMode; 4049840efe3dbdc7026521da8576574c55120782f6cChris Banes } 4059840efe3dbdc7026521da8576574c55120782f6cChris Banes } 40614d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 40714d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes /** 408b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Behavior designed for use with {@link FloatingActionButton} instances. It's main function 409b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do 410b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * not cover them. 411b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 412b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> { 413b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is 414b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // because we can use view translation properties which greatly simplifies the code. 415b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11; 416b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 417a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes private ValueAnimatorCompat mFabTranslationYAnimator; 41818d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes private float mFabTranslationY; 419a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private Rect mTmpRect; 420b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 421b7f9224b1495db47eb8fd813b5912250e900770aChris Banes @Override 422b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public boolean layoutDependsOn(CoordinatorLayout parent, 423be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes FloatingActionButton child, View dependency) { 424a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes // We're dependent on all SnackbarLayouts (if enabled) 425a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout; 426b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 427b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 428b7f9224b1495db47eb8fd813b5912250e900770aChris Banes @Override 429b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, 430a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes View dependency) { 431a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (dependency instanceof Snackbar.SnackbarLayout) { 432a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes updateFabTranslationForSnackbar(parent, child, dependency); 433a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } else if (dependency instanceof AppBarLayout) { 434be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // If we're depending on an AppBarLayout we will show/hide it automatically 435be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // if the FAB is anchored to the AppBarLayout 436be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes updateFabVisibility(parent, (AppBarLayout) dependency, child); 437be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 438be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes return false; 439be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 440a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 441be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes private boolean updateFabVisibility(CoordinatorLayout parent, 442be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes AppBarLayout appBarLayout, FloatingActionButton child) { 443be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes final CoordinatorLayout.LayoutParams lp = 444be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 445be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes if (lp.getAnchorId() != appBarLayout.getId()) { 446be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // The anchor ID doesn't match the dependency, so we won't automatically 447be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // show/hide the FAB 448be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes return false; 449be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 450a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 451fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes if (child.getUserSetVisibility() != VISIBLE) { 452fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes // The view isn't set to be visible so skip changing it's visibility 453fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes return false; 454fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes } 455fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes 456be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes if (mTmpRect == null) { 457be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes mTmpRect = new Rect(); 458a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes } 459be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes 460be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // First, let's get the visible rect of the dependency 461be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes final Rect rect = mTmpRect; 462be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect); 463be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes 464be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) { 465be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // If the anchor's bottom is below the seam, we'll animate our FAB out 466fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes child.hide(null, false); 467be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } else { 468be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // Else, we'll animate our FAB back in 469fc780bab91bd4275ae2c3b75c3dfb327e008e4dbChris Banes child.show(null, false); 470be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 471be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes return true; 472b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 473b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 474a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private void updateFabTranslationForSnackbar(CoordinatorLayout parent, 475a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes final FloatingActionButton fab, View snackbar) { 476be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes if (fab.getVisibility() != View.VISIBLE) { 477be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes return; 478be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 479be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes 48018d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes final float targetTransY = getFabTranslationYForSnackbar(parent, fab); 48118d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes if (mFabTranslationY == targetTransY) { 48218d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes // We're already at (or currently animating to) the target value, return... 48318d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes return; 48418d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes } 48518d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes 48618d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes final float currentTransY = ViewCompat.getTranslationY(fab); 48718d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes 488a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes // Make sure that any current animation is cancelled 489a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) { 490a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes mFabTranslationYAnimator.cancel(); 491a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes } 492a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes 493a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes if (Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) { 49418d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes // If the FAB will be travelling by more than 2/3 of it's height, let's animate 49518d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes // it instead 496a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes if (mFabTranslationYAnimator == null) { 497a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes mFabTranslationYAnimator = ViewUtils.createAnimator(); 498a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes mFabTranslationYAnimator.setInterpolator( 499a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 500a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes mFabTranslationYAnimator.setUpdateListener( 501a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes new ValueAnimatorCompat.AnimatorUpdateListener() { 502a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes @Override 503a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes public void onAnimationUpdate(ValueAnimatorCompat animator) { 504a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes ViewCompat.setTranslationY(fab, 505a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes animator.getAnimatedFloatValue()); 506a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes } 507a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes }); 508a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes } 509a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY); 510a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes mFabTranslationYAnimator.start(); 51118d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes } else { 51218d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes // Now update the translation Y 51318d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes ViewCompat.setTranslationY(fab, targetTransY); 51418d22257ccfb5cebb3ccd2450736e735ed1fb9bbChris Banes } 515a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes 516a419ee1ef9aef8b567f1ccd8c29d01ec7bff4cc9Chris Banes mFabTranslationY = targetTransY; 517b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 518b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 519a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes private float getFabTranslationYForSnackbar(CoordinatorLayout parent, 520a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes FloatingActionButton fab) { 521b7f9224b1495db47eb8fd813b5912250e900770aChris Banes float minOffset = 0; 522a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final List<View> dependencies = parent.getDependencies(fab); 523a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes for (int i = 0, z = dependencies.size(); i < z; i++) { 524a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes final View view = dependencies.get(i); 525a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) { 526a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes minOffset = Math.min(minOffset, 527a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes ViewCompat.getTranslationY(view) - view.getHeight()); 528b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 529b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 530a6a508b2296730ca6954aaebcca52a9962a5cb55Chris Banes 531b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return minOffset; 532b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 533b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 534e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes @Override 535e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child, 536e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes int layoutDirection) { 537be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // First, lets make sure that the visibility of the FAB is consistent 538be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes final List<View> dependencies = parent.getDependencies(child); 539be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes for (int i = 0, count = dependencies.size(); i < count; i++) { 540be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes final View dependency = dependencies.get(i); 541be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes if (dependency instanceof AppBarLayout 542be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes && updateFabVisibility(parent, (AppBarLayout) dependency, child)) { 543be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes break; 544be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 545be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes } 546be48ed9161c09c4b2178ab6dbe28638222809fc7Chris Banes // Now let the CoordinatorLayout lay out the FAB 547e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes parent.onLayoutChild(child, layoutDirection); 548e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes // Now offset it if needed 549e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes offsetIfNeeded(parent, child); 550e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes return true; 551e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes } 552e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes 553e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes /** 554e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes * Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method 555e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes * offsets our layout position so that we're positioned correctly if we're on one of 556e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes * our parent's edges. 557e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes */ 558e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) { 559e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes final Rect padding = fab.mShadowPadding; 560e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes 561e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) { 562e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes final CoordinatorLayout.LayoutParams lp = 563e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); 564e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes 565e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes int offsetTB = 0, offsetLR = 0; 566e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes 567e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes if (fab.getRight() >= parent.getWidth() - lp.rightMargin) { 568e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes // If we're on the left edge, shift it the right 569e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes offsetLR = padding.right; 570e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes } else if (fab.getLeft() <= lp.leftMargin) { 571e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes // If we're on the left edge, shift it the left 572e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes offsetLR = -padding.left; 573e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes } 574e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes if (fab.getBottom() >= parent.getBottom() - lp.bottomMargin) { 575e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes // If we're on the bottom edge, shift it down 576e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes offsetTB = padding.bottom; 577e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes } else if (fab.getTop() <= lp.topMargin) { 578e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes // If we're on the top edge, shift it up 579e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes offsetTB = -padding.top; 580e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes } 581e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes 582e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes fab.offsetTopAndBottom(offsetTB); 583e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes fab.offsetLeftAndRight(offsetLR); 584e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes } 585e882ef3492de3d2bb687b454e08b870b06d8f4e2Chris Banes } 586b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 5879840efe3dbdc7026521da8576574c55120782f6cChris Banes} 588