code: package io.virtualapp.widgets;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.ValueAnimator;
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.PorterDuff;
  import android.graphics.PorterDuffXfermode;
  import android.graphics.RectF;
  import android.support.v7.widget.AppCompatImageView;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.animation.DecelerateInterpolator;
  
  import io.virtualapp.R;
  
  import static android.graphics.Canvas.ALL_SAVE_FLAG;
  
  public class LauncherIconView extends AppCompatImageView implements ShimmerViewBase {
      private static final int SMOOTH_ANIM_THRESHOLD = 5;
  
      private static final String TAG = "LauncherIconView";
  
      private ShimmerViewHelper mShimmerViewHelper;
      private Shimmer mShimmer;
  
      private float mProgress;
      private int mHeight;
      private int mWidth;
      private int mStrokeWidth;
      private float mRadius;
      private float mInterDelta;
      private int mMaskColor;
  
      private float mMaxMaskRadius;
      private float mMaskAnimDelta;
      private boolean mIsSquare;
      private boolean mMaskAnimRunning;
  
      private long mMediumAnimTime;
  
      private Paint mShimmerPaint;
      private Paint mPaint;
      private RectF mProgressOval;
      private ValueAnimator mInterAnim;
      private ValueAnimator mProgressAnimator;
  
      public LauncherIconView(Context context) {
          super(context);
          init(context, null);
      }
  
      public LauncherIconView(Context context, AttributeSet attrs) {
          super(context, attrs);
          init(context, attrs);
      }
  
      public LauncherIconView(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          init(context, attrs);
      }
  
      private void init(Context context, AttributeSet attrs) {
          mMediumAnimTime = getContext().getResources().getInteger(android.R.integer.config_mediumAnimTime);
  
          final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressImageView);
          try {
              this.mProgress = a.getInteger(R.styleable.ProgressImageView_pi_progress, 0);
              this.mStrokeWidth = a.getDimensionPixelOffset(R.styleable.ProgressImageView_pi_stroke, 8);
              this.mRadius = a.getDimensionPixelOffset(R.styleable.ProgressImageView_pi_radius, 0);
              this.mIsSquare = a.getBoolean(R.styleable.ProgressImageView_pi_force_square, false);
              this.mMaskColor = a.getColor(R.styleable.ProgressImageView_pi_mask_color, Color.argb(180, 0, 0, 0));
  
              this.mPaint = new Paint();
              mPaint.setColor(mMaskColor);
              mPaint.setAntiAlias(true);
  
              this.mShimmerPaint = new Paint();
              mShimmerPaint.setColor(Color.WHITE);
          } finally {
              a.recycle();
          }
          mShimmerViewHelper = new ShimmerViewHelper(this, mShimmerPaint, attrs);
      }
  
      private void initParams() {
          if (mWidth == 0)
              mWidth = getWidth();
  
          if (mHeight == 0)
              mHeight = getHeight();
  
          if (mWidth != 0 && mHeight != 0) {
              if (mRadius == 0)
                  mRadius = Math.min(mWidth, mHeight) / 4f;
  
              if (mMaxMaskRadius == 0)
                  mMaxMaskRadius = (float) (0.5f * Math.sqrt(mWidth * mWidth + mHeight * mHeight));
  
              if (mProgressOval == null)
                  mProgressOval = new RectF(
                          mWidth / 2f - mRadius + mStrokeWidth,
                          mHeight / 2f - mRadius + mStrokeWidth,
                          mWidth / 2f + mRadius - mStrokeWidth,
                          mHeight / 2f + mRadius - mStrokeWidth);
          }
      }
  
      @Override
      protected void onDraw(Canvas canvas) {
          if (mShimmerViewHelper != null) {
              mShimmerViewHelper.onDraw();
          }
          super.onDraw(canvas);
          int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, ALL_SAVE_FLAG);
  
          initParams();
  
          if (mProgress < 100) {
              drawMask(canvas);
  
              if (mProgress == 0)
                  updateInterAnim(canvas);
              else
                  drawProgress(canvas);
          }
  
          if (mMaskAnimRunning)
              updateMaskAnim(canvas);
  
          canvas.restoreToCount(sc);
      }
  
      private void drawMask(Canvas canvas) {
          canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
      }
  
      private void drawProgress(Canvas canvas) {
          mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
          canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius, mPaint);
          mPaint.setXfermode(null);
  
          //start angle : -90 ~ 270;sweep Angle : 360 ~ 0;
          canvas.drawArc(mProgressOval, -90 + mProgress * 3.6f, 360 - mProgress * 3.6f, true, mPaint);
      }
  
      private void updateInterAnim(Canvas canvas) {
  //        if (!mInterAnimRunning) mInterDelta = 0.f;
  
          //outer circle
          mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
          canvas.drawCircle(mWidth / 2.f, mHeight / 2.f, mRadius, mPaint);
          mPaint.setXfermode(null);
  
          //inner circle
          canvas.drawCircle(mWidth / 2.f, mHeight / 2.f, mRadius - mInterDelta, mPaint);
      }
  
      private void updateMaskAnim(Canvas canvas) {
          canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
  
          mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
          canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius + mMaskAnimDelta, mPaint);//mRatio : 0 ~ mRatio * 1.5
          mPaint.setXfermode(null);
      }
  
      private void startInterAnim(final int progress) {
          if (mInterAnim != null)
              mInterAnim.cancel();
  
          mInterAnim = ValueAnimator.ofFloat(0.f, mStrokeWidth);
          mInterAnim.setInterpolator(new DecelerateInterpolator());
          mInterAnim.setDuration(getContext().getResources().getInteger(android.R.integer.config_shortAnimTime));
          mInterAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
              @Override
              public void onAnimationUpdate(ValueAnimator animation) {
                  mInterDelta = (float) animation.getAnimatedValue();
                  invalidate();
              }
          });
          mInterAnim.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationStart(Animator animation) {
                  super.onAnimationStart(animation);
  //                mInterAnimRunning = true;
              }
  
              @Override
              public void onAnimationCancel(Animator animation) {
                  super.onAnimationCancel(animation);
  //                mInterAnimRunning = false;
              }
  
              @Override
              public void onAnimationEnd(Animator animation) {
                  super.onAnimationEnd(animation);
  //                mInterAnimRunning = false;
  
                  if (progress > 0)
                      startProgressAnim(0, progress);
              }
          });
          mInterAnim.start();
      }
  
      private void startProgressAnim(float from, float to) {
          if (mProgressAnimator != null)
              mProgressAnimator.cancel();
  
          final boolean isReverse = from > to;
  
          mProgressAnimator = ValueAnimator.ofFloat(from, to);
          mProgressAnimator.setInterpolator(new DecelerateInterpolator());
          mProgressAnimator.setDuration(mMediumAnimTime);
          mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
              @Override
              public void onAnimationUpdate(ValueAnimator animation) {
                  mProgress = (float) animation.getAnimatedValue();
  
                  if (0 < mProgress && mProgress < 100)
                      invalidate();
                  else if (mProgress == 100 && !isReverse)
                      startMaskAnim();
              }
          });
          mProgressAnimator.start();
      }
  
      private void startMaskAnim() {
          if (mProgressAnimator != null)
              mProgressAnimator.cancel();
  
          ValueAnimator animator = ValueAnimator.ofFloat(0.f, mMaxMaskRadius);
          animator.setInterpolator(new DecelerateInterpolator());
          animator.setDuration(mMediumAnimTime);
          animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
              @Override
              public void onAnimationUpdate(ValueAnimator animation) {
                  mMaskAnimRunning = true;
                  mMaskAnimDelta = (float) animation.getAnimatedValue();
                  invalidate();
              }
          });
          animator.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationStart(Animator animation) {
                  super.onAnimationStart(animation);
                  mMaskAnimRunning = true;
              }
  
              @Override
              public void onAnimationCancel(Animator animation) {
                  super.onAnimationCancel(animation);
                  mMaskAnimRunning = false;
              }
  
              @Override
              public void onAnimationEnd(Animator animation) {
                  super.onAnimationEnd(animation);
                  mMaskAnimRunning = false;
              }
          });
          animator.start();
      }
  
      /**
       * get the stroke width.
       *
       * @return the stroke width in pixel.
       */
      public int getStrokeWidth() {
          return mStrokeWidth;
      }
  
      /**
       * set the stroke width.default is 8dp.
       *
       * @param strokeWidth stroke width in pixel
       */
      public void setStrokeWidth(int strokeWidth) {
          this.mStrokeWidth = strokeWidth;
          this.mProgressOval = null;
          invalidate();
      }
  
      /**
       * get the radius of inner progress circle.
       *
       * @return the inner circle radius in pixel.
       */
      public float getRadius() {
          return mRadius;
      }
  
      /**
       * set the radius of the inner progress circle.
       *
       * @param radius radius in pixel
       */
      public void setRadius(float radius) {
          this.mRadius = radius;
          this.mProgressOval = null;
          invalidate();
      }
  
      /**
       * get the color for mask .
       *
       * @return the mask color
       */
      public int getMaskColor() {
          return mMaskColor;
      }
  
      /**
       * set the color for mask. Argb will looks better. Default is Color.argb(180,0,0,0)
       *
       * @param maskColor the color value.
       */
      public void setMaskColor(int maskColor) {
          mMaskColor = maskColor;
          mPaint.setColor(mMaskColor);
          invalidate();
      }
  
      /**
       * get current progress.
       *
       * @return current progress value.
       */
      public int getProgress() {
          return (int) mProgress;
      }
  
      /**
       * @param progress the progress ,range [0,100]
       */
      public void setProgress(int progress) {
          setProgress(progress, true);
      }
  
      /**
       * @param progress the progress in [0,100]
       * @param animate  true to enable smooth animation when progress changed more than 5.
       */
      public void setProgress(int progress, boolean animate) {
          progress = Math.min(Math.max(progress, 0), 100);
  
          Log.d(TAG, "setProgress: p:" + progress + ",mp:" + mProgress);
  
          if (Math.abs(progress - mProgress) > SMOOTH_ANIM_THRESHOLD && animate) {
              if (mProgress == 0) {
                  startInterAnim(progress);
              } else {
                  startProgressAnim(mProgress, progress);
              }
          } else if (progress == 100 && animate) {
              mProgress = 100;
              startMaskAnim();
          } else {
              mProgress = progress;
  
              if (mProgress == 0.f)
                  mInterDelta = 0.f;
  
              invalidate();
          }
      }
  
      @Override
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
          if (mIsSquare) {
              int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
              int size = measuredWidth == 0 ? MeasureSpec.getSize(heightMeasureSpec) : measuredWidth;
              setMeasuredDimension(size, size);
          }
      }
  
  
      @Override
      public float getGradientX() {
          return mShimmerViewHelper.getGradientX();
      }
  
      @Override
      public void setGradientX(float gradientX) {
          mShimmerViewHelper.setGradientX(gradientX);
      }
  
      @Override
      public boolean isShimmering() {
          return mShimmerViewHelper.isShimmering();
      }
  
      @Override
      public void setShimmering(boolean isShimmering) {
          mShimmerViewHelper.setShimmering(isShimmering);
      }
  
      @Override
      public boolean isSetUp() {
          return mShimmerViewHelper.isSetUp();
      }
  
      @Override
      public void setAnimationSetupCallback(ShimmerViewHelper.AnimationSetupCallback callback) {
          mShimmerViewHelper.setAnimationSetupCallback(callback);
      }
  
      @Override
      public int getPrimaryColor() {
          return mShimmerViewHelper.getPrimaryColor();
      }
  
      @Override
      public void setPrimaryColor(int primaryColor) {
          mShimmerViewHelper.setPrimaryColor(primaryColor);
      }
  
      @Override
      public int getReflectionColor() {
          return mShimmerViewHelper.getReflectionColor();
      }
  
      @Override
      public void setReflectionColor(int reflectionColor) {
          mShimmerViewHelper.setReflectionColor(reflectionColor);
      }
  
      @Override
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
          super.onSizeChanged(w, h, oldw, oldh);
          if (mShimmerViewHelper != null) {
              mShimmerViewHelper.onSizeChanged();
          }
      }
  
      public void stopShimmer() {
          if (mShimmer != null && mShimmer.isAnimating()) {
              mShimmer.cancel();
              mShimmer = null;
          }
      }
  
      public void startShimmer() {
          stopShimmer();
          mShimmer = new Shimmer();
          mShimmer.setRepeatCount(1)
                  .setStartDelay(800L)
                  .setDirection(Shimmer.ANIMATION_DIRECTION_LTR)
                  .start(this);
      }
  }