mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2025-12-12 17:12:40 +01:00
432 lines
12 KiB
Java
432 lines
12 KiB
Java
package net.i2p.android.ext.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;
|
|
|
|
import org.petero.droidfish.R;
|
|
|
|
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(R.color.default_normal));
|
|
mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, getColor(R.color.default_pressed));
|
|
mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, getColor(R.color.default_disabled));
|
|
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);
|
|
}
|
|
}
|