code: package io.virtualapp.widgets;
  
  import android.annotation.TargetApi;
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Rect;
  import android.graphics.drawable.Animatable;
  import android.graphics.drawable.Drawable;
  import android.os.Build;
  import android.text.TextUtils;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.View;
  import android.view.animation.AnimationUtils;
  
  import io.virtualapp.R;
  
  public class LoadingIndicatorView extends View {
  
      private static final String TAG = "LoadingIndicatorView";
  
      private static final Indicator DEFAULT_INDICATOR = new BallGridBeatIndicator();
  
      private static final int MIN_SHOW_TIME = 500; // ms
      private static final int MIN_DELAY = 500; // ms
      int mMinWidth;
      int mMaxWidth;
      int mMinHeight;
      int mMaxHeight;
      private long mStartTime = -1;
      private boolean mPostedHide = false;
      private boolean mPostedShow = false;
      private boolean mDismissed = false;
      private Indicator mIndicator;
      private int mIndicatorColor;
      private boolean mShouldStartAnimationDrawable;
      private final Runnable mDelayedHide = new Runnable() {
  
          @Override
          public void run() {
              mPostedHide = false;
              mStartTime = -1;
              setVisibility(View.GONE);
          }
      };
      private final Runnable mDelayedShow = new Runnable() {
  
          @Override
          public void run() {
              mPostedShow = false;
              if (!mDismissed) {
                  mStartTime = System.currentTimeMillis();
                  setVisibility(View.VISIBLE);
              }
          }
      };
  
      public LoadingIndicatorView(Context context) {
          super(context);
          init(context, null, 0, 0);
      }
  
      public LoadingIndicatorView(Context context, AttributeSet attrs) {
          super(context, attrs);
          init(context, attrs, 0, R.style.AVLoadingIndicatorView);
      }
  
      public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          init(context, attrs, defStyleAttr, R.style.AVLoadingIndicatorView);
      }
  
      @TargetApi(Build.VERSION_CODES.LOLLIPOP)
      public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          init(context, attrs, defStyleAttr, R.style.AVLoadingIndicatorView);
      }
  
      private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          mMinWidth = 24;
          mMaxWidth = 48;
          mMinHeight = 24;
          mMaxHeight = 48;
  
          final TypedArray a = context.obtainStyledAttributes(
                  attrs, R.styleable.LoadingIndicatorView, defStyleAttr, defStyleRes);
  
          mMinWidth = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_minWidth, mMinWidth);
          mMaxWidth = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_maxWidth, mMaxWidth);
          mMinHeight = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_minHeight, mMinHeight);
          mMaxHeight = a.getDimensionPixelSize(R.styleable.LoadingIndicatorView_maxHeight, mMaxHeight);
          String indicatorName = a.getString(R.styleable.LoadingIndicatorView_indicatorName);
          mIndicatorColor = a.getColor(R.styleable.LoadingIndicatorView_indicatorColor, Color.WHITE);
          setIndicator(indicatorName);
          if (mIndicator == null) {
              setIndicator(DEFAULT_INDICATOR);
          }
          a.recycle();
      }
  
      public Indicator getIndicator() {
          return mIndicator;
      }
  
      /**
       * You should pay attention to pass this parameter with two way:
       * for example:
       * 1. Only class Name,like "SimpleIndicator".(This way would use default package name with
       * "com.wang.avi.indicators")
       * 2. Class name with full package,like "com.my.android.indicators.SimpleIndicator".
       *
       * @param indicatorName the class must be extend Indicator .
       */
      public void setIndicator(String indicatorName) {
          if (TextUtils.isEmpty(indicatorName)) {
              return;
          }
          StringBuilder drawableClassName = new StringBuilder();
          if (!indicatorName.contains(".")) {
              String defaultPackageName = getClass().getPackage().getName();
              drawableClassName.append(defaultPackageName)
                      .append(".");
          }
          drawableClassName.append(indicatorName);
          try {
              Class<?> drawableClass = Class.forName(drawableClassName.toString());
              Indicator indicator = (Indicator) drawableClass.newInstance();
              setIndicator(indicator);
          } catch (ClassNotFoundException e) {
              Log.e(TAG, "Didn't find your class , check the name again !");
          } catch (InstantiationException e) {
              e.printStackTrace();
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          }
      }
  
      public void setIndicator(Indicator d) {
          if (mIndicator != d) {
              if (mIndicator != null) {
                  mIndicator.setCallback(null);
                  unscheduleDrawable(mIndicator);
              }
  
              mIndicator = d;
              //need to set indicator color again if you didn't specified when you update the indicator .
              setIndicatorColor(mIndicatorColor);
              if (d != null) {
                  d.setCallback(this);
              }
              postInvalidate();
          }
      }
  
      /**
       * setIndicatorColor(0xFF00FF00)
       * or
       * setIndicatorColor(Color.BLUE)
       * or
       * setIndicatorColor(Color.parseColor("#FF4081"))
       * or
       * setIndicatorColor(0xFF00FF00)
       * or
       * setIndicatorColor(getResources().getColor(android.R.color.black))
       *
       * @param color
       */
      public void setIndicatorColor(int color) {
          this.mIndicatorColor = color;
          mIndicator.setColor(color);
      }
  
      public void smoothToShow() {
          startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_in));
          setVisibility(VISIBLE);
      }
  
      public void smoothToHide() {
          startAnimation(AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_out));
          setVisibility(GONE);
      }
  
      public void hide() {
          mDismissed = true;
          removeCallbacks(mDelayedShow);
          long diff = System.currentTimeMillis() - mStartTime;
          if (diff >= MIN_SHOW_TIME || mStartTime == -1) {
              // The progress spinner has been shown long enough
              // OR was not shown yet. If it wasn't shown yet,
              // it will just never be shown.
              setVisibility(View.GONE);
          } else {
              // The progress spinner is shown, but not long enough,
              // so put a delayed message in to hide it when its been
              // shown long enough.
              if (!mPostedHide) {
                  postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
                  mPostedHide = true;
              }
          }
      }
  
      public void show() {
          // Reset the start time.
          mStartTime = -1;
          mDismissed = false;
          removeCallbacks(mDelayedHide);
          if (!mPostedShow) {
              postDelayed(mDelayedShow, MIN_DELAY);
              mPostedShow = true;
          }
      }
  
      @Override
      protected boolean verifyDrawable(Drawable who) {
          return who == mIndicator
                  || super.verifyDrawable(who);
      }
  
      void startAnimation() {
          if (getVisibility() != VISIBLE) {
              return;
          }
  
          if (mIndicator instanceof Animatable) {
              mShouldStartAnimationDrawable = true;
          }
          postInvalidate();
      }
  
      void stopAnimation() {
          if (mIndicator instanceof Animatable) {
              mIndicator.stop();
              mShouldStartAnimationDrawable = false;
          }
          postInvalidate();
      }
  
      @Override
      public void setVisibility(int v) {
          if (getVisibility() != v) {
              super.setVisibility(v);
              if (v == GONE || v == INVISIBLE) {
                  stopAnimation();
              } else {
                  startAnimation();
              }
          }
      }
  
      @Override
      protected void onVisibilityChanged(View changedView, int visibility) {
          super.onVisibilityChanged(changedView, visibility);
          if (visibility == GONE || visibility == INVISIBLE) {
              stopAnimation();
          } else {
              startAnimation();
          }
      }
  
      @Override
      public void invalidateDrawable(Drawable dr) {
          if (verifyDrawable(dr)) {
              final Rect dirty = dr.getBounds();
              final int scrollX = getScrollX() + getPaddingLeft();
              final int scrollY = getScrollY() + getPaddingTop();
  
              invalidate(dirty.left + scrollX, dirty.top + scrollY,
                      dirty.right + scrollX, dirty.bottom + scrollY);
          } else {
              super.invalidateDrawable(dr);
          }
      }
  
      @Override
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
          updateDrawableBounds(w, h);
      }
  
      private void updateDrawableBounds(int w, int h) {
          // onDraw will translate the canvas so we draw starting at 0,0.
          // Subtract out padding for the purposes of the calculations below.
          w -= getPaddingRight() + getPaddingLeft();
          h -= getPaddingTop() + getPaddingBottom();
  
          int right = w;
          int bottom = h;
          int top = 0;
          int left = 0;
  
          if (mIndicator != null) {
              // Maintain aspect ratio. Certain kinds of animated drawables
              // get very confused otherwise.
              final int intrinsicWidth = mIndicator.getIntrinsicWidth();
              final int intrinsicHeight = mIndicator.getIntrinsicHeight();
              final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
              final float boundAspect = (float) w / h;
              if (intrinsicAspect != boundAspect) {
                  if (boundAspect > intrinsicAspect) {
                      // New width is larger. Make it smaller to match height.
                      final int width = (int) (h * intrinsicAspect);
                      left = (w - width) / 2;
                      right = left + width;
                  } else {
                      // New height is larger. Make it smaller to match width.
                      final int height = (int) (w * (1 / intrinsicAspect));
                      top = (h - height) / 2;
                      bottom = top + height;
                  }
              }
              mIndicator.setBounds(left, top, right, bottom);
          }
      }
  
      @Override
      protected synchronized void onDraw(Canvas canvas) {
          super.onDraw(canvas);
          drawTrack(canvas);
      }
  
      void drawTrack(Canvas canvas) {
          final Drawable d = mIndicator;
          if (d != null) {
              // Translate canvas so a indeterminate circular progress bar with padding
              // rotates properly in its animation
              final int saveCount = canvas.save();
  
              canvas.translate(getPaddingLeft(), getPaddingTop());
  
              d.draw(canvas);
              canvas.restoreToCount(saveCount);
  
              if (mShouldStartAnimationDrawable && d instanceof Animatable) {
                  ((Animatable) d).start();
                  mShouldStartAnimationDrawable = false;
              }
          }
      }
  
      @Override
      protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          int dw = 0;
          int dh = 0;
  
          final Drawable d = mIndicator;
          if (d != null) {
              dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
              dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
          }
  
          updateDrawableState();
  
          dw += getPaddingLeft() + getPaddingRight();
          dh += getPaddingTop() + getPaddingBottom();
  
          final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
          final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
          setMeasuredDimension(measuredWidth, measuredHeight);
      }
  
      @Override
      protected void drawableStateChanged() {
          super.drawableStateChanged();
          updateDrawableState();
      }
  
      private void updateDrawableState() {
          final int[] state = getDrawableState();
          if (mIndicator != null && mIndicator.isStateful()) {
              mIndicator.setState(state);
          }
      }
  
      @TargetApi(Build.VERSION_CODES.LOLLIPOP)
      @Override
      public void drawableHotspotChanged(float x, float y) {
          super.drawableHotspotChanged(x, y);
  
          if (mIndicator != null) {
              mIndicator.setHotspot(x, y);
          }
      }
  
      @Override
      protected void onAttachedToWindow() {
          super.onAttachedToWindow();
          startAnimation();
          removeCallbacks();
      }
  
      @Override
      protected void onDetachedFromWindow() {
          stopAnimation();
          // This should come after stopAnimation(), otherwise an invalidate message remains in the
          // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
          super.onDetachedFromWindow();
          removeCallbacks();
      }
  
      private void removeCallbacks() {
          removeCallbacks(mDelayedHide);
          removeCallbacks(mDelayedShow);
      }
  
  
  }