blob: d932d873caa420de0452d0090db9acf7091e50eb [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.design.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.design.R;
import android.util.AttributeSet;
import android.widget.Checkable;
import android.widget.ImageView;
/**
* Floating action buttons are used for a special type of promoted action. They are distinguished
* by
* a circled icon floating above the UI and have special motion behaviors related to morphing,
* launching, and the transferring anchor point.
*
* Floating action buttons come in two sizes: the default, which should be used in most cases, and
* the mini, which should only be used to create visual continuity with other elements on the
* screen.
*/
public class FloatingActionButton extends ImageView {
// These values must match those in the attrs declaration
private static final int SIZE_MINI = 1;
private static final int SIZE_NORMAL = 0;
private ColorStateList mBackgroundTint;
private PorterDuff.Mode mBackgroundTintMode;
private int mRippleColor;
private int mSize;
private int mContentPadding;
private final Rect mShadowPadding;
private final FloatingActionButtonImpl mImpl;
public FloatingActionButton(Context context) {
this(context, null);
}
public FloatingActionButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mShadowPadding = new Rect();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.FloatingActionButton, defStyleAttr,
R.style.Widget_Design_FloatingActionButton);
Drawable background = a.getDrawable(R.styleable.FloatingActionButton_android_background);
mBackgroundTint = a.getColorStateList(R.styleable.FloatingActionButton_backgroundTint);
mBackgroundTintMode = parseTintMode(a.getInt(
R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL);
final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
final float pressedTranslationZ = a.getDimension(
R.styleable.FloatingActionButton_pressedTranslationZ, 0f);
a.recycle();
final ShadowViewDelegate delegate = new ShadowViewDelegate() {
@Override
public float getRadius() {
return getSizeDimension() / 2f;
}
@Override
public void setShadowPadding(int left, int top, int right, int bottom) {
mShadowPadding.set(left, top, right, bottom);
setPadding(left + mContentPadding, top + mContentPadding,
right + mContentPadding, bottom + mContentPadding);
}
@Override
public void setBackgroundDrawable(Drawable background) {
FloatingActionButton.super.setBackgroundDrawable(background);
}
};
if (Build.VERSION.SDK_INT >= 21) {
mImpl = new FloatingActionButtonLollipop(this, delegate);
} else {
mImpl = new FloatingActionButtonEclairMr1(this, delegate);
}
final int maxContentSize = (int) getResources().getDimension(R.dimen.fab_content_size);
mContentPadding = (getSizeDimension() - maxContentSize) / 2;
mImpl.setBackgroundDrawable(background, mBackgroundTint,
mBackgroundTintMode, mRippleColor);
mImpl.setElevation(elevation);
mImpl.setPressedTranslationZ(pressedTranslationZ);
setClickable(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int preferredSize = getSizeDimension();
final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec);
final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec);
// As we want to stay circular, we set both dimensions to be the
// smallest resolved dimension
final int d = Math.min(w, h);
// We add the shadow's padding to the measured dimension
setMeasuredDimension(
d + mShadowPadding.left + mShadowPadding.right,
d + mShadowPadding.top + mShadowPadding.bottom);
}
/**
* Set the ripple color for this {@link FloatingActionButton}.
* <p>
* When running on devices with KitKat or below, we draw a fill rather than a ripple.
*
* @param color ARGB color to use for the ripple.
*/
public void setRippleColor(int color) {
if (mRippleColor != color) {
mRippleColor = color;
mImpl.setRippleColor(color);
}
}
/**
* Return the tint applied to the background drawable, if specified.
*
* @return the tint applied to the background drawable
* @see #setBackgroundTintList(ColorStateList)
*/
@Nullable
@Override
public ColorStateList getBackgroundTintList() {
return mBackgroundTint;
}
/**
* Applies a tint to the background drawable. Does not modify the current tint
* mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
*
* @param tint the tint to apply, may be {@code null} to clear tint
*/
public void setBackgroundTintList(@Nullable ColorStateList tint) {
mImpl.setBackgroundTintList(tint);
}
/**
* Return the blending mode used to apply the tint to the background
* drawable, if specified.
*
* @return the blending mode used to apply the tint to the background
* drawable
* @see #setBackgroundTintMode(PorterDuff.Mode)
*/
@Nullable
@Override
public PorterDuff.Mode getBackgroundTintMode() {
return mBackgroundTintMode;
}
/**
* Specifies the blending mode used to apply the tint specified by
* {@link #setBackgroundTintList(ColorStateList)}} to the background
* drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
*
* @param tintMode the blending mode used to apply the tint, may be
* {@code null} to clear tint
*/
public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
mImpl.setBackgroundTintMode(tintMode);
}
@Override
public void setBackgroundDrawable(Drawable background) {
if (mImpl != null) {
mImpl.setBackgroundDrawable(
background, mBackgroundTint, mBackgroundTintMode, mRippleColor);
}
}
final int getSizeDimension() {
switch (mSize) {
case SIZE_MINI:
return getResources().getDimensionPixelSize(R.dimen.fab_size_mini);
case SIZE_NORMAL:
default:
return getResources().getDimensionPixelSize(R.dimen.fab_size_normal);
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
mImpl.onDrawableStateChanged(getDrawableState());
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
mImpl.jumpDrawableToCurrentState();
}
private static int resolveAdjustedSize(int desiredSize, int measureSpec) {
int result = desiredSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
// Parent says we can be as big as we want. Just don't be larger
// than max size imposed on ourselves.
result = desiredSize;
break;
case MeasureSpec.AT_MOST:
// Parent says we can be as big as we want, up to specSize.
// Don't be larger than specSize, and don't be larger than
// the max size imposed on ourselves.
result = Math.min(desiredSize, specSize);
break;
case MeasureSpec.EXACTLY:
// No choice. Do what we are told.
result = specSize;
break;
}
return result;
}
static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
switch (value) {
case 3:
return PorterDuff.Mode.SRC_OVER;
case 5:
return PorterDuff.Mode.SRC_IN;
case 9:
return PorterDuff.Mode.SRC_ATOP;
case 14:
return PorterDuff.Mode.MULTIPLY;
case 15:
return PorterDuff.Mode.SCREEN;
default:
return defaultMode;
}
}
}