package com.github.clans.fab; import android.annotation.TargetApi; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.graphics.Xfermode; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.StateListDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.os.Build; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewOutlineProvider; import android.view.animation.Animation; import android.widget.TextView; public class Label extends TextView { private static final Xfermode PORTER_DUFF_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); private int mShadowRadius; private int mShadowXOffset; private int mShadowYOffset; private int mShadowColor; private Drawable mBackgroundDrawable; private boolean mShowShadow = true; private int mRawWidth; private int mRawHeight; private int mColorNormal; private int mColorPressed; private int mColorRipple; private int mCornerRadius; private FloatingActionButton mFab; private Animation mShowAnimation; private Animation mHideAnimation; private boolean mUsingStyle; private boolean mHandleVisibilityChanges = true; public Label(Context context) { super(context); } public Label(Context context, AttributeSet attrs) { super(context, attrs); } public Label(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(calculateMeasuredWidth(), calculateMeasuredHeight()); } private int calculateMeasuredWidth() { if (mRawWidth == 0) { mRawWidth = getMeasuredWidth(); } return getMeasuredWidth() + calculateShadowWidth(); } private int calculateMeasuredHeight() { if (mRawHeight == 0) { mRawHeight = getMeasuredHeight(); } return getMeasuredHeight() + calculateShadowHeight(); } int calculateShadowWidth() { return mShowShadow ? (mShadowRadius + Math.abs(mShadowXOffset)) : 0; } int calculateShadowHeight() { return mShowShadow ? (mShadowRadius + Math.abs(mShadowYOffset)) : 0; } void updateBackground() { LayerDrawable layerDrawable; if (mShowShadow) { layerDrawable = new LayerDrawable(new Drawable[]{ new Shadow(), createFillDrawable() }); int leftInset = mShadowRadius + Math.abs(mShadowXOffset); int topInset = mShadowRadius + Math.abs(mShadowYOffset); int rightInset = (mShadowRadius + Math.abs(mShadowXOffset)); int bottomInset = (mShadowRadius + Math.abs(mShadowYOffset)); layerDrawable.setLayerInset( 1, leftInset, topInset, rightInset, bottomInset ); } else { layerDrawable = new LayerDrawable(new Drawable[]{ createFillDrawable() }); } setBackgroundCompat(layerDrawable); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private Drawable createFillDrawable() { StateListDrawable drawable = new StateListDrawable(); drawable.addState(new int[]{android.R.attr.state_pressed}, createRectDrawable(mColorPressed)); drawable.addState(new int[]{}, createRectDrawable(mColorNormal)); if (Util.hasLollipop()) { RippleDrawable ripple = new RippleDrawable(new ColorStateList(new int[][]{{}}, new int[]{mColorRipple}), drawable, null); setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setOval(0, 0, view.getWidth(), view.getHeight()); } }); setClipToOutline(true); mBackgroundDrawable = ripple; return ripple; } mBackgroundDrawable = drawable; return drawable; } private Drawable createRectDrawable(int color) { RoundRectShape shape = new RoundRectShape( new float[]{ mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius }, null, null); ShapeDrawable shapeDrawable = new ShapeDrawable(shape); shapeDrawable.getPaint().setColor(color); return shapeDrawable; } private void setShadow(FloatingActionButton fab) { mShadowColor = fab.getShadowColor(); mShadowRadius = fab.getShadowRadius(); mShadowXOffset = fab.getShadowXOffset(); mShadowYOffset = fab.getShadowYOffset(); mShowShadow = fab.hasShadow(); } @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void setBackgroundCompat(Drawable drawable) { if (Util.hasJellyBean()) { setBackground(drawable); } else { setBackgroundDrawable(drawable); } } private void playShowAnimation() { if (mShowAnimation != null) { mHideAnimation.cancel(); startAnimation(mShowAnimation); } } private void playHideAnimation() { if (mHideAnimation != null) { mShowAnimation.cancel(); startAnimation(mHideAnimation); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) void onActionDown() { if (mUsingStyle) { mBackgroundDrawable = getBackground(); } if (mBackgroundDrawable instanceof StateListDrawable) { StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable; drawable.setState(new int[]{android.R.attr.state_pressed}); } else if (Util.hasLollipop() && mBackgroundDrawable instanceof RippleDrawable) { RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable; ripple.setState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}); ripple.setHotspot(getMeasuredWidth() / 2, getMeasuredHeight() / 2); ripple.setVisible(true, true); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) void onActionUp() { if (mUsingStyle) { mBackgroundDrawable = getBackground(); } if (mBackgroundDrawable instanceof StateListDrawable) { StateListDrawable drawable = (StateListDrawable) mBackgroundDrawable; drawable.setState(new int[]{}); } else if (Util.hasLollipop() && mBackgroundDrawable instanceof RippleDrawable) { RippleDrawable ripple = (RippleDrawable) mBackgroundDrawable; ripple.setState(new int[]{}); ripple.setHotspot(getMeasuredWidth() / 2, getMeasuredHeight() / 2); ripple.setVisible(true, true); } } void setFab(FloatingActionButton fab) { mFab = fab; setShadow(fab); } void setShowShadow(boolean show) { mShowShadow = show; } void setCornerRadius(int cornerRadius) { mCornerRadius = cornerRadius; } void setColors(int colorNormal, int colorPressed, int colorRipple) { mColorNormal = colorNormal; mColorPressed = colorPressed; mColorRipple = colorRipple; } void show(boolean animate) { if (animate) { playShowAnimation(); } setVisibility(VISIBLE); } void hide(boolean animate) { if (animate) { playHideAnimation(); } setVisibility(INVISIBLE); } void setShowAnimation(Animation showAnimation) { mShowAnimation = showAnimation; } void setHideAnimation(Animation hideAnimation) { mHideAnimation = hideAnimation; } void setUsingStyle(boolean usingStyle) { mUsingStyle = usingStyle; } void setHandleVisibilityChanges(boolean handle) { mHandleVisibilityChanges = handle; } boolean isHandleVisibilityChanges() { return mHandleVisibilityChanges; } @Override public boolean onTouchEvent(MotionEvent event) { if (mFab == null || mFab.getOnClickListener() == null || !mFab.isEnabled()) { return super.onTouchEvent(event); } int action = event.getAction(); switch (action) { case MotionEvent.ACTION_UP: onActionUp(); mFab.onActionUp(); break; } mGestureDetector.onTouchEvent(event); return super.onTouchEvent(event); } GestureDetector mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { onActionDown(); if (mFab != null) { mFab.onActionDown(); } return super.onDown(e); } @Override public boolean onSingleTapUp(MotionEvent e) { onActionUp(); if (mFab != null) { mFab.onActionUp(); } return super.onSingleTapUp(e); } }); private class Shadow extends Drawable { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mErase = new Paint(Paint.ANTI_ALIAS_FLAG); private Shadow() { this.init(); } private void init() { setLayerType(LAYER_TYPE_SOFTWARE, null); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(mColorNormal); mErase.setXfermode(PORTER_DUFF_CLEAR); if (!isInEditMode()) { mPaint.setShadowLayer(mShadowRadius, mShadowXOffset, mShadowYOffset, mShadowColor); } } @Override public void draw(Canvas canvas) { RectF shadowRect = new RectF( mShadowRadius + Math.abs(mShadowXOffset), mShadowRadius + Math.abs(mShadowYOffset), mRawWidth, mRawHeight ); canvas.drawRoundRect(shadowRect, mCornerRadius, mCornerRadius, mPaint); canvas.drawRoundRect(shadowRect, mCornerRadius, mCornerRadius, mErase); } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter cf) { } @Override public int getOpacity() { return 0; } } }