code: package io.virtualapp.widgets;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.annotation.SuppressLint;
  import android.app.Activity;
  import android.view.View;
  import android.view.ViewAnimationUtils;
  import android.view.ViewGroup;
  import android.widget.ImageView;
  
  
  public class CircularAnim {
  
      public static final long PERFECT_MILLS = 618;
      public static final int MINI_RADIUS = 0;
  
      private static Long sPerfectMills;
      private static Long sFullActivityPerfectMills;
      private static Integer sColorOrImageRes;
  
      private static long getPerfectMills() {
          if (sPerfectMills != null)
              return sPerfectMills;
          else
              return PERFECT_MILLS;
      }
  
      private static long getFullActivityMills() {
          if (sFullActivityPerfectMills != null)
              return sFullActivityPerfectMills;
          else
              return PERFECT_MILLS;
      }
  
      private static int getColorOrImageRes() {
          if (sColorOrImageRes != null)
              return sColorOrImageRes;
          else
              return android.R.color.white;
      }
  
      public static VisibleBuilder show(View animView) {
          return new VisibleBuilder(animView, true);
      }
  
      public static VisibleBuilder hide(View animView) {
          return new VisibleBuilder(animView, false);
      }
  
      public static FullActivityBuilder fullActivity(Activity activity, View triggerView) {
          return new FullActivityBuilder(activity, triggerView);
      }
  
      public static void init(long perfectMills, long fullActivityPerfectMills, int colorOrImageRes) {
          sPerfectMills = perfectMills;
          sFullActivityPerfectMills = fullActivityPerfectMills;
          sColorOrImageRes = colorOrImageRes;
      }
  
      public interface OnAnimationEndListener {
          void onAnimationEnd();
      }
  
      @SuppressLint("NewApi")
      public static class VisibleBuilder {
  
          private View mAnimView, mTriggerView;
  
          private Float mStartRadius, mEndRadius;
  
          private long mDurationMills = getPerfectMills();
  
          private boolean isShow;
  
          private OnAnimationEndListener mOnAnimationEndListener;
  
          public VisibleBuilder(View animView, boolean isShow) {
              mAnimView = animView;
              this.isShow = isShow;
  
              if (isShow) {
                  mStartRadius = MINI_RADIUS + 0F;
              } else {
                  mEndRadius = MINI_RADIUS + 0F;
              }
          }
  
          public VisibleBuilder triggerView(View triggerView) {
              mTriggerView = triggerView;
              return this;
          }
  
          public VisibleBuilder startRadius(float startRadius) {
              mStartRadius = startRadius;
              return this;
          }
  
          public VisibleBuilder endRadius(float endRadius) {
              mEndRadius = endRadius;
              return this;
          }
  
          public VisibleBuilder duration(long durationMills) {
              mDurationMills = durationMills;
              return this;
          }
  
          @Deprecated //You can use method - go(OnAnimationEndListener onAnimationEndListener).
          public VisibleBuilder onAnimationEndListener(OnAnimationEndListener onAnimationEndListener) {
              mOnAnimationEndListener = onAnimationEndListener;
              return this;
          }
  
          public void go() {
              go(null);
          }
  
          public void go(OnAnimationEndListener onAnimationEndListener) {
              mOnAnimationEndListener = onAnimationEndListener;
  
              // 版本判断
              if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
                  doOnEnd();
                  return;
              }
  
              int rippleCX, rippleCY, maxRadius;
              if (mTriggerView != null) {
                  int[] tvLocation = new int[2];
                  mTriggerView.getLocationInWindow(tvLocation);
                  final int tvCX = tvLocation[0] + mTriggerView.getWidth() / 2;
                  final int tvCY = tvLocation[1] + mTriggerView.getHeight() / 2;
  
                  int[] avLocation = new int[2];
                  mAnimView.getLocationInWindow(avLocation);
                  final int avLX = avLocation[0];
                  final int avTY = avLocation[1];
  
                  int triggerX = Math.max(avLX, tvCX);
                  triggerX = Math.min(triggerX, avLX + mAnimView.getWidth());
  
                  int triggerY = Math.max(avTY, tvCY);
                  triggerY = Math.min(triggerY, avTY + mAnimView.getHeight());
  
                  // 以上全为绝对坐标
  
                  int avW = mAnimView.getWidth();
                  int avH = mAnimView.getHeight();
  
                  rippleCX = triggerX - avLX;
                  rippleCY = triggerY - avTY;
  
                  // 计算水波中心点至 @mAnimView 边界的最大距离
                  int maxW = Math.max(rippleCX, avW - rippleCX);
                  int maxH = Math.max(rippleCY, avH - rippleCY);
                  maxRadius = (int) Math.sqrt(maxW * maxW + maxH * maxH) + 1;
              } else {
                  rippleCX = (mAnimView.getLeft() + mAnimView.getRight()) / 2;
                  rippleCY = (mAnimView.getTop() + mAnimView.getBottom()) / 2;
  
                  int w = mAnimView.getWidth();
                  int h = mAnimView.getHeight();
  
                  // 勾股定理 & 进一法
                  maxRadius = (int) Math.sqrt(w * w + h * h) + 1;
              }
  
              if (isShow && mEndRadius == null)
                  mEndRadius = maxRadius + 0F;
              else if (!isShow && mStartRadius == null)
                  mStartRadius = maxRadius + 0F;
  
              try {
                  Animator anim = ViewAnimationUtils.createCircularReveal(
                          mAnimView, rippleCX, rippleCY, mStartRadius, mEndRadius);
  
  
                  mAnimView.setVisibility(View.VISIBLE);
                  anim.setDuration(mDurationMills);
  
                  anim.addListener(new AnimatorListenerAdapter() {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                          super.onAnimationEnd(animation);
                          doOnEnd();
                      }
                  });
  
                  anim.start();
              } catch (Exception e) {
                  e.printStackTrace();
                  doOnEnd();
              }
          }
  
          private void doOnEnd() {
              if (isShow)
                  mAnimView.setVisibility(View.VISIBLE);
              else
                  mAnimView.setVisibility(View.INVISIBLE);
  
              if (mOnAnimationEndListener != null)
                  mOnAnimationEndListener.onAnimationEnd();
          }
  
      }
  
      @SuppressLint("NewApi")
      public static class FullActivityBuilder {
          private Activity mActivity;
          private View mTriggerView;
          private float mStartRadius = MINI_RADIUS;
          private int mColorOrImageRes = getColorOrImageRes();
          private Long mDurationMills;
          private OnAnimationEndListener mOnAnimationEndListener;
          private int mEnterAnim = android.R.anim.fade_in, mExitAnim = android.R.anim.fade_out;
  
          public FullActivityBuilder(Activity activity, View triggerView) {
              mActivity = activity;
              mTriggerView = triggerView;
          }
  
          public FullActivityBuilder startRadius(float startRadius) {
              mStartRadius = startRadius;
              return this;
          }
  
          public FullActivityBuilder colorOrImageRes(int colorOrImageRes) {
              mColorOrImageRes = colorOrImageRes;
              return this;
          }
  
          public FullActivityBuilder duration(long durationMills) {
              mDurationMills = durationMills;
              return this;
          }
  
          public FullActivityBuilder overridePendingTransition(int enterAnim, int exitAnim) {
              mEnterAnim = enterAnim;
              mExitAnim = exitAnim;
              return this;
          }
  
          public void go(OnAnimationEndListener onAnimationEndListener) {
              mOnAnimationEndListener = onAnimationEndListener;
  
              // 版本判断,小于5.0则无动画.
              if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
                  doOnEnd();
                  return;
              }
  
              int[] location = new int[2];
              mTriggerView.getLocationInWindow(location);
              final int cx = location[0] + mTriggerView.getWidth() / 2;
              final int cy = location[1] + mTriggerView.getHeight() / 2;
              final ImageView view = new ImageView(mActivity);
              view.setScaleType(ImageView.ScaleType.CENTER_CROP);
              view.setImageResource(mColorOrImageRes);
              final ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
              int w = decorView.getWidth();
              int h = decorView.getHeight();
              decorView.addView(view, w, h);
  
              int maxW = Math.max(cx, w - cx);
              int maxH = Math.max(cy, h - cy);
              final int finalRadius = (int) Math.sqrt(maxW * maxW + maxH * maxH) + 1;
  
              try {
                  Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, mStartRadius, finalRadius);
  
                  int maxRadius = (int) Math.sqrt(w * w + h * h) + 1;
                  if (mDurationMills == null) {
                      double rate = 1d * finalRadius / maxRadius;
                      mDurationMills = (long) (getFullActivityMills() * Math.sqrt(rate));
                  }
                  final long finalDuration = mDurationMills;
                  anim.setDuration((long) (finalDuration * 0.9));
                  anim.addListener(new AnimatorListenerAdapter() {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                          super.onAnimationEnd(animation);
  
                          doOnEnd();
  
                          mActivity.overridePendingTransition(mEnterAnim, mExitAnim);
  
                          mTriggerView.postDelayed(new Runnable() {
                              @Override
                              public void run() {
                                  if (mActivity.isFinishing()) return;
                                  try {
                                      Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy,
                                              finalRadius, mStartRadius);
                                      anim.setDuration(finalDuration);
                                      anim.addListener(new AnimatorListenerAdapter() {
                                          @Override
                                          public void onAnimationEnd(Animator animation) {
                                              super.onAnimationEnd(animation);
                                              try {
                                                  decorView.removeView(view);
                                              } catch (Exception e) {
                                                  e.printStackTrace();
                                              }
                                          }
                                      });
                                      anim.start();
                                  } catch (Exception e) {
                                      e.printStackTrace();
                                      try {
                                          decorView.removeView(view);
                                      } catch (Exception e1) {
                                          e1.printStackTrace();
                                      }
                                  }
                              }
                          }, 1000);
  
                      }
                  });
                  anim.start();
              } catch (Exception e) {
                  e.printStackTrace();
                  doOnEnd();
              }
          }
  
          private void doOnEnd() {
              mOnAnimationEndListener.onAnimationEnd();
          }
      }
  }
  