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