package zenproject.meditation.android.ui.menu.buttons; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; import com.nineoldandroids.view.ViewPropertyAnimator; import zenproject.meditation.android.ContextRetriever; import zenproject.meditation.android.R; @SuppressWarnings("TooManyMethods") public class FloatingActionButton extends View { private static final float SHADOW_RADIUS_TOUCHED = ContextRetriever.INSTANCE.getResources().getDimensionPixelSize(R.dimen.shadow_fab_touch); private static final float SHADOW_RADIUS_RELEASED = ContextRetriever.INSTANCE.getResources().getDimensionPixelSize(R.dimen.shadow_fab_untouch); private static final float SHADOW_OFFSET_X = 0f; private static final float SHADOW_OFFSET_Y = 0f; private static final int SHADOW_COLOR_TOUCHED = Color.argb(150, 0, 0, 0); private static final int SHADOW_COLOR_RELEASED = Color.argb(100, 0, 0, 0); private static final int ROTATION_TIME_MILLISECONDS = 400; private static final int TOUCHED_ROTATION = 90; private static final int RELEASED_ROTATION = 0; private final Paint buttonPaint; private final Paint drawablePaint; private Bitmap bitmap; private boolean hidden; private boolean touching; private boolean rotated; private boolean rotating; private int color; private int pressedColor; private float angle = RELEASED_ROTATION; public FloatingActionButton(Context context) { super(context); setWillNotDraw(false); setClickable(true); setLayerType(View.LAYER_TYPE_SOFTWARE, null); buttonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); buttonPaint.setStyle(Paint.Style.FILL); drawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG); } public void setFloatingActionButtonColor(int floatingActionButtonColor) { this.color = floatingActionButtonColor; } public void setFloatingActionPressedButtonColor(int floatingActionButtonPressedColor) { this.pressedColor = floatingActionButtonPressedColor; } public void setFloatingActionButtonDrawable(Bitmap floatingActionButtonDrawable) { bitmap = floatingActionButtonDrawable; invalidate(); } @Override protected void onDraw(Canvas canvas) { buttonPaint.setShadowLayer(touching ? SHADOW_RADIUS_TOUCHED : SHADOW_RADIUS_RELEASED, SHADOW_OFFSET_X, SHADOW_OFFSET_Y, touching ? SHADOW_COLOR_TOUCHED : SHADOW_COLOR_RELEASED); buttonPaint.setColor(touching ? pressedColor : color); canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, getWidth() / 2.6f, buttonPaint); canvas.save(); canvas.rotate(angle, getWidth() / 2f, getHeight() / 2f); canvas.drawBitmap(bitmap, (getWidth() - bitmap.getWidth()) / 2f, (getHeight() - bitmap.getHeight()) / 2f, drawablePaint); canvas.restore(); } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { touching = false; invalidate(); } else if (event.getAction() == MotionEvent.ACTION_DOWN) { touching = true; invalidate(); } return super.onTouchEvent(event); } public void hide() { if (!isHidden()) { ViewPropertyAnimator.animate(this).setInterpolator(new AccelerateDecelerateInterpolator()) .translationY(-getBottom()); hidden = true; } } public boolean isHidden() { return hidden; } public void show() { if (isHidden()) { ViewPropertyAnimator.animate(this).setInterpolator(new AccelerateDecelerateInterpolator()) .translationY(0); hidden = false; } } public void rotate() { if (!rotating) { float startAngle = rotated ? TOUCHED_ROTATION : RELEASED_ROTATION; float endAngle = rotated ? RELEASED_ROTATION : TOUCHED_ROTATION; Animator animator = ObjectAnimator.ofFloat(this, "angle", startAngle, endAngle); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.setDuration(ROTATION_TIME_MILLISECONDS); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); rotating = true; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); rotated ^= true; rotating = false; } }); animator.start(); } } public Point getCentre() { return new Point((int) getX() + getWidth() / 2, (int) getY() + getHeight() / 2); } protected float getAngle() { return angle; } protected void setAngle(float angle) { this.angle = angle; invalidate(); } public static class Builder { private final int size; private final float scale; private int id; private FrameLayout.LayoutParams params; private int gravity = Gravity.BOTTOM | Gravity.END; private Drawable drawable; private int color = Color.WHITE; private int pressedColor; public Builder(Context context) { scale = context.getResources().getDisplayMetrics().density; size = convertToPixels(72, scale); // default size is 72dp by 72dp params = new FrameLayout.LayoutParams(size, size); params.gravity = gravity; } private int convertToPixels(int dp, float scale) { return (int) (dp * scale + 0.5f); } /** * Sets the gravity for the FAB */ public Builder withGravity(int gravity) { this.gravity = gravity; return this; } /** * Sets the margins for the FAB in dp */ public Builder withMargins(int left, int top, int right, int bottom) { params.setMargins(left, top, right, bottom); return this; } /** * Sets the FAB drawable */ public Builder withDrawable(final Drawable drawable) { this.drawable = drawable; return this; } /** * Set the Tag */ /** * Sets the ID */ public Builder withId(final int id) { this.id = id; return this; } /** * Sets the FAB color */ public Builder withButtonColor(final int color) { this.color = color; return this; } /** * Sets the FAB color */ public Builder withButtonPressedColor(final int color) { this.pressedColor = color; return this; } /** * Sets the FAB size in dp */ public Builder withButtonSize(int size) { params = new FrameLayout.LayoutParams(size, size); return this; } /** * Creates a FloatingActionButton and attaches it to the rootView. * * @param rootView * @return */ public FloatingActionButton createInto(ViewGroup rootView) { final FloatingActionButton button = new FloatingActionButton(rootView.getContext().getApplicationContext()); Bitmap scaledBitmap = Bitmap.createScaledBitmap(((BitmapDrawable) drawable).getBitmap(), params.width / 2, params.height / 2, true); button.setFloatingActionButtonColor(this.color); button.setFloatingActionPressedButtonColor(this.pressedColor == 0 ? getDarkerFrom(this.color) : this.pressedColor); button.setFloatingActionButtonDrawable(scaledBitmap); button.setId(this.id); params.gravity = this.gravity; rootView.addView(button, params); return button; } private int getDarkerFrom(int color) { return Color.rgb((int) (Color.red(color) / 1.1f), (int) (Color.green(color) / 1.1f), (int) (Color.blue(color) / 1.1f)); } } }