package com.getbase.floatingactionbutton;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.ShapeDrawable.ShaderFactory;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.widget.ImageButton;
import android.widget.TextView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class FloatingActionButton extends ImageButton {
public static final int SIZE_NORMAL = 0;
public static final int SIZE_MINI = 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ SIZE_NORMAL, SIZE_MINI })
public @interface FAB_SIZE {
}
int mColorNormal;
int mColorPressed;
int mColorDisabled;
String mTitle;
@DrawableRes
private int mIcon;
private Drawable mIconDrawable;
private int mSize;
private float mCircleSize;
private float mShadowRadius;
private float mShadowOffset;
private int mDrawableSize;
boolean mStrokeVisible;
public FloatingActionButton(Context context) {
this(context, null);
}
public FloatingActionButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
void init(Context context, AttributeSet attributeSet) {
TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionButton, 0, 0);
mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal, getColor(android.R.color.holo_blue_dark));
mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, getColor(android.R.color.holo_blue_light));
mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, getColor(android.R.color.darker_gray));
mSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL);
mIcon = attr.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0);
mTitle = attr.getString(R.styleable.FloatingActionButton_fab_title);
mStrokeVisible = attr.getBoolean(R.styleable.FloatingActionButton_fab_stroke_visible, true);
attr.recycle();
updateCircleSize();
mShadowRadius = getDimension(R.dimen.fab_shadow_radius);
mShadowOffset = getDimension(R.dimen.fab_shadow_offset);
updateDrawableSize();
updateBackground();
}
private void updateDrawableSize() {
mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius);
}
private void updateCircleSize() {
mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini);
}
public void setSize(@FAB_SIZE int size) {
if (size != SIZE_MINI && size != SIZE_NORMAL) {
throw new IllegalArgumentException("Use @FAB_SIZE constants only!");
}
if (mSize != size) {
mSize = size;
updateCircleSize();
updateDrawableSize();
updateBackground();
}
}
@FAB_SIZE
public int getSize() {
return mSize;
}
public void setIcon(@DrawableRes int icon) {
if (mIcon != icon) {
mIcon = icon;
mIconDrawable = null;
updateBackground();
}
}
public void setIconDrawable(@NonNull Drawable iconDrawable) {
if (mIconDrawable != iconDrawable) {
mIcon = 0;
mIconDrawable = iconDrawable;
updateBackground();
}
}
/**
* @return the current Color for normal state.
*/
public int getColorNormal() {
return mColorNormal;
}
public void setColorNormalResId(@ColorRes int colorNormal) {
setColorNormal(getColor(colorNormal));
}
public void setColorNormal(int color) {
if (mColorNormal != color) {
mColorNormal = color;
updateBackground();
}
}
/**
* @return the current color for pressed state.
*/
public int getColorPressed() {
return mColorPressed;
}
public void setColorPressedResId(@ColorRes int colorPressed) {
setColorPressed(getColor(colorPressed));
}
public void setColorPressed(int color) {
if (mColorPressed != color) {
mColorPressed = color;
updateBackground();
}
}
/**
* @return the current color for disabled state.
*/
public int getColorDisabled() {
return mColorDisabled;
}
public void setColorDisabledResId(@ColorRes int colorDisabled) {
setColorDisabled(getColor(colorDisabled));
}
public void setColorDisabled(int color) {
if (mColorDisabled != color) {
mColorDisabled = color;
updateBackground();
}
}
public void setStrokeVisible(boolean visible) {
if (mStrokeVisible != visible) {
mStrokeVisible = visible;
updateBackground();
}
}
public boolean isStrokeVisible() {
return mStrokeVisible;
}
int getColor(@ColorRes int id) {
return getResources().getColor(id);
}
float getDimension(@DimenRes int id) {
return getResources().getDimension(id);
}
public void setTitle(String title) {
mTitle = title;
TextView label = getLabelView();
if (label != null) {
label.setText(title);
}
}
TextView getLabelView() {
return (TextView) getTag(R.id.fab_label);
}
public String getTitle() {
return mTitle;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(mDrawableSize, mDrawableSize);
}
void updateBackground() {
final float strokeWidth = getDimension(R.dimen.fab_stroke_width);
final float halfStrokeWidth = strokeWidth / 2f;
LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[] {
getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.fab_bg_normal : R.drawable.fab_bg_mini),
createFillDrawable(strokeWidth),
createOuterStrokeDrawable(strokeWidth),
getIconDrawable()
});
int iconOffset = (int) (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2;
int circleInsetHorizontal = (int) (mShadowRadius);
int circleInsetTop = (int) (mShadowRadius - mShadowOffset);
int circleInsetBottom = (int) (mShadowRadius + mShadowOffset);
layerDrawable.setLayerInset(1,
circleInsetHorizontal,
circleInsetTop,
circleInsetHorizontal,
circleInsetBottom);
layerDrawable.setLayerInset(2,
(int) (circleInsetHorizontal - halfStrokeWidth),
(int) (circleInsetTop - halfStrokeWidth),
(int) (circleInsetHorizontal - halfStrokeWidth),
(int) (circleInsetBottom - halfStrokeWidth));
layerDrawable.setLayerInset(3,
circleInsetHorizontal + iconOffset,
circleInsetTop + iconOffset,
circleInsetHorizontal + iconOffset,
circleInsetBottom + iconOffset);
setBackgroundCompat(layerDrawable);
}
Drawable getIconDrawable() {
if (mIconDrawable != null) {
return mIconDrawable;
} else if (mIcon != 0) {
return getResources().getDrawable(mIcon);
} else {
return new ColorDrawable(Color.TRANSPARENT);
}
}
private StateListDrawable createFillDrawable(float strokeWidth) {
StateListDrawable drawable = new StateListDrawable();
drawable.addState(new int[] { -android.R.attr.state_enabled }, createCircleDrawable(mColorDisabled, strokeWidth));
drawable.addState(new int[] { android.R.attr.state_pressed }, createCircleDrawable(mColorPressed, strokeWidth));
drawable.addState(new int[] { }, createCircleDrawable(mColorNormal, strokeWidth));
return drawable;
}
private Drawable createCircleDrawable(int color, float strokeWidth) {
int alpha = Color.alpha(color);
int opaqueColor = opaque(color);
ShapeDrawable fillDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = fillDrawable.getPaint();
paint.setAntiAlias(true);
paint.setColor(opaqueColor);
Drawable[] layers = {
fillDrawable,
createInnerStrokesDrawable(opaqueColor, strokeWidth)
};
LayerDrawable drawable = alpha == 255 || !mStrokeVisible
? new LayerDrawable(layers)
: new TranslucentLayerDrawable(alpha, layers);
int halfStrokeWidth = (int) (strokeWidth / 2f);
drawable.setLayerInset(1, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth);
return drawable;
}
private static class TranslucentLayerDrawable extends LayerDrawable {
private final int mAlpha;
public TranslucentLayerDrawable(int alpha, Drawable... layers) {
super(layers);
mAlpha = alpha;
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, mAlpha, Canvas.ALL_SAVE_FLAG);
super.draw(canvas);
canvas.restore();
}
}
private Drawable createOuterStrokeDrawable(float strokeWidth) {
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
paint.setColor(Color.BLACK);
paint.setAlpha(opacityToAlpha(0.02f));
return shapeDrawable;
}
private int opacityToAlpha(float opacity) {
return (int) (255f * opacity);
}
private int darkenColor(int argb) {
return adjustColorBrightness(argb, 0.9f);
}
private int lightenColor(int argb) {
return adjustColorBrightness(argb, 1.1f);
}
private int adjustColorBrightness(int argb, float factor) {
float[] hsv = new float[3];
Color.colorToHSV(argb, hsv);
hsv[2] = Math.min(hsv[2] * factor, 1f);
return Color.HSVToColor(Color.alpha(argb), hsv);
}
private int halfTransparent(int argb) {
return Color.argb(
Color.alpha(argb) / 2,
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private int opaque(int argb) {
return Color.rgb(
Color.red(argb),
Color.green(argb),
Color.blue(argb)
);
}
private Drawable createInnerStrokesDrawable(final int color, float strokeWidth) {
if (!mStrokeVisible) {
return new ColorDrawable(Color.TRANSPARENT);
}
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
final int bottomStrokeColor = darkenColor(color);
final int bottomStrokeColorHalfTransparent = halfTransparent(bottomStrokeColor);
final int topStrokeColor = lightenColor(color);
final int topStrokeColorHalfTransparent = halfTransparent(topStrokeColor);
final Paint paint = shapeDrawable.getPaint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Style.STROKE);
shapeDrawable.setShaderFactory(new ShaderFactory() {
@Override
public Shader resize(int width, int height) {
return new LinearGradient(width / 2, 0, width / 2, height,
new int[] { topStrokeColor, topStrokeColorHalfTransparent, color, bottomStrokeColorHalfTransparent, bottomStrokeColor },
new float[] { 0f, 0.2f, 0.5f, 0.8f, 1f },
TileMode.CLAMP
);
}
});
return shapeDrawable;
}
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
private void setBackgroundCompat(Drawable drawable) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
setBackground(drawable);
} else {
setBackgroundDrawable(drawable);
}
}
@Override
public void setVisibility(int visibility) {
TextView label = getLabelView();
if (label != null) {
label.setVisibility(visibility);
}
super.setVisibility(visibility);
}
}