FloatingActionButton.java revision 097e80a3c5518c6bf2e9f3f9b55ed9f4b5cc37e8
1709a0978ae141198018ca9769f8d96292a8928e6Jason Sams/* 2709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * Copyright (C) 2015 The Android Open Source Project 3709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * 4709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * Licensed under the Apache License, Version 2.0 (the "License"); 5709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * you may not use this file except in compliance with the License. 6709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * You may obtain a copy of the License at 7709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * 8709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * http://www.apache.org/licenses/LICENSE-2.0 9709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * 10709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * Unless required by applicable law or agreed to in writing, software 11709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * distributed under the License is distributed on an "AS IS" BASIS, 12709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * See the License for the specific language governing permissions and 14709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * limitations under the License. 15709a0978ae141198018ca9769f8d96292a8928e6Jason Sams */ 16709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 17709a0978ae141198018ca9769f8d96292a8928e6Jason Samspackage android.support.design.widget; 18709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 19709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.annotation.TargetApi; 20709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.content.Context; 21709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.content.res.ColorStateList; 22709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.content.res.TypedArray; 2325e3af55a43faddced1a9931574dfdc3cc8ad8fdStephen Hinesimport android.graphics.PorterDuff; 2429809d1f95d4cd4cbc6b2f9384b3321759691e13Tim Murrayimport android.graphics.Rect; 2525e3af55a43faddced1a9931574dfdc3cc8ad8fdStephen Hinesimport android.graphics.drawable.Drawable; 2625e3af55a43faddced1a9931574dfdc3cc8ad8fdStephen Hinesimport android.os.Build; 27709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.support.annotation.ColorInt; 28709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.support.annotation.NonNull; 29709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.support.annotation.Nullable; 30709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.support.design.R; 31709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener; 32709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.support.v4.view.ViewCompat; 33709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport android.util.AttributeSet; 349ab5094dd32352b33e251e540934f6e814c5fa5bJean-Luc Brouilletimport android.util.Log; 359ab5094dd32352b33e251e540934f6e814c5fa5bJean-Luc Brouilletimport android.view.View; 369ab5094dd32352b33e251e540934f6e814c5fa5bJean-Luc Brouilletimport android.widget.ImageButton; 379ab5094dd32352b33e251e540934f6e814c5fa5bJean-Luc Brouilletimport android.widget.ImageView; 38709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 39709a0978ae141198018ca9769f8d96292a8928e6Jason Samsimport java.util.List; 40709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 412abfcc6d129fe3defddef4540aa95cc445c03a7aYang Ni/** 42709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * Floating action buttons are used for a special type of promoted action. They are distinguished 43709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * by a circled icon floating above the UI and have special motion behaviors related to morphing, 44709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * launching, and the transferring anchor point. 45709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * 46b0abb140ac51b93d1a85aadaa63fe057f2d29850David Gross * <p>Floating action buttons come in two sizes: the default and the mini. The size can be 47709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * controlled with the {@code fabSize} attribute.</p> 489ed79105cc6a8dbfaf959875249f36022cc2c798Chris Wailes * 49dc0d8f7c0f1f43f25c34fbc04656ad578f6e953bPirama Arumuga Nainar * <p>As this class descends from {@link ImageView}, you can control the icon which is displayed 50110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams * via {@link #setImageDrawable(Drawable)}.</p> 51110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams * 52110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams * <p>The background color of this view defaults to the your theme's {@code colorAccent}. If you 53dc0d8f7c0f1f43f25c34fbc04656ad578f6e953bPirama Arumuga Nainar * wish to change this at runtime then you can do so via 54110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams * {@link #setBackgroundTintList(ColorStateList)}.</p> 55110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams * 56709a0978ae141198018ca9769f8d96292a8928e6Jason Sams * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize 57709a0978ae141198018ca9769f8d96292a8928e6Jason Sams */ 58005113297b19ed256b6db9d6bc293ed9266899fcStephen Hines@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class) 5944bef6fba6244292b751387f3d6c31cca96c28adChris Wailespublic class FloatingActionButton extends ImageButton { 60c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines 61709a0978ae141198018ca9769f8d96292a8928e6Jason Sams private static final String LOG_TAG = "FloatingActionButton"; 62c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines 63c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines /** 64f37121300217d3b39ab66dd9c8881bcbcad932dfChris Wailes * Callback to be invoked when the visibility of a FloatingActionButton changes. 65f37121300217d3b39ab66dd9c8881bcbcad932dfChris Wailes */ 6617e3cdc24776d8fdbf1ce16287b9b4dcd516708fJason Sams public abstract static class OnVisibilityChangedListener { 67f37121300217d3b39ab66dd9c8881bcbcad932dfChris Wailes /** 68f37121300217d3b39ab66dd9c8881bcbcad932dfChris Wailes * Called when a FloatingActionButton has been 69f37121300217d3b39ab66dd9c8881bcbcad932dfChris Wailes * {@link #show(OnVisibilityChangedListener) shown}. 70f37121300217d3b39ab66dd9c8881bcbcad932dfChris Wailes * 71f37121300217d3b39ab66dd9c8881bcbcad932dfChris Wailes * @param fab the FloatingActionButton that was shown. 72c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines */ 73c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines public void onShown(FloatingActionButton fab) {} 74c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines 75c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines /** 76c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines * Called when a FloatingActionButton has been 77c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines * {@link #hide(OnVisibilityChangedListener) hidden}. 78c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines * 79c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines * @param fab the FloatingActionButton that was hidden. 80c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines */ 81c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines public void onHidden(FloatingActionButton fab) {} 82c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines } 83c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines 84c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines // These values must match those in the attrs declaration 85c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines private static final int SIZE_MINI = 1; 86c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines private static final int SIZE_NORMAL = 0; 87c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines 88c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines private ColorStateList mBackgroundTint; 89c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines private PorterDuff.Mode mBackgroundTintMode; 90709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 91062c287f573ecc06c38ee4295e5627e12c52ac3dYang Ni private int mBorderWidth; 92709a0978ae141198018ca9769f8d96292a8928e6Jason Sams private int mRippleColor; 93c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines private int mSize; 94709a0978ae141198018ca9769f8d96292a8928e6Jason Sams private int mContentPadding; 95709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 96709a0978ae141198018ca9769f8d96292a8928e6Jason Sams private final Rect mShadowPadding; 97709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 98bf2111d3b3de310932099514f06924e48fa1d7b2Jason Sams private final FloatingActionButtonImpl mImpl; 994b3c34e6833e39bc89c2128002806b654b8e623dChris Wailes 1004b3c34e6833e39bc89c2128002806b654b8e623dChris Wailes public FloatingActionButton(Context context) { 1014b3c34e6833e39bc89c2128002806b654b8e623dChris Wailes this(context, null); 102709a0978ae141198018ca9769f8d96292a8928e6Jason Sams } 103709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 104709a0978ae141198018ca9769f8d96292a8928e6Jason Sams public FloatingActionButton(Context context, AttributeSet attrs) { 105709a0978ae141198018ca9769f8d96292a8928e6Jason Sams this(context, attrs, 0); 106709a0978ae141198018ca9769f8d96292a8928e6Jason Sams } 107709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 108c060f1435e7b9405f3be8974417fa6f410f03753Stephen Hines public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { 109dc0d8f7c0f1f43f25c34fbc04656ad578f6e953bPirama Arumuga Nainar super(context, attrs, defStyleAttr); 110709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 1118409d6414dd4a42aa59779fcfe9fce18648cb135Stephen Hines ThemeUtils.checkAppCompatTheme(context); 1128409d6414dd4a42aa59779fcfe9fce18648cb135Stephen Hines 1138409d6414dd4a42aa59779fcfe9fce18648cb135Stephen Hines mShadowPadding = new Rect(); 1148409d6414dd4a42aa59779fcfe9fce18648cb135Stephen Hines 1155aa018cc36e589b07674957714d27ae3d1fa1c4eStephen Hines TypedArray a = context.obtainStyledAttributes(attrs, 1168409d6414dd4a42aa59779fcfe9fce18648cb135Stephen Hines R.styleable.FloatingActionButton, defStyleAttr, 117709a0978ae141198018ca9769f8d96292a8928e6Jason Sams R.style.Widget_Design_FloatingActionButton); 118709a0978ae141198018ca9769f8d96292a8928e6Jason Sams mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint); 119709a0978ae141198018ca9769f8d96292a8928e6Jason Sams mBackgroundTintMode = parseTintMode(a.getInt( 120dc0d8f7c0f1f43f25c34fbc04656ad578f6e953bPirama Arumuga Nainar R.styleable.FloatingActionButton_backgroundTintMode, -1), null); 121709a0978ae141198018ca9769f8d96292a8928e6Jason Sams mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0); 122110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL); 12340e35cdbe217ec8bf9fc3c69873c7d62fc14158fJean-Luc Brouillet mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0); 12440e35cdbe217ec8bf9fc3c69873c7d62fc14158fJean-Luc Brouillet final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f); 1259ab5094dd32352b33e251e540934f6e814c5fa5bJean-Luc Brouillet final float pressedTranslationZ = a.getDimension( 12629809d1f95d4cd4cbc6b2f9384b3321759691e13Tim Murray R.styleable.FloatingActionButton_pressedTranslationZ, 0f); 127709a0978ae141198018ca9769f8d96292a8928e6Jason Sams a.recycle(); 12845e753a46e587c69b3b0d0c5138e88715a24a29aStephen Hines 12945e753a46e587c69b3b0d0c5138e88715a24a29aStephen Hines final ShadowViewDelegate delegate = new ShadowViewDelegate() { 130110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams @Override 131110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams public float getRadius() { 132110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams return getSizeDimension() / 2f; 133110f181b7966212a36ef18016f9b81c7322d0a2fJason Sams } 134d9bae689c1b8c3f2ed1a5f2b374dc9393584b8ddYang Ni 135709a0978ae141198018ca9769f8d96292a8928e6Jason Sams @Override 136709a0978ae141198018ca9769f8d96292a8928e6Jason Sams public void setShadowPadding(int left, int top, int right, int bottom) { 137709a0978ae141198018ca9769f8d96292a8928e6Jason Sams mShadowPadding.set(left, top, right, bottom); 138709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 139da0f069871343119251d6b0586be356dc2146a62Yang Ni setPadding(left + mContentPadding, top + mContentPadding, 1402abfcc6d129fe3defddef4540aa95cc445c03a7aYang Ni right + mContentPadding, bottom + mContentPadding); 1412abfcc6d129fe3defddef4540aa95cc445c03a7aYang Ni } 1422abfcc6d129fe3defddef4540aa95cc445c03a7aYang Ni 143da0f069871343119251d6b0586be356dc2146a62Yang Ni @Override 1442abfcc6d129fe3defddef4540aa95cc445c03a7aYang Ni public void setBackgroundDrawable(Drawable background) { 1452abfcc6d129fe3defddef4540aa95cc445c03a7aYang Ni FloatingActionButton.super.setBackgroundDrawable(background); 146cb17015fed6b11a5028f31cc804a3847e379945dYang Ni } 147aa6757ffc1b23d771566439c3179fdbc1e5ba569Pirama Arumuga Nainar }; 148709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 149709a0978ae141198018ca9769f8d96292a8928e6Jason Sams final int sdk = Build.VERSION.SDK_INT; 150709a0978ae141198018ca9769f8d96292a8928e6Jason Sams if (sdk >= 21) { 151709a0978ae141198018ca9769f8d96292a8928e6Jason Sams mImpl = new FloatingActionButtonLollipop(this, delegate); 152709a0978ae141198018ca9769f8d96292a8928e6Jason Sams } else if (sdk >= 14) { 153709a0978ae141198018ca9769f8d96292a8928e6Jason Sams mImpl = new FloatingActionButtonIcs(this, delegate); 154709a0978ae141198018ca9769f8d96292a8928e6Jason Sams } else { 155cb17015fed6b11a5028f31cc804a3847e379945dYang Ni mImpl = new FloatingActionButtonEclairMr1(this, delegate); 156cb17015fed6b11a5028f31cc804a3847e379945dYang Ni } 157cb17015fed6b11a5028f31cc804a3847e379945dYang Ni 158f02a2b0a2749d4a4f07edbc23eddff2e51d11b72Yang Ni final int maxContentSize = (int) getResources().getDimension( 159709a0978ae141198018ca9769f8d96292a8928e6Jason Sams R.dimen.design_fab_content_size); 160d9bae689c1b8c3f2ed1a5f2b374dc9393584b8ddYang Ni mContentPadding = (getSizeDimension() - maxContentSize) / 2; 161da0f069871343119251d6b0586be356dc2146a62Yang Ni 162da0f069871343119251d6b0586be356dc2146a62Yang Ni mImpl.setBackgroundDrawable(mBackgroundTint, mBackgroundTintMode, 163682672e36b05349bc4d9dee74e9fab73ce804183Pirama Arumuga Nainar mRippleColor, mBorderWidth); 164da0f069871343119251d6b0586be356dc2146a62Yang Ni mImpl.setElevation(elevation); 165da0f069871343119251d6b0586be356dc2146a62Yang Ni mImpl.setPressedTranslationZ(pressedTranslationZ); 166682672e36b05349bc4d9dee74e9fab73ce804183Pirama Arumuga Nainar } 167da0f069871343119251d6b0586be356dc2146a62Yang Ni 168da0f069871343119251d6b0586be356dc2146a62Yang Ni @Override 169709a0978ae141198018ca9769f8d96292a8928e6Jason Sams protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 170709a0978ae141198018ca9769f8d96292a8928e6Jason Sams final int preferredSize = getSizeDimension(); 171709a0978ae141198018ca9769f8d96292a8928e6Jason Sams 172 final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec); 173 final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec); 174 175 // As we want to stay circular, we set both dimensions to be the 176 // smallest resolved dimension 177 final int d = Math.min(w, h); 178 179 // We add the shadow's padding to the measured dimension 180 setMeasuredDimension( 181 d + mShadowPadding.left + mShadowPadding.right, 182 d + mShadowPadding.top + mShadowPadding.bottom); 183 } 184 185 /** 186 * Set the ripple color for this {@link FloatingActionButton}. 187 * <p> 188 * When running on devices with KitKat or below, we draw a fill rather than a ripple. 189 * 190 * @param color ARGB color to use for the ripple. 191 */ 192 public void setRippleColor(@ColorInt int color) { 193 if (mRippleColor != color) { 194 mRippleColor = color; 195 mImpl.setRippleColor(color); 196 } 197 } 198 199 /** 200 * Return the tint applied to the background drawable, if specified. 201 * 202 * @return the tint applied to the background drawable 203 * @see #setBackgroundTintList(ColorStateList) 204 */ 205 @Nullable 206 @Override 207 public ColorStateList getBackgroundTintList() { 208 return mBackgroundTint; 209 } 210 211 /** 212 * Applies a tint to the background drawable. Does not modify the current tint 213 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 214 * 215 * @param tint the tint to apply, may be {@code null} to clear tint 216 */ 217 public void setBackgroundTintList(@Nullable ColorStateList tint) { 218 if (mBackgroundTint != tint) { 219 mBackgroundTint = tint; 220 mImpl.setBackgroundTintList(tint); 221 } 222 } 223 224 225 /** 226 * Return the blending mode used to apply the tint to the background 227 * drawable, if specified. 228 * 229 * @return the blending mode used to apply the tint to the background 230 * drawable 231 * @see #setBackgroundTintMode(PorterDuff.Mode) 232 */ 233 @Nullable 234 @Override 235 public PorterDuff.Mode getBackgroundTintMode() { 236 return mBackgroundTintMode; 237 } 238 239 /** 240 * Specifies the blending mode used to apply the tint specified by 241 * {@link #setBackgroundTintList(ColorStateList)}} to the background 242 * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. 243 * 244 * @param tintMode the blending mode used to apply the tint, may be 245 * {@code null} to clear tint 246 */ 247 public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { 248 if (mBackgroundTintMode != tintMode) { 249 mBackgroundTintMode = tintMode; 250 mImpl.setBackgroundTintMode(tintMode); 251 } 252 } 253 254 @Override 255 public void setBackgroundDrawable(Drawable background) { 256 Log.i(LOG_TAG, "Setting a custom background is not supported."); 257 } 258 259 @Override 260 public void setBackgroundResource(int resid) { 261 Log.i(LOG_TAG, "Setting a custom background is not supported."); 262 } 263 264 @Override 265 public void setBackgroundColor(int color) { 266 Log.i(LOG_TAG, "Setting a custom background is not supported."); 267 } 268 269 /** 270 * Shows the button. 271 * <p>This method will animate the button show if the view has already been laid out.</p> 272 */ 273 public void show() { 274 mImpl.show(null); 275 } 276 277 /** 278 * Shows the button. 279 * <p>This method will animate the button show if the view has already been laid out.</p> 280 * 281 * @param listener the listener to notify when this view is shown 282 */ 283 public void show(@Nullable final OnVisibilityChangedListener listener) { 284 mImpl.show(wrapOnVisibilityChangedListener(listener)); 285 } 286 287 /** 288 * Hides the button. 289 * <p>This method will animate the button hide if the view has already been laid out.</p> 290 */ 291 public void hide() { 292 mImpl.hide(null); 293 } 294 295 /** 296 * Hides the button. 297 * <p>This method will animate the button hide if the view has already been laid out.</p> 298 * 299 * @param listener the listener to notify when this view is hidden 300 */ 301 public void hide(@Nullable OnVisibilityChangedListener listener) { 302 mImpl.hide(wrapOnVisibilityChangedListener(listener)); 303 } 304 305 @Nullable 306 private InternalVisibilityChangedListener wrapOnVisibilityChangedListener( 307 @Nullable final OnVisibilityChangedListener listener) { 308 if (listener == null) { 309 return null; 310 } 311 312 return new InternalVisibilityChangedListener() { 313 @Override 314 public void onShown() { 315 listener.onShown(FloatingActionButton.this); 316 } 317 318 @Override 319 public void onHidden() { 320 listener.onHidden(FloatingActionButton.this); 321 } 322 }; 323 } 324 325 final int getSizeDimension() { 326 switch (mSize) { 327 case SIZE_MINI: 328 return getResources().getDimensionPixelSize(R.dimen.design_fab_size_mini); 329 case SIZE_NORMAL: 330 default: 331 return getResources().getDimensionPixelSize(R.dimen.design_fab_size_normal); 332 } 333 } 334 335 @Override 336 protected void onAttachedToWindow() { 337 super.onAttachedToWindow(); 338 mImpl.onAttachedToWindow(); 339 } 340 341 @Override 342 protected void onDetachedFromWindow() { 343 super.onDetachedFromWindow(); 344 mImpl.onDetachedFromWindow(); 345 } 346 347 @Override 348 protected void drawableStateChanged() { 349 super.drawableStateChanged(); 350 mImpl.onDrawableStateChanged(getDrawableState()); 351 } 352 353 @TargetApi(Build.VERSION_CODES.HONEYCOMB) 354 @Override 355 public void jumpDrawablesToCurrentState() { 356 super.jumpDrawablesToCurrentState(); 357 mImpl.jumpDrawableToCurrentState(); 358 } 359 360 private static int resolveAdjustedSize(int desiredSize, int measureSpec) { 361 int result = desiredSize; 362 int specMode = MeasureSpec.getMode(measureSpec); 363 int specSize = MeasureSpec.getSize(measureSpec); 364 switch (specMode) { 365 case MeasureSpec.UNSPECIFIED: 366 // Parent says we can be as big as we want. Just don't be larger 367 // than max size imposed on ourselves. 368 result = desiredSize; 369 break; 370 case MeasureSpec.AT_MOST: 371 // Parent says we can be as big as we want, up to specSize. 372 // Don't be larger than specSize, and don't be larger than 373 // the max size imposed on ourselves. 374 result = Math.min(desiredSize, specSize); 375 break; 376 case MeasureSpec.EXACTLY: 377 // No choice. Do what we are told. 378 result = specSize; 379 break; 380 } 381 return result; 382 } 383 384 static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) { 385 switch (value) { 386 case 3: 387 return PorterDuff.Mode.SRC_OVER; 388 case 5: 389 return PorterDuff.Mode.SRC_IN; 390 case 9: 391 return PorterDuff.Mode.SRC_ATOP; 392 case 14: 393 return PorterDuff.Mode.MULTIPLY; 394 case 15: 395 return PorterDuff.Mode.SCREEN; 396 default: 397 return defaultMode; 398 } 399 } 400 401 /** 402 * Behavior designed for use with {@link FloatingActionButton} instances. It's main function 403 * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do 404 * not cover them. 405 */ 406 public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> { 407 // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is 408 // because we can use view translation properties which greatly simplifies the code. 409 private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11; 410 411 private float mFabTranslationY; 412 private Rect mTmpRect; 413 414 @Override 415 public boolean layoutDependsOn(CoordinatorLayout parent, 416 FloatingActionButton child, View dependency) { 417 // We're dependent on all SnackbarLayouts (if enabled) 418 return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout; 419 } 420 421 @Override 422 public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, 423 View dependency) { 424 if (dependency instanceof Snackbar.SnackbarLayout) { 425 updateFabTranslationForSnackbar(parent, child, dependency); 426 } else if (dependency instanceof AppBarLayout) { 427 // If we're depending on an AppBarLayout we will show/hide it automatically 428 // if the FAB is anchored to the AppBarLayout 429 updateFabVisibility(parent, (AppBarLayout) dependency, child); 430 } 431 return false; 432 } 433 434 private boolean updateFabVisibility(CoordinatorLayout parent, 435 AppBarLayout appBarLayout, FloatingActionButton child) { 436 final CoordinatorLayout.LayoutParams lp = 437 (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 438 if (lp.getAnchorId() != appBarLayout.getId()) { 439 // The anchor ID doesn't match the dependency, so we won't automatically 440 // show/hide the FAB 441 return false; 442 } 443 444 if (mTmpRect == null) { 445 mTmpRect = new Rect(); 446 } 447 448 // First, let's get the visible rect of the dependency 449 final Rect rect = mTmpRect; 450 ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect); 451 452 if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) { 453 // If the anchor's bottom is below the seam, we'll animate our FAB out 454 child.hide(); 455 } else { 456 // Else, we'll animate our FAB back in 457 child.show(); 458 } 459 return true; 460 } 461 462 private void updateFabTranslationForSnackbar(CoordinatorLayout parent, 463 FloatingActionButton fab, View snackbar) { 464 if (fab.getVisibility() != View.VISIBLE) { 465 return; 466 } 467 468 final float targetTransY = getFabTranslationYForSnackbar(parent, fab); 469 if (mFabTranslationY == targetTransY) { 470 // We're already at (or currently animating to) the target value, return... 471 return; 472 } 473 474 mFabTranslationY = targetTransY; 475 final float currentTransY = ViewCompat.getTranslationY(fab); 476 final float dy = currentTransY - targetTransY; 477 478 if (Math.abs(dy) > (fab.getHeight() * 0.667f)) { 479 // If the FAB will be travelling by more than 2/3 of it's height, let's animate 480 // it instead 481 ViewCompat.animate(fab) 482 .translationY(targetTransY) 483 .scaleX(1f) 484 .scaleY(1f) 485 .alpha(1f) 486 .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR) 487 .setListener(null); 488 } else { 489 // Make sure that any current animation is cancelled 490 ViewCompat.animate(fab).cancel(); 491 // Now update the translation Y 492 ViewCompat.setTranslationY(fab, targetTransY); 493 } 494 } 495 496 private float getFabTranslationYForSnackbar(CoordinatorLayout parent, 497 FloatingActionButton fab) { 498 float minOffset = 0; 499 final List<View> dependencies = parent.getDependencies(fab); 500 for (int i = 0, z = dependencies.size(); i < z; i++) { 501 final View view = dependencies.get(i); 502 if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) { 503 minOffset = Math.min(minOffset, 504 ViewCompat.getTranslationY(view) - view.getHeight()); 505 } 506 } 507 508 return minOffset; 509 } 510 511 @Override 512 public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child, 513 int layoutDirection) { 514 // First, lets make sure that the visibility of the FAB is consistent 515 final List<View> dependencies = parent.getDependencies(child); 516 for (int i = 0, count = dependencies.size(); i < count; i++) { 517 final View dependency = dependencies.get(i); 518 if (dependency instanceof AppBarLayout 519 && updateFabVisibility(parent, (AppBarLayout) dependency, child)) { 520 break; 521 } 522 } 523 // Now let the CoordinatorLayout lay out the FAB 524 parent.onLayoutChild(child, layoutDirection); 525 // Now offset it if needed 526 offsetIfNeeded(parent, child); 527 return true; 528 } 529 530 /** 531 * Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method 532 * offsets our layout position so that we're positioned correctly if we're on one of 533 * our parent's edges. 534 */ 535 private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) { 536 final Rect padding = fab.mShadowPadding; 537 538 if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) { 539 final CoordinatorLayout.LayoutParams lp = 540 (CoordinatorLayout.LayoutParams) fab.getLayoutParams(); 541 542 int offsetTB = 0, offsetLR = 0; 543 544 if (fab.getRight() >= parent.getWidth() - lp.rightMargin) { 545 // If we're on the left edge, shift it the right 546 offsetLR = padding.right; 547 } else if (fab.getLeft() <= lp.leftMargin) { 548 // If we're on the left edge, shift it the left 549 offsetLR = -padding.left; 550 } 551 if (fab.getBottom() >= parent.getBottom() - lp.bottomMargin) { 552 // If we're on the bottom edge, shift it down 553 offsetTB = padding.bottom; 554 } else if (fab.getTop() <= lp.topMargin) { 555 // If we're on the top edge, shift it up 556 offsetTB = -padding.top; 557 } 558 559 fab.offsetTopAndBottom(offsetTB); 560 fab.offsetLeftAndRight(offsetLR); 561 } 562 } 563 } 564} 565