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