/* * Copyright (C) 2015 Peter Cai * * This file is part of BlackLight * * BlackLight is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * BlackLight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with BlackLight. If not, see <http://www.gnu.org/licenses/>. */ package com.jasonchen.microlang.view; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.Build; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.animation.AccelerateInterpolator; import android.view.animation.OvershootInterpolator; import android.widget.FrameLayout; /* * From GitHub Gist: https://gist.github.com/Jogan/9def6110edf3247825c9 */ public class FloatingActionButton extends View implements Animator.AnimatorListener { final static OvershootInterpolator overshootInterpolator = new OvershootInterpolator(); final static AccelerateInterpolator accelerateInterpolator = new AccelerateInterpolator(); Context context; Paint mButtonPaint; Paint mDrawablePaint; Bitmap mBitmap; Drawable mRipple; boolean mHidden = false; public FloatingActionButton(Context context) { super(context); this.context = context; init(Color.WHITE); } public void setFloatingActionButtonColor(int FloatingActionButtonColor) { init(FloatingActionButtonColor); } public void setFloatingActionButtonDrawable(Drawable FloatingActionButtonDrawable) { mBitmap = ((BitmapDrawable) FloatingActionButtonDrawable).getBitmap(); invalidate(); } public void init(int FloatingActionButtonColor) { setWillNotDraw(false); setClickable(true); mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG); if (Build.VERSION.SDK_INT >= 21) { setLayerType(View.LAYER_TYPE_HARDWARE, null); mRipple = new RippleDrawable(new ColorStateList(new int[][]{ {} }, new int[]{ Color.WHITE }), new ColorDrawable(FloatingActionButtonColor), null); setBackgroundDrawable(mRipple); setOutlineProvider(new ViewOutlineProvider() { @SuppressLint("NewApi") @Override public void getOutline(View view, Outline outline) { outline.setOval(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + getRealWidth(), getPaddingTop() + getRealHeight()); } }); setClipToOutline(true); setElevation(19.6f); } else { setLayerType(View.LAYER_TYPE_SOFTWARE, null); mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mButtonPaint.setColor(FloatingActionButtonColor); mButtonPaint.setStyle(Paint.Style.FILL); mButtonPaint.setShadowLayer(10.0f, 0.0f, 3.5f, Color.argb(100, 0, 0, 0)); } invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mRipple != null) { mRipple.setBounds(0, 0, getWidth(), getHeight()); mRipple.draw(canvas); } else { canvas.drawCircle(getPaddingLeft() + getRealWidth() / 2, getPaddingTop() + getRealHeight() / 2, (float) getRealWidth() / 2.6f, mButtonPaint); } canvas.drawBitmap(mBitmap, getPaddingLeft() + (getRealWidth() - mBitmap.getWidth()) / 2, getPaddingTop() + (getRealHeight() - mBitmap.getHeight()) / 2, mDrawablePaint); } private int getRealWidth() { return getWidth() - getPaddingLeft() - getPaddingRight(); } private int getRealHeight() { return getHeight() - getPaddingTop() - getPaddingBottom(); } @Override public boolean onTouchEvent(MotionEvent event) { boolean ret = super.onTouchEvent(event); if (mRipple == null) { if (event.getAction() == MotionEvent.ACTION_UP) { setAlpha(1.0f); } else if (event.getAction() == MotionEvent.ACTION_DOWN) { setAlpha(0.6f); } } return ret; } @Override public void onAnimationCancel(Animator anim) { } @Override public void onAnimationEnd(Animator anim) { if (mHidden) { setVisibility(View.GONE); } } @Override public void onAnimationRepeat(Animator anim) { } @Override public void onAnimationStart(Animator anim) { } public void hideFloatingActionButton() { if (!mHidden) { ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 1, 0); ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 1, 0); AnimatorSet animSetXY = new AnimatorSet(); animSetXY.playTogether(scaleX, scaleY); animSetXY.setInterpolator(accelerateInterpolator); animSetXY.setDuration(100); animSetXY.start(); animSetXY.addListener(this); mHidden = true; } } public void showFloatingActionButton() { if (mHidden) { setVisibility(View.VISIBLE); ObjectAnimator scaleX = ObjectAnimator.ofFloat(this, "scaleX", 0, 1); ObjectAnimator scaleY = ObjectAnimator.ofFloat(this, "scaleY", 0, 1); AnimatorSet animSetXY = new AnimatorSet(); animSetXY.playTogether(scaleX, scaleY); animSetXY.setInterpolator(overshootInterpolator); animSetXY.setDuration(200); animSetXY.start(); mHidden = false; } } public boolean isHidden() { return mHidden; } static public class Builder { private FrameLayout.LayoutParams params; private final Activity activity; int gravity = Gravity.BOTTOM | Gravity.RIGHT; // default bottom right Drawable drawable; int color = Color.WHITE; int size = 0; float scale = 0; int paddingLeft = 0, paddingTop = 0, paddingBottom = 0, paddingRight = 0; public Builder(Activity 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; this.activity = context; } /** * 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 withPaddings(int left, int top, int right, int bottom) { paddingLeft = convertToPixels(left, scale); paddingTop = convertToPixels(top, scale); paddingRight = convertToPixels(right, scale); paddingBottom = convertToPixels(bottom, scale); return this; } /** * Sets the FAB drawable */ public Builder withDrawable(final Drawable drawable) { this.drawable = drawable; return this; } /** * Sets the FAB color */ public Builder withButtonColor(final int color) { this.color = color; return this; } /** * Sets the FAB size in dp */ public Builder withButtonSize(int size) { if (Build.VERSION.SDK_INT >= 21) { size -= 10; } size = convertToPixels(size, scale); params = new FrameLayout.LayoutParams(size, size); return this; } public FloatingActionButton create() { final FloatingActionButton button = new FloatingActionButton(activity); button.setFloatingActionButtonColor(this.color); button.setFloatingActionButtonDrawable(this.drawable); button.setPadding(paddingLeft, paddingTop, paddingBottom, paddingRight); params.gravity = this.gravity; ViewGroup root = (ViewGroup) activity.findViewById(android.R.id.content); root.addView(button, params); return button; } // The calculation (value * scale + 0.5f) is a widely used to convert to dps to pixel units // based on density scale // see developer.android.com (Supporting Multiple Screen Sizes) private int convertToPixels(int dp, float scale){ return (int) (dp * scale + 0.5f) ; } } }