FloatingActionButton.java revision b7f9224b1495db47eb8fd813b5912250e900770a
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; 309840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.util.AttributeSet; 3114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banesimport android.view.View; 3214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banesimport android.view.ViewGroup; 3314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banesimport android.view.ViewParent; 349840efe3dbdc7026521da8576574c55120782f6cChris Banesimport android.widget.ImageView; 359840efe3dbdc7026521da8576574c55120782f6cChris Banes 36b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport java.lang.ref.WeakReference; 37b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport java.util.HashSet; 38b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport java.util.Iterator; 39b7f9224b1495db47eb8fd813b5912250e900770aChris Banesimport java.util.Set; 40b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 419840efe3dbdc7026521da8576574c55120782f6cChris Banes/** 429840efe3dbdc7026521da8576574c55120782f6cChris Banes * Floating action buttons are used for a special type of promoted action. They are distinguished 4314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes * by a circled icon floating above the UI and have special motion behaviors related to morphing, 449840efe3dbdc7026521da8576574c55120782f6cChris Banes * launching, and the transferring anchor point. 459840efe3dbdc7026521da8576574c55120782f6cChris Banes * 469840efe3dbdc7026521da8576574c55120782f6cChris Banes * Floating action buttons come in two sizes: the default, which should be used in most cases, and 479840efe3dbdc7026521da8576574c55120782f6cChris Banes * the mini, which should only be used to create visual continuity with other elements on the 489840efe3dbdc7026521da8576574c55120782f6cChris Banes * screen. 499840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 50b7f9224b1495db47eb8fd813b5912250e900770aChris Banes@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class) 519840efe3dbdc7026521da8576574c55120782f6cChris Banespublic class FloatingActionButton extends ImageView { 529840efe3dbdc7026521da8576574c55120782f6cChris Banes 539840efe3dbdc7026521da8576574c55120782f6cChris Banes // These values must match those in the attrs declaration 549840efe3dbdc7026521da8576574c55120782f6cChris Banes private static final int SIZE_MINI = 1; 559840efe3dbdc7026521da8576574c55120782f6cChris Banes private static final int SIZE_NORMAL = 0; 569840efe3dbdc7026521da8576574c55120782f6cChris Banes 579840efe3dbdc7026521da8576574c55120782f6cChris Banes private ColorStateList mBackgroundTint; 589840efe3dbdc7026521da8576574c55120782f6cChris Banes private PorterDuff.Mode mBackgroundTintMode; 599840efe3dbdc7026521da8576574c55120782f6cChris Banes 609840efe3dbdc7026521da8576574c55120782f6cChris Banes private int mRippleColor; 619840efe3dbdc7026521da8576574c55120782f6cChris Banes private int mSize; 629840efe3dbdc7026521da8576574c55120782f6cChris Banes private int mContentPadding; 639840efe3dbdc7026521da8576574c55120782f6cChris Banes 649840efe3dbdc7026521da8576574c55120782f6cChris Banes private final Rect mShadowPadding; 659840efe3dbdc7026521da8576574c55120782f6cChris Banes 669840efe3dbdc7026521da8576574c55120782f6cChris Banes private final FloatingActionButtonImpl mImpl; 679840efe3dbdc7026521da8576574c55120782f6cChris Banes 689840efe3dbdc7026521da8576574c55120782f6cChris Banes public FloatingActionButton(Context context) { 699840efe3dbdc7026521da8576574c55120782f6cChris Banes this(context, null); 709840efe3dbdc7026521da8576574c55120782f6cChris Banes } 719840efe3dbdc7026521da8576574c55120782f6cChris Banes 729840efe3dbdc7026521da8576574c55120782f6cChris Banes public FloatingActionButton(Context context, AttributeSet attrs) { 739840efe3dbdc7026521da8576574c55120782f6cChris Banes this(context, attrs, 0); 749840efe3dbdc7026521da8576574c55120782f6cChris Banes } 759840efe3dbdc7026521da8576574c55120782f6cChris Banes 769840efe3dbdc7026521da8576574c55120782f6cChris Banes public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { 779840efe3dbdc7026521da8576574c55120782f6cChris Banes super(context, attrs, defStyleAttr); 789840efe3dbdc7026521da8576574c55120782f6cChris Banes 799840efe3dbdc7026521da8576574c55120782f6cChris Banes mShadowPadding = new Rect(); 809840efe3dbdc7026521da8576574c55120782f6cChris Banes 819840efe3dbdc7026521da8576574c55120782f6cChris Banes TypedArray a = context.obtainStyledAttributes(attrs, 829840efe3dbdc7026521da8576574c55120782f6cChris Banes R.styleable.FloatingActionButton, defStyleAttr, 839840efe3dbdc7026521da8576574c55120782f6cChris Banes R.style.Widget_Design_FloatingActionButton); 849840efe3dbdc7026521da8576574c55120782f6cChris Banes Drawable background = a.getDrawable(R.styleable.FloatingActionButton_android_background); 859840efe3dbdc7026521da8576574c55120782f6cChris Banes mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint); 869840efe3dbdc7026521da8576574c55120782f6cChris Banes mBackgroundTintMode = parseTintMode(a.getInt( 879840efe3dbdc7026521da8576574c55120782f6cChris Banes R.styleable.FloatingActionButton_backgroundTintMode, -1), null); 889840efe3dbdc7026521da8576574c55120782f6cChris Banes mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0); 899840efe3dbdc7026521da8576574c55120782f6cChris Banes mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL); 909840efe3dbdc7026521da8576574c55120782f6cChris Banes final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f); 919840efe3dbdc7026521da8576574c55120782f6cChris Banes final float pressedTranslationZ = a.getDimension( 929840efe3dbdc7026521da8576574c55120782f6cChris Banes R.styleable.FloatingActionButton_pressedTranslationZ, 0f); 939840efe3dbdc7026521da8576574c55120782f6cChris Banes a.recycle(); 949840efe3dbdc7026521da8576574c55120782f6cChris Banes 959840efe3dbdc7026521da8576574c55120782f6cChris Banes final ShadowViewDelegate delegate = new ShadowViewDelegate() { 969840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 979840efe3dbdc7026521da8576574c55120782f6cChris Banes public float getRadius() { 989840efe3dbdc7026521da8576574c55120782f6cChris Banes return getSizeDimension() / 2f; 999840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1009840efe3dbdc7026521da8576574c55120782f6cChris Banes 1019840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1029840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setShadowPadding(int left, int top, int right, int bottom) { 1039840efe3dbdc7026521da8576574c55120782f6cChris Banes mShadowPadding.set(left, top, right, bottom); 1049840efe3dbdc7026521da8576574c55120782f6cChris Banes 1059840efe3dbdc7026521da8576574c55120782f6cChris Banes setPadding(left + mContentPadding, top + mContentPadding, 1069840efe3dbdc7026521da8576574c55120782f6cChris Banes right + mContentPadding, bottom + mContentPadding); 1079840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1089840efe3dbdc7026521da8576574c55120782f6cChris Banes 1099840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1109840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setBackgroundDrawable(Drawable background) { 1119840efe3dbdc7026521da8576574c55120782f6cChris Banes FloatingActionButton.super.setBackgroundDrawable(background); 1129840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1139840efe3dbdc7026521da8576574c55120782f6cChris Banes }; 1149840efe3dbdc7026521da8576574c55120782f6cChris Banes 1159840efe3dbdc7026521da8576574c55120782f6cChris Banes if (Build.VERSION.SDK_INT >= 21) { 1169840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl = new FloatingActionButtonLollipop(this, delegate); 1179840efe3dbdc7026521da8576574c55120782f6cChris Banes } else { 1189840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl = new FloatingActionButtonEclairMr1(this, delegate); 1199840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1209840efe3dbdc7026521da8576574c55120782f6cChris Banes 1219840efe3dbdc7026521da8576574c55120782f6cChris Banes final int maxContentSize = (int) getResources().getDimension(R.dimen.fab_content_size); 1229840efe3dbdc7026521da8576574c55120782f6cChris Banes mContentPadding = (getSizeDimension() - maxContentSize) / 2; 1239840efe3dbdc7026521da8576574c55120782f6cChris Banes 1249840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setBackgroundDrawable(background, mBackgroundTint, 1259840efe3dbdc7026521da8576574c55120782f6cChris Banes mBackgroundTintMode, mRippleColor); 1269840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setElevation(elevation); 1279840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setPressedTranslationZ(pressedTranslationZ); 1289840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1299840efe3dbdc7026521da8576574c55120782f6cChris Banes 1309840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1319840efe3dbdc7026521da8576574c55120782f6cChris Banes protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1329840efe3dbdc7026521da8576574c55120782f6cChris Banes final int preferredSize = getSizeDimension(); 1339840efe3dbdc7026521da8576574c55120782f6cChris Banes 1349840efe3dbdc7026521da8576574c55120782f6cChris Banes final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec); 1359840efe3dbdc7026521da8576574c55120782f6cChris Banes final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec); 1369840efe3dbdc7026521da8576574c55120782f6cChris Banes 1379840efe3dbdc7026521da8576574c55120782f6cChris Banes // As we want to stay circular, we set both dimensions to be the 1389840efe3dbdc7026521da8576574c55120782f6cChris Banes // smallest resolved dimension 1399840efe3dbdc7026521da8576574c55120782f6cChris Banes final int d = Math.min(w, h); 1409840efe3dbdc7026521da8576574c55120782f6cChris Banes 1419840efe3dbdc7026521da8576574c55120782f6cChris Banes // We add the shadow's padding to the measured dimension 1429840efe3dbdc7026521da8576574c55120782f6cChris Banes setMeasuredDimension( 1439840efe3dbdc7026521da8576574c55120782f6cChris Banes d + mShadowPadding.left + mShadowPadding.right, 1449840efe3dbdc7026521da8576574c55120782f6cChris Banes d + mShadowPadding.top + mShadowPadding.bottom); 1459840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1469840efe3dbdc7026521da8576574c55120782f6cChris Banes 14714d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes @Override 14814d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 14914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes super.onLayout(changed, left, top, right, bottom); 15014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes updateOffset(); 15114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 15214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 1539840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 1549840efe3dbdc7026521da8576574c55120782f6cChris Banes * Set the ripple color for this {@link FloatingActionButton}. 1559840efe3dbdc7026521da8576574c55120782f6cChris Banes * <p> 1569840efe3dbdc7026521da8576574c55120782f6cChris Banes * When running on devices with KitKat or below, we draw a fill rather than a ripple. 1579840efe3dbdc7026521da8576574c55120782f6cChris Banes * 1589840efe3dbdc7026521da8576574c55120782f6cChris Banes * @param color ARGB color to use for the ripple. 1599840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 1609840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setRippleColor(int color) { 1619840efe3dbdc7026521da8576574c55120782f6cChris Banes if (mRippleColor != color) { 1629840efe3dbdc7026521da8576574c55120782f6cChris Banes mRippleColor = color; 1639840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setRippleColor(color); 1649840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1659840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1669840efe3dbdc7026521da8576574c55120782f6cChris Banes 1679840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 1689840efe3dbdc7026521da8576574c55120782f6cChris Banes * Return the tint applied to the background drawable, if specified. 1699840efe3dbdc7026521da8576574c55120782f6cChris Banes * 1709840efe3dbdc7026521da8576574c55120782f6cChris Banes * @return the tint applied to the background drawable 1719840efe3dbdc7026521da8576574c55120782f6cChris Banes * @see #setBackgroundTintList(ColorStateList) 1729840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 1739840efe3dbdc7026521da8576574c55120782f6cChris Banes @Nullable 1749840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 1759840efe3dbdc7026521da8576574c55120782f6cChris Banes public ColorStateList getBackgroundTintList() { 1769840efe3dbdc7026521da8576574c55120782f6cChris Banes return mBackgroundTint; 1779840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1789840efe3dbdc7026521da8576574c55120782f6cChris Banes 1799840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 1809840efe3dbdc7026521da8576574c55120782f6cChris Banes * Applies a tint to the background drawable. Does not modify the current tint 1819840efe3dbdc7026521da8576574c55120782f6cChris Banes * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 1829840efe3dbdc7026521da8576574c55120782f6cChris Banes * 1839840efe3dbdc7026521da8576574c55120782f6cChris Banes * @param tint the tint to apply, may be {@code null} to clear tint 1849840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 1859840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setBackgroundTintList(@Nullable ColorStateList tint) { 1869840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setBackgroundTintList(tint); 1879840efe3dbdc7026521da8576574c55120782f6cChris Banes } 1889840efe3dbdc7026521da8576574c55120782f6cChris Banes 1899840efe3dbdc7026521da8576574c55120782f6cChris Banes 1909840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 1919840efe3dbdc7026521da8576574c55120782f6cChris Banes * Return the blending mode used to apply the tint to the background 1929840efe3dbdc7026521da8576574c55120782f6cChris Banes * drawable, if specified. 1939840efe3dbdc7026521da8576574c55120782f6cChris Banes * 1949840efe3dbdc7026521da8576574c55120782f6cChris Banes * @return the blending mode used to apply the tint to the background 1959840efe3dbdc7026521da8576574c55120782f6cChris Banes * drawable 1969840efe3dbdc7026521da8576574c55120782f6cChris Banes * @see #setBackgroundTintMode(PorterDuff.Mode) 1979840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 1989840efe3dbdc7026521da8576574c55120782f6cChris Banes @Nullable 1999840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 2009840efe3dbdc7026521da8576574c55120782f6cChris Banes public PorterDuff.Mode getBackgroundTintMode() { 2019840efe3dbdc7026521da8576574c55120782f6cChris Banes return mBackgroundTintMode; 2029840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2039840efe3dbdc7026521da8576574c55120782f6cChris Banes 2049840efe3dbdc7026521da8576574c55120782f6cChris Banes /** 2059840efe3dbdc7026521da8576574c55120782f6cChris Banes * Specifies the blending mode used to apply the tint specified by 2069840efe3dbdc7026521da8576574c55120782f6cChris Banes * {@link #setBackgroundTintList(ColorStateList)}} to the background 2079840efe3dbdc7026521da8576574c55120782f6cChris Banes * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. 2089840efe3dbdc7026521da8576574c55120782f6cChris Banes * 2099840efe3dbdc7026521da8576574c55120782f6cChris Banes * @param tintMode the blending mode used to apply the tint, may be 2109840efe3dbdc7026521da8576574c55120782f6cChris Banes * {@code null} to clear tint 2119840efe3dbdc7026521da8576574c55120782f6cChris Banes */ 2129840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { 2139840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setBackgroundTintMode(tintMode); 2149840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2159840efe3dbdc7026521da8576574c55120782f6cChris Banes 2169840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 2179840efe3dbdc7026521da8576574c55120782f6cChris Banes public void setBackgroundDrawable(Drawable background) { 2189840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.setBackgroundDrawable(background, mBackgroundTint, mBackgroundTintMode, mRippleColor); 2199840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2209840efe3dbdc7026521da8576574c55120782f6cChris Banes 2219840efe3dbdc7026521da8576574c55120782f6cChris Banes final int getSizeDimension() { 2229840efe3dbdc7026521da8576574c55120782f6cChris Banes switch (mSize) { 2239840efe3dbdc7026521da8576574c55120782f6cChris Banes case SIZE_MINI: 2249840efe3dbdc7026521da8576574c55120782f6cChris Banes return getResources().getDimensionPixelSize(R.dimen.fab_size_mini); 2259840efe3dbdc7026521da8576574c55120782f6cChris Banes case SIZE_NORMAL: 2269840efe3dbdc7026521da8576574c55120782f6cChris Banes default: 2279840efe3dbdc7026521da8576574c55120782f6cChris Banes return getResources().getDimensionPixelSize(R.dimen.fab_size_normal); 2289840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2299840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2309840efe3dbdc7026521da8576574c55120782f6cChris Banes 2319840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 2329840efe3dbdc7026521da8576574c55120782f6cChris Banes protected void drawableStateChanged() { 2339840efe3dbdc7026521da8576574c55120782f6cChris Banes super.drawableStateChanged(); 2349840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.onDrawableStateChanged(getDrawableState()); 2359840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2369840efe3dbdc7026521da8576574c55120782f6cChris Banes 2379840efe3dbdc7026521da8576574c55120782f6cChris Banes @TargetApi(Build.VERSION_CODES.HONEYCOMB) 2389840efe3dbdc7026521da8576574c55120782f6cChris Banes @Override 2399840efe3dbdc7026521da8576574c55120782f6cChris Banes public void jumpDrawablesToCurrentState() { 2409840efe3dbdc7026521da8576574c55120782f6cChris Banes super.jumpDrawablesToCurrentState(); 2419840efe3dbdc7026521da8576574c55120782f6cChris Banes mImpl.jumpDrawableToCurrentState(); 2429840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2439840efe3dbdc7026521da8576574c55120782f6cChris Banes 2449840efe3dbdc7026521da8576574c55120782f6cChris Banes private static int resolveAdjustedSize(int desiredSize, int measureSpec) { 2459840efe3dbdc7026521da8576574c55120782f6cChris Banes int result = desiredSize; 2469840efe3dbdc7026521da8576574c55120782f6cChris Banes int specMode = MeasureSpec.getMode(measureSpec); 2479840efe3dbdc7026521da8576574c55120782f6cChris Banes int specSize = MeasureSpec.getSize(measureSpec); 2489840efe3dbdc7026521da8576574c55120782f6cChris Banes switch (specMode) { 2499840efe3dbdc7026521da8576574c55120782f6cChris Banes case MeasureSpec.UNSPECIFIED: 2509840efe3dbdc7026521da8576574c55120782f6cChris Banes // Parent says we can be as big as we want. Just don't be larger 2519840efe3dbdc7026521da8576574c55120782f6cChris Banes // than max size imposed on ourselves. 2529840efe3dbdc7026521da8576574c55120782f6cChris Banes result = desiredSize; 2539840efe3dbdc7026521da8576574c55120782f6cChris Banes break; 2549840efe3dbdc7026521da8576574c55120782f6cChris Banes case MeasureSpec.AT_MOST: 2559840efe3dbdc7026521da8576574c55120782f6cChris Banes // Parent says we can be as big as we want, up to specSize. 2569840efe3dbdc7026521da8576574c55120782f6cChris Banes // Don't be larger than specSize, and don't be larger than 2579840efe3dbdc7026521da8576574c55120782f6cChris Banes // the max size imposed on ourselves. 2589840efe3dbdc7026521da8576574c55120782f6cChris Banes result = Math.min(desiredSize, specSize); 2599840efe3dbdc7026521da8576574c55120782f6cChris Banes break; 2609840efe3dbdc7026521da8576574c55120782f6cChris Banes case MeasureSpec.EXACTLY: 2619840efe3dbdc7026521da8576574c55120782f6cChris Banes // No choice. Do what we are told. 2629840efe3dbdc7026521da8576574c55120782f6cChris Banes result = specSize; 2639840efe3dbdc7026521da8576574c55120782f6cChris Banes break; 2649840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2659840efe3dbdc7026521da8576574c55120782f6cChris Banes return result; 2669840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2679840efe3dbdc7026521da8576574c55120782f6cChris Banes 2689840efe3dbdc7026521da8576574c55120782f6cChris Banes static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) { 2699840efe3dbdc7026521da8576574c55120782f6cChris Banes switch (value) { 2709840efe3dbdc7026521da8576574c55120782f6cChris Banes case 3: 2719840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.SRC_OVER; 2729840efe3dbdc7026521da8576574c55120782f6cChris Banes case 5: 2739840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.SRC_IN; 2749840efe3dbdc7026521da8576574c55120782f6cChris Banes case 9: 2759840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.SRC_ATOP; 2769840efe3dbdc7026521da8576574c55120782f6cChris Banes case 14: 2779840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.MULTIPLY; 2789840efe3dbdc7026521da8576574c55120782f6cChris Banes case 15: 2799840efe3dbdc7026521da8576574c55120782f6cChris Banes return PorterDuff.Mode.SCREEN; 2809840efe3dbdc7026521da8576574c55120782f6cChris Banes default: 2819840efe3dbdc7026521da8576574c55120782f6cChris Banes return defaultMode; 2829840efe3dbdc7026521da8576574c55120782f6cChris Banes } 2839840efe3dbdc7026521da8576574c55120782f6cChris Banes } 28414d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 28514d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes /** 28614d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes * Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method 28714d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes * offsets our layout position so that we're positioned correctly if we're on one of 28814d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes * our parent's edges. 28914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes */ 29014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes private void updateOffset() { 291b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mShadowPadding.centerX() != 0 || mShadowPadding.centerY() != 0) { 29214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes int offsetTB = 0, offsetLR = 0; 29314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 29414d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes if (isOnRightParentEdge()) { 29514d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes offsetLR = mShadowPadding.right; 29614d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } else if (isOnLeftParentEdge()) { 29714d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes offsetLR = -mShadowPadding.left; 29814d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 29914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes if (isOnBottomParentEdge()) { 30014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes offsetTB = mShadowPadding.bottom; 30114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } else if (isOnTopParentEdge()) { 30214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes offsetTB = -mShadowPadding.top; 30314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 30414d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 30514d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes offsetTopAndBottom(offsetTB); 30614d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes offsetLeftAndRight(offsetLR); 30714d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 30814d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 30914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 31014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes private boolean isOnLeftParentEdge() { 31114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes final int margin = getLayoutParams() instanceof ViewGroup.MarginLayoutParams ? 31214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes ((ViewGroup.MarginLayoutParams) getLayoutParams()).leftMargin : 0; 31314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes return getLeft() <= margin; 31414d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 31514d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 31614d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes private boolean isOnTopParentEdge() { 31714d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes final int margin = getLayoutParams() instanceof ViewGroup.MarginLayoutParams ? 31814d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes ((ViewGroup.MarginLayoutParams) getLayoutParams()).topMargin : 0; 31914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes return getTop() <= margin; 32014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 32114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 32214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes private boolean isOnRightParentEdge() { 32314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes final int margin = getLayoutParams() instanceof ViewGroup.MarginLayoutParams ? 32414d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes ((ViewGroup.MarginLayoutParams) getLayoutParams()).rightMargin : 0; 32514d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 32614d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes ViewParent parent = getParent(); 32714d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes if (parent instanceof View) { 32814d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes return getRight() >= (((View) getParent()).getWidth() - margin); 32914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 33014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes return false; 33114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 33214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 33314d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes private boolean isOnBottomParentEdge() { 33414d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes final int margin = getLayoutParams() instanceof ViewGroup.MarginLayoutParams ? 33514d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes ((ViewGroup.MarginLayoutParams) getLayoutParams()).bottomMargin : 0; 33614d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes 33714d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes ViewParent parent = getParent(); 33814d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes if (parent instanceof View) { 33914d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes return getBottom() >= (((View) getParent()).getHeight() - margin); 34014d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 34114d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes return false; 34214d064edb3e4a16a3b90a4a850560177bea1e60dChris Banes } 343b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 344b7f9224b1495db47eb8fd813b5912250e900770aChris Banes /** 345b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * Behavior designed for use with {@link FloatingActionButton} instances. It's main function 346b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do 347b7f9224b1495db47eb8fd813b5912250e900770aChris Banes * not cover them. 348b7f9224b1495db47eb8fd813b5912250e900770aChris Banes */ 349b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> { 350b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is 351b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // because we can use view translation properties which greatly simplifies the code. 352b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11; 353b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 354b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private float mTranslationY; 355b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private Set<WeakReference<View>> mSnackbars; 356b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 357b7f9224b1495db47eb8fd813b5912250e900770aChris Banes @Override 358b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public boolean layoutDependsOn(CoordinatorLayout parent, 359b7f9224b1495db47eb8fd813b5912250e900770aChris Banes FloatingActionButton child, 360b7f9224b1495db47eb8fd813b5912250e900770aChris Banes View dependency) { 361b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // We're dependent on all SnackbarLayouts 362b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout) { 363b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (!containsView(dependency)) { 364b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mSnackbars == null) mSnackbars = new HashSet<>(); 365b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mSnackbars.add(new WeakReference<>(dependency)); 366b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 367b7f9224b1495db47eb8fd813b5912250e900770aChris Banes cleanUpSet(); 368b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return true; 369b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 370b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return false; 371b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 372b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 373b7f9224b1495db47eb8fd813b5912250e900770aChris Banes @Override 374b7f9224b1495db47eb8fd813b5912250e900770aChris Banes public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, 375b7f9224b1495db47eb8fd813b5912250e900770aChris Banes View snackbar) { 376b7f9224b1495db47eb8fd813b5912250e900770aChris Banes updateFabTranslation(parent, child, snackbar); 377b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return false; 378b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 379b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 380b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void updateFabTranslation(CoordinatorLayout parent, FloatingActionButton fab, 381b7f9224b1495db47eb8fd813b5912250e900770aChris Banes View snackbar) { 382b7f9224b1495db47eb8fd813b5912250e900770aChris Banes final float translationY = getTranslationYForFab(parent, fab); 383b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (translationY != mTranslationY) { 384b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // First, cancel any current animation 385b7f9224b1495db47eb8fd813b5912250e900770aChris Banes ViewCompat.animate(fab).cancel(); 386b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 387b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (Math.abs(translationY - mTranslationY) == snackbar.getHeight()) { 388b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // If we're travelling by the height of the Snackbar then we probably need to 389b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // animate to the value 390b7f9224b1495db47eb8fd813b5912250e900770aChris Banes ViewCompat.animate(fab).translationY(translationY) 391b7f9224b1495db47eb8fd813b5912250e900770aChris Banes .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 392b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } else { 393b7f9224b1495db47eb8fd813b5912250e900770aChris Banes // Else we'll set use setTranslationY 394b7f9224b1495db47eb8fd813b5912250e900770aChris Banes ViewCompat.setTranslationY(fab, translationY); 395b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 396b7f9224b1495db47eb8fd813b5912250e900770aChris Banes mTranslationY = translationY; 397b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 398b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 399b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 400b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private float getTranslationYForFab(CoordinatorLayout parent, FloatingActionButton fab) { 401b7f9224b1495db47eb8fd813b5912250e900770aChris Banes float minOffset = 0; 402b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mSnackbars != null && !mSnackbars.isEmpty()) { 403b7f9224b1495db47eb8fd813b5912250e900770aChris Banes for (WeakReference<View> viewRef : mSnackbars) { 404b7f9224b1495db47eb8fd813b5912250e900770aChris Banes final View view = viewRef.get(); 405b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (view != null && parent.doViewsOverlap(fab, view)) { 406b7f9224b1495db47eb8fd813b5912250e900770aChris Banes minOffset = Math.min(minOffset, 407b7f9224b1495db47eb8fd813b5912250e900770aChris Banes ViewCompat.getTranslationY(view) - view.getHeight()); 408b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 409b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 410b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 411b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return minOffset; 412b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 413b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 414b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private void cleanUpSet() { 415b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mSnackbars != null && !mSnackbars.isEmpty()) { 416b7f9224b1495db47eb8fd813b5912250e900770aChris Banes for (final Iterator<WeakReference<View>> i = mSnackbars.iterator(); i.hasNext();) { 417b7f9224b1495db47eb8fd813b5912250e900770aChris Banes WeakReference<View> ref = i.next(); 418b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (ref == null || ref.get() == null) { 419b7f9224b1495db47eb8fd813b5912250e900770aChris Banes i.remove(); 420b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 421b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 422b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 423b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 424b7f9224b1495db47eb8fd813b5912250e900770aChris Banes 425b7f9224b1495db47eb8fd813b5912250e900770aChris Banes private boolean containsView(View dependency) { 426b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (mSnackbars != null && !mSnackbars.isEmpty()) { 427b7f9224b1495db47eb8fd813b5912250e900770aChris Banes for (WeakReference<View> viewRef : mSnackbars) { 428b7f9224b1495db47eb8fd813b5912250e900770aChris Banes if (viewRef.get() == dependency) { 429b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return true; 430b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 431b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 432b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 433b7f9224b1495db47eb8fd813b5912250e900770aChris Banes return false; 434b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 435b7f9224b1495db47eb8fd813b5912250e900770aChris Banes } 4369840efe3dbdc7026521da8576574c55120782f6cChris Banes} 437