code: package io.virtualapp.widgets;
  
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.Path;
  import android.graphics.RadialGradient;
  import android.graphics.Rect;
  import android.graphics.Shader;
  import android.os.Build;
  import android.support.v7.widget.AppCompatButton;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.MotionEvent;
  import android.view.animation.AccelerateDecelerateInterpolator;
  
  import com.nineoldandroids.animation.Animator;
  import com.nineoldandroids.animation.ObjectAnimator;
  import com.nineoldandroids.view.ViewHelper;
  
  import io.virtualapp.R;
  
  @SuppressLint("ClickableViewAccessibility")
  public class RippleButton extends AppCompatButton {
  
      private float mDownX;
      private float mDownY;
      private float mAlphaFactor;
      private float mDensity;
      private float mRadius;
      private float mMaxRadius;
  
      private int mRippleColor;
      private boolean mIsAnimating = false;
      private boolean mHover = true;
  
      private RadialGradient mRadialGradient;
      private Paint mPaint;
      private ObjectAnimator mRadiusAnimator;
      private boolean mAnimationIsCancel;
      private Rect mRect;
      private Path mPath = new Path();
  
      public RippleButton(Context context) {
          this(context, null);
      }
  
      public RippleButton(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public RippleButton(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
          init();
          TypedArray a = context.obtainStyledAttributes(attrs,
                  R.styleable.RippleButton);
          mRippleColor = a.getColor(R.styleable.RippleButton_rippleColor,
                  mRippleColor);
          mAlphaFactor = a.getFloat(R.styleable.RippleButton_alphaFactor,
                  mAlphaFactor);
          mHover = a.getBoolean(R.styleable.RippleButton_hover, mHover);
          a.recycle();
      }
  
      private int dp(int dp) {
          return (int) (dp * mDensity + 0.5f);
      }
  
      public void init() {
          mDensity = getContext().getResources().getDisplayMetrics().density;
  
          mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
          mPaint.setAlpha(100);
          setRippleColor(Color.BLACK, 0.2f);
  
      }
  
      public void setRippleColor(int rippleColor, float alphaFactor) {
          mRippleColor = rippleColor;
          mAlphaFactor = alphaFactor;
      }
  
      public void setHover(boolean enabled) {
          mHover = enabled;
      }
  
      @Override
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
          super.onSizeChanged(w, h, oldw, oldh);
          mMaxRadius = (float) Math.sqrt(w * w + h * h);
      }
  
      @Override
      public boolean onTouchEvent(final MotionEvent event) {
          Log.d("TouchEvent", String.valueOf(event.getActionMasked()));
          Log.d("mIsAnimating", String.valueOf(mIsAnimating));
          Log.d("mAnimationIsCancel", String.valueOf(mAnimationIsCancel));
          boolean superResult = super.onTouchEvent(event);
          if (event.getActionMasked() == MotionEvent.ACTION_DOWN
                  && this.isEnabled() && mHover) {
              mRect = new Rect(getLeft(), getTop(), getRight(), getBottom());
              mAnimationIsCancel = false;
              mDownX = event.getX();
              mDownY = event.getY();
  
              mRadiusAnimator = ObjectAnimator.ofFloat(this, "radius", 0, dp(50))
                      .setDuration(400);
              mRadiusAnimator
                      .setInterpolator(new AccelerateDecelerateInterpolator());
              mRadiusAnimator.addListener(new Animator.AnimatorListener() {
                  @Override
                  public void onAnimationStart(Animator animator) {
                      mIsAnimating = true;
                  }
  
                  @Override
                  public void onAnimationEnd(Animator animator) {
                      setRadius(0);
                      ViewHelper.setAlpha(RippleButton.this, 1);
                      mIsAnimating = false;
                  }
  
                  @Override
                  public void onAnimationCancel(Animator animator) {
  
                  }
  
                  @Override
                  public void onAnimationRepeat(Animator animator) {
  
                  }
              });
              mRadiusAnimator.start();
              if (!superResult) {
                  return true;
              }
          } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE
                  && this.isEnabled() && mHover) {
              mDownX = event.getX();
              mDownY = event.getY();
  
              // Cancel the ripple animation when moved outside
              if (mAnimationIsCancel = !mRect.contains(
                      getLeft() + (int) event.getX(),
                      getTop() + (int) event.getY())) {
                  setRadius(0);
              } else {
                  setRadius(dp(50));
              }
              if (!superResult) {
                  return true;
              }
          } else if (event.getActionMasked() == MotionEvent.ACTION_UP
                  && !mAnimationIsCancel && this.isEnabled()) {
              mDownX = event.getX();
              mDownY = event.getY();
  
              final float tempRadius = (float) Math.sqrt(mDownX * mDownX + mDownY
                      * mDownY);
              float targetRadius = Math.max(tempRadius, mMaxRadius);
  
              if (mIsAnimating) {
                  mRadiusAnimator.cancel();
              }
              mRadiusAnimator = ObjectAnimator.ofFloat(this, "radius", dp(50),
                      targetRadius);
              mRadiusAnimator.setDuration(500);
              mRadiusAnimator
                      .setInterpolator(new AccelerateDecelerateInterpolator());
              mRadiusAnimator.addListener(new Animator.AnimatorListener() {
                  @Override
                  public void onAnimationStart(Animator animator) {
                      mIsAnimating = true;
                  }
  
                  @Override
                  public void onAnimationEnd(Animator animator) {
                      setRadius(0);
                      ViewHelper.setAlpha(RippleButton.this, 1);
                      mIsAnimating = false;
                  }
  
                  @Override
                  public void onAnimationCancel(Animator animator) {
  
                  }
  
                  @Override
                  public void onAnimationRepeat(Animator animator) {
  
                  }
              });
              mRadiusAnimator.start();
              if (!superResult) {
                  return true;
              }
          }
          return superResult;
      }
  
      public int adjustAlpha(int color, float factor) {
          int alpha = Math.round(Color.alpha(color) * factor);
          int red = Color.red(color);
          int green = Color.green(color);
          int blue = Color.blue(color);
          return Color.argb(alpha, red, green, blue);
      }
  
      public void setRadius(final float radius) {
          mRadius = radius;
          if (mRadius > 0) {
              mRadialGradient = new RadialGradient(mDownX, mDownY, mRadius,
                      adjustAlpha(mRippleColor, mAlphaFactor), mRippleColor,
                      Shader.TileMode.MIRROR);
              mPaint.setShader(mRadialGradient);
          }
          invalidate();
      }
  
      @Override
      protected void onDraw(final Canvas canvas) {
          super.onDraw(canvas);
  
          if (isInEditMode()) {
              return;
          }
  
          canvas.save(Canvas.CLIP_SAVE_FLAG);
  
          mPath.reset();
          mPath.addCircle(mDownX, mDownY, mRadius, Path.Direction.CW);
  
          canvas.clipPath(mPath);
  
          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
              canvas.restore();
  
          canvas.drawCircle(mDownX, mDownY, mRadius, mPaint);
      }
  
  }