package com.marshalchen.common.ui.floatingactionbutton; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.RectF; import android.graphics.Shader.TileMode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.StateListDrawable; 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.util.AttributeSet; import android.widget.ImageButton; import com.marshalchen.common.R; public class FloatingActionButton extends ImageButton { public static final int SIZE_NORMAL = 0; public static final int SIZE_MINI = 1; private static final int HALF_TRANSPARENT_WHITE = Color.argb(128, 255, 255, 255); private static final int HALF_TRANSPARENT_BLACK = Color.argb(128, 0, 0, 0); int mColorNormal; int mColorPressed; @DrawableRes private int mIcon; private int mSize; private float mCircleSize; private float mShadowRadius; private float mShadowOffset; private int mDrawableSize; 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) { mColorNormal = getColor(android.R.color.holo_blue_dark); mColorPressed = getColor(android.R.color.holo_blue_light); mIcon = 0; mSize = SIZE_NORMAL; if (attributeSet != null) { initAttributes(context, attributeSet); } mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini); mShadowRadius = getDimension(R.dimen.fab_shadow_radius); mShadowOffset = getDimension(R.dimen.fab_shadow_offset); mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius); updateBackground(); } int getColor(@ColorRes int id) { return getResources().getColor(id); } float getDimension(@DimenRes int id) { return getResources().getDimension(id); } private void initAttributes(Context context, AttributeSet attributeSet) { TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatActionButton, 0, 0); if (attr != null) { try { mColorNormal = attr.getColor(R.styleable.FloatActionButton_colorNormal, getColor(android.R.color.holo_blue_dark)); mColorPressed = attr.getColor(R.styleable.FloatActionButton_colorPressed, getColor(android.R.color.holo_blue_light)); mSize = attr.getInt(R.styleable.FloatActionButton_size, SIZE_NORMAL); mIcon = attr.getResourceId(R.styleable.FloatActionButton_icon, 0); } finally { attr.recycle(); } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mDrawableSize, mDrawableSize); } void updateBackground() { float circleLeft = mShadowRadius; float circleTop = mShadowRadius - mShadowOffset; final RectF circleRect = new RectF(circleLeft, circleTop, circleLeft + mCircleSize, circleTop + mCircleSize); LayerDrawable layerDrawable = new LayerDrawable( new Drawable[] { getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.floating_action_button_fab_bg_normal : R.drawable.floating_action_button_fab_bg_mini), createFillDrawable(circleRect), createStrokesDrawable(circleRect), getIconDrawable() }); float iconOffset = (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2f; int iconInsetHorizontal = (int) (mShadowRadius + iconOffset); int iconInsetTop = (int) (circleTop + iconOffset); int iconInsetBottom = (int) (mShadowRadius + mShadowOffset + iconOffset); layerDrawable.setLayerInset(3, iconInsetHorizontal, iconInsetTop, iconInsetHorizontal, iconInsetBottom); setBackgroundCompat(layerDrawable); } Drawable getIconDrawable() { if (mIcon != 0) { return getResources().getDrawable(mIcon); } else { return new ColorDrawable(Color.TRANSPARENT); } } private StateListDrawable createFillDrawable(RectF circleRect) { StateListDrawable drawable = new StateListDrawable(); drawable.addState(new int[] { android.R.attr.state_pressed }, createCircleDrawable(circleRect, mColorPressed)); drawable.addState(new int[] { }, createCircleDrawable(circleRect, mColorNormal)); return drawable; } private Drawable createCircleDrawable(RectF circleRect, int color) { final Bitmap bitmap = Bitmap.createBitmap(mDrawableSize, mDrawableSize, Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); final Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(color); canvas.drawOval(circleRect, paint); return new BitmapDrawable(getResources(), bitmap); } private int opacityToAlpha(float opacity) { return (int) (255f * opacity); } private Drawable createStrokesDrawable(RectF circleRect) { final Bitmap bitmap = Bitmap.createBitmap(mDrawableSize, mDrawableSize, Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); final float strokeWidth = getDimension(R.dimen.fab_stroke_width); final float halfStrokeWidth = strokeWidth / 2f; RectF outerStrokeRect = new RectF( circleRect.left - halfStrokeWidth, circleRect.top - halfStrokeWidth, circleRect.right + halfStrokeWidth, circleRect.bottom + halfStrokeWidth ); RectF innerStrokeRect = new RectF( circleRect.left + halfStrokeWidth, circleRect.top + halfStrokeWidth, circleRect.right - halfStrokeWidth, circleRect.bottom - halfStrokeWidth ); final Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStrokeWidth(strokeWidth); paint.setStyle(Style.STROKE); // outer paint.setColor(Color.BLACK); paint.setAlpha(opacityToAlpha(0.02f)); canvas.drawOval(outerStrokeRect, paint); // inner bottom paint.setShader(new LinearGradient(innerStrokeRect.centerX(), innerStrokeRect.top, innerStrokeRect.centerX(), innerStrokeRect.bottom, new int[] { Color.TRANSPARENT, HALF_TRANSPARENT_BLACK, Color.BLACK }, new float[] { 0f, 0.8f, 1f }, TileMode.CLAMP )); paint.setAlpha(opacityToAlpha(0.04f)); canvas.drawOval(innerStrokeRect, paint); // inner top paint.setShader(new LinearGradient(innerStrokeRect.centerX(), innerStrokeRect.top, innerStrokeRect.centerX(), innerStrokeRect.bottom, new int[] { Color.WHITE, HALF_TRANSPARENT_WHITE, Color.TRANSPARENT }, new float[] { 0f, 0.2f, 1f }, TileMode.CLAMP )); paint.setAlpha(opacityToAlpha(0.8f)); canvas.drawOval(innerStrokeRect, paint); return new BitmapDrawable(getResources(), bitmap); } @SuppressWarnings("deprecation") @SuppressLint("NewApi") private void setBackgroundCompat(Drawable drawable) { if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { setBackground(drawable); } else { setBackgroundDrawable(drawable); } } }