FloatingActionButton.java revision df23c413315751774dd83a1d930ae6e83bc21d55
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.design.widget;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.content.res.ColorStateList;
22import android.content.res.TypedArray;
23import android.graphics.PorterDuff;
24import android.graphics.Rect;
25import android.graphics.drawable.Drawable;
26import android.os.Build;
27import android.support.annotation.ColorInt;
28import android.support.annotation.NonNull;
29import android.support.annotation.Nullable;
30import android.support.design.R;
31import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
32import android.support.v4.view.ViewCompat;
33import android.util.AttributeSet;
34import android.util.Log;
35import android.view.MotionEvent;
36import android.view.View;
37import android.widget.ImageView;
38
39import java.util.List;
40
41/**
42 * Floating action buttons are used for a special type of promoted action. They are distinguished
43 * by a circled icon floating above the UI and have special motion behaviors related to morphing,
44 * launching, and the transferring anchor point.
45 *
46 * <p>Floating action buttons come in two sizes: the default and the mini. The size can be
47 * controlled with the {@code fabSize} attribute.</p>
48 *
49 * <p>As this class descends from {@link ImageView}, you can control the icon which is displayed
50 * via {@link #setImageDrawable(Drawable)}.</p>
51 *
52 * <p>The background color of this view defaults to the your theme's {@code colorAccent}. If you
53 * wish to change this at runtime then you can do so via
54 * {@link #setBackgroundTintList(ColorStateList)}.</p>
55 *
56 * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize
57 */
58@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
59public class FloatingActionButton extends VisibilityAwareImageButton {
60
61    private static final String LOG_TAG = "FloatingActionButton";
62
63    /**
64     * Callback to be invoked when the visibility of a FloatingActionButton changes.
65     */
66    public abstract static class OnVisibilityChangedListener {
67        /**
68         * Called when a FloatingActionButton has been
69         * {@link #show(OnVisibilityChangedListener) shown}.
70         *
71         * @param fab the FloatingActionButton that was shown.
72         */
73        public void onShown(FloatingActionButton fab) {}
74
75        /**
76         * Called when a FloatingActionButton has been
77         * {@link #hide(OnVisibilityChangedListener) hidden}.
78         *
79         * @param fab the FloatingActionButton that was hidden.
80         */
81        public void onHidden(FloatingActionButton fab) {}
82    }
83
84    // These values must match those in the attrs declaration
85    private static final int SIZE_MINI = 1;
86    private static final int SIZE_NORMAL = 0;
87
88    private ColorStateList mBackgroundTint;
89    private PorterDuff.Mode mBackgroundTintMode;
90
91    private int mBorderWidth;
92    private int mRippleColor;
93    private int mSize;
94    private int mImagePadding;
95
96    private boolean mCompatPadding;
97    private final Rect mShadowPadding = new Rect();
98    private final Rect mTouchArea = new Rect();
99
100    private final FloatingActionButtonImpl mImpl = createImpl(this, new ShadowDelegateImpl());
101
102    public FloatingActionButton(Context context) {
103        this(context, null);
104    }
105
106    public FloatingActionButton(Context context, AttributeSet attrs) {
107        this(context, attrs, 0);
108    }
109
110    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
111        super(context, attrs, defStyleAttr);
112
113        ThemeUtils.checkAppCompatTheme(context);
114
115        TypedArray a = context.obtainStyledAttributes(attrs,
116                R.styleable.FloatingActionButton, defStyleAttr,
117                R.style.Widget_Design_FloatingActionButton);
118        mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint);
119        mBackgroundTintMode = parseTintMode(a.getInt(
120                R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
121        mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
122        mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL);
123        mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0);
124        final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
125        final float pressedTranslationZ = a.getDimension(
126                R.styleable.FloatingActionButton_pressedTranslationZ, 0f);
127        mCompatPadding = a.getBoolean(R.styleable.FloatingActionButton_useCompatPadding, false);
128        a.recycle();
129
130        final int maxImageSize = (int) getResources().getDimension(R.dimen.design_fab_image_size);
131        mImagePadding = (getSizeDimension() - maxImageSize) / 2;
132
133        mImpl.setBackgroundDrawable(mBackgroundTint, mBackgroundTintMode,
134                mRippleColor, mBorderWidth);
135        mImpl.setElevation(elevation);
136        mImpl.setPressedTranslationZ(pressedTranslationZ);
137        mImpl.updatePadding();
138    }
139
140    @Override
141    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
142        final int preferredSize = getSizeDimension();
143
144        final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec);
145        final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec);
146
147        // As we want to stay circular, we set both dimensions to be the
148        // smallest resolved dimension
149        final int d = Math.min(w, h);
150
151        // We add the shadow's padding to the measured dimension
152        setMeasuredDimension(
153                d + mShadowPadding.left + mShadowPadding.right,
154                d + mShadowPadding.top + mShadowPadding.bottom);
155    }
156
157    /**
158     * Set the ripple color for this {@link FloatingActionButton}.
159     * <p>
160     * When running on devices with KitKat or below, we draw a fill rather than a ripple.
161     *
162     * @param color ARGB color to use for the ripple.
163     *
164     * @attr ref android.support.design.R.styleable#FloatingActionButton_rippleColor
165     */
166    public void setRippleColor(@ColorInt int color) {
167        if (mRippleColor != color) {
168            mRippleColor = color;
169            mImpl.setRippleColor(color);
170        }
171    }
172
173    /**
174     * Return the tint applied to the background drawable, if specified.
175     *
176     * @return the tint applied to the background drawable
177     * @see #setBackgroundTintList(ColorStateList)
178     */
179    @Nullable
180    @Override
181    public ColorStateList getBackgroundTintList() {
182        return mBackgroundTint;
183    }
184
185    /**
186     * Applies a tint to the background drawable. Does not modify the current tint
187     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
188     *
189     * @param tint the tint to apply, may be {@code null} to clear tint
190     */
191    public void setBackgroundTintList(@Nullable ColorStateList tint) {
192        if (mBackgroundTint != tint) {
193            mBackgroundTint = tint;
194            mImpl.setBackgroundTintList(tint);
195        }
196    }
197
198    /**
199     * Return the blending mode used to apply the tint to the background
200     * drawable, if specified.
201     *
202     * @return the blending mode used to apply the tint to the background
203     *         drawable
204     * @see #setBackgroundTintMode(PorterDuff.Mode)
205     */
206    @Nullable
207    @Override
208    public PorterDuff.Mode getBackgroundTintMode() {
209        return mBackgroundTintMode;
210    }
211
212    /**
213     * Specifies the blending mode used to apply the tint specified by
214     * {@link #setBackgroundTintList(ColorStateList)}} to the background
215     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
216     *
217     * @param tintMode the blending mode used to apply the tint, may be
218     *                 {@code null} to clear tint
219     */
220    public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
221        if (mBackgroundTintMode != tintMode) {
222            mBackgroundTintMode = tintMode;
223            mImpl.setBackgroundTintMode(tintMode);
224        }
225    }
226
227    @Override
228    public void setBackgroundDrawable(Drawable background) {
229        Log.i(LOG_TAG, "Setting a custom background is not supported.");
230    }
231
232    @Override
233    public void setBackgroundResource(int resid) {
234        Log.i(LOG_TAG, "Setting a custom background is not supported.");
235    }
236
237    @Override
238    public void setBackgroundColor(int color) {
239        Log.i(LOG_TAG, "Setting a custom background is not supported.");
240    }
241
242    /**
243     * Shows the button.
244     * <p>This method will animate the button show if the view has already been laid out.</p>
245     */
246    public void show() {
247        show(null);
248    }
249
250    /**
251     * Shows the button.
252     * <p>This method will animate the button show if the view has already been laid out.</p>
253     *
254     * @param listener the listener to notify when this view is shown
255     */
256    public void show(@Nullable final OnVisibilityChangedListener listener) {
257        show(listener, true);
258    }
259
260    private void show(OnVisibilityChangedListener listener, boolean fromUser) {
261        mImpl.show(wrapOnVisibilityChangedListener(listener), fromUser);
262    }
263
264    /**
265     * Hides the button.
266     * <p>This method will animate the button hide if the view has already been laid out.</p>
267     */
268    public void hide() {
269        hide(null);
270    }
271
272    /**
273     * Hides the button.
274     * <p>This method will animate the button hide if the view has already been laid out.</p>
275     *
276     * @param listener the listener to notify when this view is hidden
277     */
278    public void hide(@Nullable OnVisibilityChangedListener listener) {
279        hide(listener, true);
280    }
281
282    private void hide(@Nullable OnVisibilityChangedListener listener, boolean fromUser) {
283        mImpl.hide(wrapOnVisibilityChangedListener(listener), fromUser);
284    }
285
286    /**
287     * Set whether FloatingActionButton should add inner padding on platforms Lollipop and after,
288     * to ensure consistent dimensions on all platforms.
289     *
290     * @param useCompatPadding true if FloatingActionButton is adding inner padding on platforms
291     *                         Lollipop and after, to ensure consistent dimensions on all platforms.
292     *
293     * @attr ref android.support.design.R.styleable#FloatingActionButton_useCompatPadding
294     * @see #getUseCompatPadding()
295     */
296    public void setUseCompatPadding(boolean useCompatPadding) {
297        if (mCompatPadding != useCompatPadding) {
298            mCompatPadding = useCompatPadding;
299            mImpl.onCompatShadowChanged();
300        }
301    }
302
303    /**
304     * Returns whether FloatingActionButton will add inner padding on platforms Lollipop and after.
305     *
306     * @return true if FloatingActionButton is adding inner padding on platforms Lollipop and after,
307     * to ensure consistent dimensions on all platforms.
308     *
309     * @attr ref android.support.design.R.styleable#FloatingActionButton_useCompatPadding
310     * @see #setUseCompatPadding(boolean)
311     */
312    public boolean getUseCompatPadding() {
313        return mCompatPadding;
314    }
315
316    @Nullable
317    private InternalVisibilityChangedListener wrapOnVisibilityChangedListener(
318            @Nullable final OnVisibilityChangedListener listener) {
319        if (listener == null) {
320            return null;
321        }
322
323        return new InternalVisibilityChangedListener() {
324            @Override
325            public void onShown() {
326                listener.onShown(FloatingActionButton.this);
327            }
328
329            @Override
330            public void onHidden() {
331                listener.onHidden(FloatingActionButton.this);
332            }
333        };
334    }
335
336    final int getSizeDimension() {
337        switch (mSize) {
338            case SIZE_MINI:
339                return getResources().getDimensionPixelSize(R.dimen.design_fab_size_mini);
340            case SIZE_NORMAL:
341            default:
342                return getResources().getDimensionPixelSize(R.dimen.design_fab_size_normal);
343        }
344    }
345
346    @Override
347    protected void onAttachedToWindow() {
348        super.onAttachedToWindow();
349        mImpl.onAttachedToWindow();
350    }
351
352    @Override
353    protected void onDetachedFromWindow() {
354        super.onDetachedFromWindow();
355        mImpl.onDetachedFromWindow();
356    }
357
358    @Override
359    protected void drawableStateChanged() {
360        super.drawableStateChanged();
361        mImpl.onDrawableStateChanged(getDrawableState());
362    }
363
364    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
365    @Override
366    public void jumpDrawablesToCurrentState() {
367        super.jumpDrawablesToCurrentState();
368        mImpl.jumpDrawableToCurrentState();
369    }
370
371    /**
372     * Return in {@code rect} the bounds of the actual floating action button content in view-local
373     * coordinates. This is defined as anything within any visible shadow.
374     *
375     * @return true if this view actually has been laid out and has a content rect, else false.
376     */
377    public boolean getContentRect(@NonNull Rect rect) {
378        if (ViewCompat.isLaidOut(this)) {
379            rect.set(0, 0, getWidth(), getHeight());
380            rect.left += mShadowPadding.left;
381            rect.top += mShadowPadding.top;
382            rect.right -= mShadowPadding.right;
383            rect.bottom -= mShadowPadding.bottom;
384            return true;
385        } else {
386            return false;
387        }
388    }
389
390    /**
391     * Returns the FloatingActionButton's background, minus any compatible shadow implementation.
392     */
393    @NonNull
394    public Drawable getContentBackground() {
395        return mImpl.getContentBackground();
396    }
397
398    private static int resolveAdjustedSize(int desiredSize, int measureSpec) {
399        int result = desiredSize;
400        int specMode = MeasureSpec.getMode(measureSpec);
401        int specSize = MeasureSpec.getSize(measureSpec);
402        switch (specMode) {
403            case MeasureSpec.UNSPECIFIED:
404                // Parent says we can be as big as we want. Just don't be larger
405                // than max size imposed on ourselves.
406                result = desiredSize;
407                break;
408            case MeasureSpec.AT_MOST:
409                // Parent says we can be as big as we want, up to specSize.
410                // Don't be larger than specSize, and don't be larger than
411                // the max size imposed on ourselves.
412                result = Math.min(desiredSize, specSize);
413                break;
414            case MeasureSpec.EXACTLY:
415                // No choice. Do what we are told.
416                result = specSize;
417                break;
418        }
419        return result;
420    }
421
422    static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
423        switch (value) {
424            case 3:
425                return PorterDuff.Mode.SRC_OVER;
426            case 5:
427                return PorterDuff.Mode.SRC_IN;
428            case 9:
429                return PorterDuff.Mode.SRC_ATOP;
430            case 14:
431                return PorterDuff.Mode.MULTIPLY;
432            case 15:
433                return PorterDuff.Mode.SCREEN;
434            default:
435                return defaultMode;
436        }
437    }
438
439    @Override
440    public boolean onTouchEvent(MotionEvent ev) {
441        if(getContentRect(mTouchArea) && !mTouchArea.contains((int) ev.getX(), (int) ev.getY())) {
442            return false;
443        }
444
445        return super.onTouchEvent(ev);
446    }
447
448    /**
449     * Behavior designed for use with {@link FloatingActionButton} instances. Its main function
450     * is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do
451     * not cover them.
452     */
453    public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
454        // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is
455        // because we can use view translation properties which greatly simplifies the code.
456        private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
457
458        private ValueAnimatorCompat mFabTranslationYAnimator;
459        private float mFabTranslationY;
460        private Rect mTmpRect;
461
462        @Override
463        public boolean layoutDependsOn(CoordinatorLayout parent,
464                FloatingActionButton child, View dependency) {
465            // We're dependent on all SnackbarLayouts (if enabled)
466            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
467        }
468
469        @Override
470        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
471                View dependency) {
472            if (dependency instanceof Snackbar.SnackbarLayout) {
473                updateFabTranslationForSnackbar(parent, child, dependency);
474            } else if (dependency instanceof AppBarLayout) {
475                // If we're depending on an AppBarLayout we will show/hide it automatically
476                // if the FAB is anchored to the AppBarLayout
477                updateFabVisibility(parent, (AppBarLayout) dependency, child);
478            }
479            return false;
480        }
481
482        private boolean updateFabVisibility(CoordinatorLayout parent,
483                AppBarLayout appBarLayout, FloatingActionButton child) {
484            final CoordinatorLayout.LayoutParams lp =
485                    (CoordinatorLayout.LayoutParams) child.getLayoutParams();
486            if (lp.getAnchorId() != appBarLayout.getId()) {
487                // The anchor ID doesn't match the dependency, so we won't automatically
488                // show/hide the FAB
489                return false;
490            }
491
492            if (child.getUserSetVisibility() != VISIBLE) {
493                // The view isn't set to be visible so skip changing its visibility
494                return false;
495            }
496
497            if (mTmpRect == null) {
498                mTmpRect = new Rect();
499            }
500
501            // First, let's get the visible rect of the dependency
502            final Rect rect = mTmpRect;
503            ViewGroupUtils.getDescendantRect(parent, appBarLayout, rect);
504
505            if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
506                // If the anchor's bottom is below the seam, we'll animate our FAB out
507                child.hide(null, false);
508            } else {
509                // Else, we'll animate our FAB back in
510                child.show(null, false);
511            }
512            return true;
513        }
514
515        private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
516                final FloatingActionButton fab, View snackbar) {
517            final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
518            if (mFabTranslationY == targetTransY) {
519                // We're already at (or currently animating to) the target value, return...
520                return;
521            }
522
523            final float currentTransY = ViewCompat.getTranslationY(fab);
524
525            // Make sure that any current animation is cancelled
526            if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) {
527                mFabTranslationYAnimator.cancel();
528            }
529
530            if (fab.isShown()
531                    && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
532                // If the FAB will be travelling by more than 2/3 of its height, let's animate
533                // it instead
534                if (mFabTranslationYAnimator == null) {
535                    mFabTranslationYAnimator = ViewUtils.createAnimator();
536                    mFabTranslationYAnimator.setInterpolator(
537                            AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
538                    mFabTranslationYAnimator.setUpdateListener(
539                            new ValueAnimatorCompat.AnimatorUpdateListener() {
540                                @Override
541                                public void onAnimationUpdate(ValueAnimatorCompat animator) {
542                                    ViewCompat.setTranslationY(fab,
543                                            animator.getAnimatedFloatValue());
544                                }
545                            });
546                }
547                mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY);
548                mFabTranslationYAnimator.start();
549            } else {
550                // Now update the translation Y
551                ViewCompat.setTranslationY(fab, targetTransY);
552            }
553
554            mFabTranslationY = targetTransY;
555        }
556
557        private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
558                FloatingActionButton fab) {
559            float minOffset = 0;
560            final List<View> dependencies = parent.getDependencies(fab);
561            for (int i = 0, z = dependencies.size(); i < z; i++) {
562                final View view = dependencies.get(i);
563                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
564                    minOffset = Math.min(minOffset,
565                            ViewCompat.getTranslationY(view) - view.getHeight());
566                }
567            }
568
569            return minOffset;
570        }
571
572        @Override
573        public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
574                int layoutDirection) {
575            // First, let's make sure that the visibility of the FAB is consistent
576            final List<View> dependencies = parent.getDependencies(child);
577            for (int i = 0, count = dependencies.size(); i < count; i++) {
578                final View dependency = dependencies.get(i);
579                if (dependency instanceof AppBarLayout
580                        && updateFabVisibility(parent, (AppBarLayout) dependency, child)) {
581                    break;
582                }
583            }
584            // Now let the CoordinatorLayout lay out the FAB
585            parent.onLayoutChild(child, layoutDirection);
586            // Now offset it if needed
587            offsetIfNeeded(parent, child);
588            return true;
589        }
590
591        /**
592         * Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method
593         * offsets our layout position so that we're positioned correctly if we're on one of
594         * our parent's edges.
595         */
596        private void offsetIfNeeded(CoordinatorLayout parent, FloatingActionButton fab) {
597            final Rect padding = fab.mShadowPadding;
598
599            if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) {
600                final CoordinatorLayout.LayoutParams lp =
601                        (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
602
603                int offsetTB = 0, offsetLR = 0;
604
605                if (fab.getRight() >= parent.getWidth() - lp.rightMargin) {
606                    // If we're on the left edge, shift it the right
607                    offsetLR = padding.right;
608                } else if (fab.getLeft() <= lp.leftMargin) {
609                    // If we're on the left edge, shift it the left
610                    offsetLR = -padding.left;
611                }
612                if (fab.getBottom() >= parent.getBottom() - lp.bottomMargin) {
613                    // If we're on the bottom edge, shift it down
614                    offsetTB = padding.bottom;
615                } else if (fab.getTop() <= lp.topMargin) {
616                    // If we're on the top edge, shift it up
617                    offsetTB = -padding.top;
618                }
619
620                fab.offsetTopAndBottom(offsetTB);
621                fab.offsetLeftAndRight(offsetLR);
622            }
623        }
624    }
625
626    /**
627     * Returns the backward compatible elevation of the FloatingActionButton.
628     *
629     * @return the backward compatible elevation in pixels.
630     * @attr ref android.support.design.R.styleable#FloatingActionButton_elevation
631     * @see #setCompatElevation(float)
632     */
633    public float getCompatElevation() {
634        return mImpl.getElevation();
635    }
636
637    /**
638     * Updates the backward compatible elevation of the FloatingActionButton.
639     *
640     * @param elevation The backward compatible elevation in pixels.
641     * @attr ref android.support.design.R.styleable#FloatingActionButton_elevation
642     * @see #getCompatElevation()
643     * @see #setUseCompatPadding(boolean)
644     */
645    public void setCompatElevation(float elevation) {
646        mImpl.setElevation(elevation);
647    }
648
649    private static FloatingActionButtonImpl createImpl(FloatingActionButton view,
650            ShadowViewDelegate shadowViewDelegate) {
651        final int sdk = Build.VERSION.SDK_INT;
652        if (sdk >= 21) {
653            return new FloatingActionButtonLollipop(view, shadowViewDelegate);
654        } else if (sdk >= 14) {
655            return new FloatingActionButtonIcs(view, shadowViewDelegate);
656        } else {
657            return new FloatingActionButtonEclairMr1(view, shadowViewDelegate);
658        }
659    }
660
661    private class ShadowDelegateImpl implements ShadowViewDelegate {
662        @Override
663        public float getRadius() {
664            return getSizeDimension() / 2f;
665        }
666
667        @Override
668        public void setShadowPadding(int left, int top, int right, int bottom) {
669            mShadowPadding.set(left, top, right, bottom);
670            setPadding(left + mImagePadding, top + mImagePadding,
671                    right + mImagePadding, bottom + mImagePadding);
672        }
673
674        @Override
675        public void setBackgroundDrawable(Drawable background) {
676            FloatingActionButton.super.setBackgroundDrawable(background);
677        }
678
679        @Override
680        public boolean isCompatPaddingEnabled() {
681            return mCompatPadding;
682        }
683    }
684}
685