FloatingActionButton.java revision d039e3555848f678a2e5363e99026df322d02044
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.Nullable;
28import android.support.design.R;
29import android.util.AttributeSet;
30import android.widget.Checkable;
31import android.widget.ImageView;
32
33/**
34 * Floating action buttons are used for a special type of promoted action. They are distinguished
35 * by
36 * a circled icon floating above the UI and have special motion behaviors related to morphing,
37 * launching, and the transferring anchor point.
38 *
39 * Floating action buttons come in two sizes: the default, which should be used in most cases, and
40 * the mini, which should only be used to create visual continuity with other elements on the
41 * screen.
42 */
43public class FloatingActionButton extends ImageView {
44
45    // These values must match those in the attrs declaration
46    private static final int SIZE_MINI = 1;
47    private static final int SIZE_NORMAL = 0;
48
49    private ColorStateList mBackgroundTint;
50    private PorterDuff.Mode mBackgroundTintMode;
51
52    private int mRippleColor;
53    private int mSize;
54    private int mContentPadding;
55
56    private final Rect mShadowPadding;
57
58    private final FloatingActionButtonImpl mImpl;
59
60    public FloatingActionButton(Context context) {
61        this(context, null);
62    }
63
64    public FloatingActionButton(Context context, AttributeSet attrs) {
65        this(context, attrs, 0);
66    }
67
68    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
69        super(context, attrs, defStyleAttr);
70
71        mShadowPadding = new Rect();
72
73        TypedArray a = context.obtainStyledAttributes(attrs,
74                R.styleable.FloatingActionButton, defStyleAttr,
75                R.style.Widget_Design_FloatingActionButton);
76        Drawable background = a.getDrawable(R.styleable.FloatingActionButton_android_background);
77        mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint);
78        mBackgroundTintMode = parseTintMode(a.getInt(
79                R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
80        mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
81        mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL);
82        final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
83        final float pressedTranslationZ = a.getDimension(
84                R.styleable.FloatingActionButton_pressedTranslationZ, 0f);
85        a.recycle();
86
87        final ShadowViewDelegate delegate = new ShadowViewDelegate() {
88            @Override
89            public float getRadius() {
90                return getSizeDimension() / 2f;
91            }
92
93            @Override
94            public void setShadowPadding(int left, int top, int right, int bottom) {
95                mShadowPadding.set(left, top, right, bottom);
96
97                setPadding(left + mContentPadding, top + mContentPadding,
98                        right + mContentPadding, bottom + mContentPadding);
99            }
100
101            @Override
102            public void setBackgroundDrawable(Drawable background) {
103                FloatingActionButton.super.setBackgroundDrawable(background);
104            }
105        };
106
107        if (Build.VERSION.SDK_INT >= 21) {
108            mImpl = new FloatingActionButtonLollipop(this, delegate);
109        } else {
110            mImpl = new FloatingActionButtonEclairMr1(this, delegate);
111        }
112
113        final int maxContentSize = (int) getResources().getDimension(R.dimen.fab_content_size);
114        mContentPadding = (getSizeDimension() - maxContentSize) / 2;
115
116        mImpl.setBackgroundDrawable(background, mBackgroundTint,
117                mBackgroundTintMode, mRippleColor);
118        mImpl.setElevation(elevation);
119        mImpl.setPressedTranslationZ(pressedTranslationZ);
120
121        setClickable(true);
122    }
123
124    @Override
125    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
126        final int preferredSize = getSizeDimension();
127
128        final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec);
129        final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec);
130
131        // As we want to stay circular, we set both dimensions to be the
132        // smallest resolved dimension
133        final int d = Math.min(w, h);
134
135        // We add the shadow's padding to the measured dimension
136        setMeasuredDimension(
137                d + mShadowPadding.left + mShadowPadding.right,
138                d + mShadowPadding.top + mShadowPadding.bottom);
139    }
140
141    /**
142     * Set the ripple color for this {@link FloatingActionButton}.
143     * <p>
144     * When running on devices with KitKat or below, we draw a fill rather than a ripple.
145     *
146     * @param color ARGB color to use for the ripple.
147     */
148    public void setRippleColor(int color) {
149        if (mRippleColor != color) {
150            mRippleColor = color;
151            mImpl.setRippleColor(color);
152        }
153    }
154
155    /**
156     * Return the tint applied to the background drawable, if specified.
157     *
158     * @return the tint applied to the background drawable
159     * @see #setBackgroundTintList(ColorStateList)
160     */
161    @Nullable
162    @Override
163    public ColorStateList getBackgroundTintList() {
164        return mBackgroundTint;
165    }
166
167    /**
168     * Applies a tint to the background drawable. Does not modify the current tint
169     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
170     *
171     * @param tint the tint to apply, may be {@code null} to clear tint
172     */
173    public void setBackgroundTintList(@Nullable ColorStateList tint) {
174        mImpl.setBackgroundTintList(tint);
175    }
176
177
178    /**
179     * Return the blending mode used to apply the tint to the background
180     * drawable, if specified.
181     *
182     * @return the blending mode used to apply the tint to the background
183     *         drawable
184     * @see #setBackgroundTintMode(PorterDuff.Mode)
185     */
186    @Nullable
187    @Override
188    public PorterDuff.Mode getBackgroundTintMode() {
189        return mBackgroundTintMode;
190    }
191
192    /**
193     * Specifies the blending mode used to apply the tint specified by
194     * {@link #setBackgroundTintList(ColorStateList)}} to the background
195     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
196     *
197     * @param tintMode the blending mode used to apply the tint, may be
198     *                 {@code null} to clear tint
199     */
200    public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
201        mImpl.setBackgroundTintMode(tintMode);
202    }
203
204    @Override
205    public void setBackgroundDrawable(Drawable background) {
206        if (mImpl != null) {
207            mImpl.setBackgroundDrawable(
208                background, mBackgroundTint, mBackgroundTintMode, mRippleColor);
209        }
210    }
211
212    final int getSizeDimension() {
213        switch (mSize) {
214            case SIZE_MINI:
215                return getResources().getDimensionPixelSize(R.dimen.fab_size_mini);
216            case SIZE_NORMAL:
217            default:
218                return getResources().getDimensionPixelSize(R.dimen.fab_size_normal);
219        }
220    }
221
222    @Override
223    protected void drawableStateChanged() {
224        super.drawableStateChanged();
225        mImpl.onDrawableStateChanged(getDrawableState());
226    }
227
228    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
229    @Override
230    public void jumpDrawablesToCurrentState() {
231        super.jumpDrawablesToCurrentState();
232        mImpl.jumpDrawableToCurrentState();
233    }
234
235    private static int resolveAdjustedSize(int desiredSize, int measureSpec) {
236        int result = desiredSize;
237        int specMode = MeasureSpec.getMode(measureSpec);
238        int specSize = MeasureSpec.getSize(measureSpec);
239        switch (specMode) {
240            case MeasureSpec.UNSPECIFIED:
241                // Parent says we can be as big as we want. Just don't be larger
242                // than max size imposed on ourselves.
243                result = desiredSize;
244                break;
245            case MeasureSpec.AT_MOST:
246                // Parent says we can be as big as we want, up to specSize.
247                // Don't be larger than specSize, and don't be larger than
248                // the max size imposed on ourselves.
249                result = Math.min(desiredSize, specSize);
250                break;
251            case MeasureSpec.EXACTLY:
252                // No choice. Do what we are told.
253                result = specSize;
254                break;
255        }
256        return result;
257    }
258
259    static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
260        switch (value) {
261            case 3:
262                return PorterDuff.Mode.SRC_OVER;
263            case 5:
264                return PorterDuff.Mode.SRC_IN;
265            case 9:
266                return PorterDuff.Mode.SRC_ATOP;
267            case 14:
268                return PorterDuff.Mode.MULTIPLY;
269            case 15:
270                return PorterDuff.Mode.SCREEN;
271            default:
272                return defaultMode;
273        }
274    }
275}
276