package org.succlz123.doubanbooklog.support.com.shamanland.fab;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.ImageButton;
import android.widget.ImageView;
import org.succlz123.doubanbooklog.R;
/**
* A circular button made of paper that lifts and emits ink reactions on press.
* <p/>
* This widget supports two sizes: {@link #SIZE_NORMAL} and {@link #SIZE_MINI}
* according to <a href="http://www.google.com/design/spec/patterns/promoted-actions.html">Promoted Actions</a> pattern.
* <p/>
* Like an {@link ImageView} this widget require {@code android:src} attribute.
* According to official documentation this drawable should be not more than {@code 24dp}.
* <p/>
* Use theme to customize all floating buttons in your app:
* <p/>
* Declare own style:
* <pre>
* <style name="AppTheme.Fab" parent="FloatingActionButton">
* <item name="floatingActionButtonColor">@color/my_fab_color</item>
* </style>
* </pre>
* Link this style in your theme:
* <pre>
* <style name="AppTheme" parent="android:Theme">
* <item name="floatingActionButtonStyle">@style/AppTheme.Fab</item>
* </style>
* </pre>
* <p/>
* Customizing in layout.xml:
* <pre>
* <com.shamanland.fab.FloatingActionButton
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* android:src="@drawable/ic_action_my"
* app:floatingActionButtonColor="@color/my_fab_color"
* app:floatingActionButtonSize="mini"
* />
* </pre>
* <p/>
* Customizing in java-code:
* <pre>
* FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
* fab.setSize(FloatingActionButton.SIZE_MINI);
* fab.setColor(Color.RED);
* // NOTE invoke this method after setting new values!
* fab.initBackground();
* // NOTE standard method of ImageView
* fab.setImageResource(R.drawable.ic_action_my);
* </pre>
*/
public class FloatingActionButton extends ImageButton {
/**
* Constant representing normal size {@code 56dp}. Value: 0x0
*/
public static final int SIZE_NORMAL = 0;
/**
* Constant representing mini size {@code 40dp}. Value: 0x1
*/
public static final int SIZE_MINI = 1;
private int mSize;
private int mColor;
private ColorStateList mColorStateList;
private boolean mShadow;
private boolean mImplicitElevation;
private boolean mUpdateLocked;
private GradientDrawable mCircleDrawable;
/**
* Gets abstract size of this button.
*
* @return {@link #SIZE_NORMAL} or {@link #SIZE_MINI}
*/
public int getSize() {
return mSize;
}
/**
* Sets abstract size for this button.
* <p/>
* Xml attribute: {@code app:floatingActionButtonSize}
*
* @param size {@link #SIZE_NORMAL} or {@link #SIZE_MINI}
*/
public void setSize(int size) {
boolean changed = mSize != size;
mSize = size;
if (changed) {
updateBackground();
}
}
/**
* Gets background color of this button.
*
* @return color
*/
public int getColor() {
return mColor;
}
/**
* Sets background color for this button.
* <p/>
* Xml attribute: {@code app:floatingActionButtonColor}
* <p/>
* NOTE: this method sets the <code>mColorStateList</code> field to <code>null</code>
*
* @param color color
*/
public void setColor(int color) {
boolean changed = mColor != color || mColorStateList != null;
mColor = color;
mColorStateList = null;
if (changed) {
updateBackground();
}
}
public boolean isShadow() {
return mShadow;
}
public void setShadow(boolean shadow) {
boolean changed = mShadow != shadow;
mShadow = shadow;
if (changed) {
updateBackground();
}
}
public boolean isImplicitElevation() {
return mImplicitElevation;
}
public void setImplicitElevation(boolean implicitElevation) {
boolean changed = mImplicitElevation != implicitElevation;
mImplicitElevation = implicitElevation;
if (changed) {
updateBackground();
}
}
/**
* Gets color state list used as background for this button.
*
* @return may be null
*/
public ColorStateList getColorStateList() {
return mColorStateList;
}
/**
* Sets color state list as background for this button.
* <p/>
* Xml attribute: {@code app:floatingActionButtonColor}
*
* @param colorStateList color
*/
public void setColorStateList(ColorStateList colorStateList) {
boolean changed = mColorStateList != colorStateList;
mColorStateList = colorStateList;
if (changed) {
updateBackground();
}
}
public void lockUpdate() {
mUpdateLocked = true;
}
public void unlockUpdate() {
mUpdateLocked = false;
}
public FloatingActionButton(Context context) {
super(context);
init(context, null, 0);
}
public FloatingActionButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, R.attr.floatingActionButtonStyle);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
TypedArray a;
try {
if (isInEditMode()) {
return;
}
if (attrs == null) {
return;
}
Resources.Theme theme = context.getTheme();
if (theme == null) {
return;
}
a = theme.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton, defStyle, R.style.FloatingActionButton_Dark);
if (a == null) {
return;
}
} finally {
mSize = SIZE_NORMAL;
mColor = Color.GRAY;
mColorStateList = null;
mShadow = true;
mImplicitElevation = true;
}
try {
lockUpdate();
initAttrs(a);
} finally {
unlockUpdate();
a.recycle();
}
initBackground();
}
private void initAttrs(TypedArray a) {
setSize(a.getInteger(R.styleable.FloatingActionButton_floatingActionButtonSize, SIZE_NORMAL));
setColor(a.getColor(R.styleable.FloatingActionButton_floatingActionButtonColor, Color.GRAY));
setColorStateList(a.getColorStateList(R.styleable.FloatingActionButton_floatingActionButtonColor));
setShadow(a.getBoolean(R.styleable.FloatingActionButton_floatingActionButtonShadow, true));
setImplicitElevation(a.getBoolean(R.styleable.FloatingActionButton_floatingActionButtonImplicitElevation, true));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mImplicitElevation) {
setElevation(getResources().getDimension(R.dimen.floating_action_button_elevation));
}
}
}
/**
* Inflate and initialize background drawable for this view with arguments
* inflated from xml or specified using {@link #setSize(int)} or {@link #setColor(int)}
* <p/>
* Invoked from constructor, but it's allowed to invoke this method manually from code.
*/
public void initBackground() {
final int backgroundId;
if (mSize == SIZE_MINI) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
backgroundId = R.drawable.com_shamanland_fab_circle_mini;
} else {
backgroundId = R.drawable.com_shamanland_fab_mini;
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
backgroundId = R.drawable.com_shamanland_fab_circle_normal;
} else {
backgroundId = R.drawable.com_shamanland_fab_normal;
}
}
updateBackground(getResources().getDrawable(backgroundId));
}
public void updateBackground() {
if (!mUpdateLocked) {
updateBackground(getBackground());
}
}
private void updateBackground(Drawable background) {
if (background instanceof LayerDrawable) {
LayerDrawable layers = (LayerDrawable) background;
if (layers.getNumberOfLayers() == 2) {
Drawable shadow = layers.getDrawable(0);
Drawable circle = layers.getDrawable(1);
if (shadow instanceof GradientDrawable) {
if (mShadow) {
((GradientDrawable) shadow.mutate()).setGradientRadius(getShadowRadius(shadow, circle));
} else {
shadow.setAlpha(0);
}
}
if (circle instanceof GradientDrawable) {
initCircleDrawable(circle);
}
}
} else if (background instanceof GradientDrawable) {
initCircleDrawable(background);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mImplicitElevation) {
setElevation(mShadow ? getResources().getDimension(R.dimen.floating_action_button_elevation) : 0f);
}
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
//noinspection deprecation
setBackgroundDrawable(background);
} else {
setBackground(background);
}
}
private void initCircleDrawable(Drawable circle) {
mCircleDrawable = (GradientDrawable) circle.mutate();
mCircleDrawable.setColor(mColor);
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mCircleDrawable != null && mColorStateList != null) {
mCircleDrawable.setColor(mColorStateList.getColorForState(getDrawableState(), mColor));
// NOTE maybe this line is required only for Gingerbread
invalidate();
}
}
/**
* Calculates required radius of shadow.
*
* @param shadow underlay drawable
* @param circle overlay drawable
* @return calculated radius, always >= 1
*/
protected static int getShadowRadius(Drawable shadow, Drawable circle) {
int radius = 0;
if (shadow != null && circle != null) {
Rect rect = new Rect();
radius = (circle.getIntrinsicWidth() + (shadow.getPadding(rect) ? rect.left + rect.right : 0)) / 2;
}
return Math.max(1, radius);
}
}