package com.github.jdsjlzx.progressindicator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
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 com.github.jdsjlzx.R;
import com.github.jdsjlzx.progressindicator.indicators.BallPulseIndicator;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallBeat;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallClipRotate;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallClipRotateMultiple;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallClipRotatePulse;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallGridBeat;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallGridPulse;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallPulse;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallPulseRise;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallPulseSync;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallRotate;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallScale;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallScaleMultiple;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallScaleRipple;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallScaleRippleMultiple;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallSpinFadeLoader;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallTrianglePath;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallZigZag;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.BallZigZagDeflect;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.CubeTransition;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.LineScale;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.LineScaleParty;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.LineScalePulseOut;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.LineScalePulseOutRapid;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.LineSpinFadeLoader;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.Pacman;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.SemiCircleSpin;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.SquareSpin;
import static com.github.jdsjlzx.recyclerview.ProgressStyle.TriangleSkewSpin;
public class AVLoadingIndicatorView extends View {
private static final String TAG = "AVLoadingIndicatorView";
private static final BallPulseIndicator DEFAULT_INDICATOR = new BallPulseIndicator();
private static final int MIN_SHOW_TIME = 500; // ms
private static final int MIN_DELAY = 500; // ms
private long mStartTime = -1;
private boolean mPostedHide = false;
private boolean mPostedShow = false;
private boolean mDismissed = false;
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);
}
}
};
int mMinWidth;
int mMaxWidth;
int mMinHeight;
int mMaxHeight;
private Indicator mIndicator;
private int mIndicatorColor;
private boolean mShouldStartAnimationDrawable;
public AVLoadingIndicatorView(Context context) {
super(context);
init(context, null, 0, 0);
}
public AVLoadingIndicatorView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0, R.style.AVLoadingIndicatorView);
}
public AVLoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr, R.style.AVLoadingIndicatorView);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public AVLoadingIndicatorView(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.AVLoadingIndicatorView, defStyleAttr, defStyleRes);
mMinWidth = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_minWidth, mMinWidth);
mMaxWidth = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_maxWidth, mMaxWidth);
mMinHeight = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_minHeight, mMinHeight);
mMaxHeight = a.getDimensionPixelSize(R.styleable.AVLoadingIndicatorView_maxHeight, mMaxHeight);
String indicatorName = a.getString(R.styleable.AVLoadingIndicatorView_indicatorName);
mIndicatorColor = a.getColor(R.styleable.AVLoadingIndicatorView_indicatorColor, getResources().getColor(R.color.colorAccent));
setIndicator(indicatorName);
if (mIndicator == null) {
setIndicator(DEFAULT_INDICATOR);
}
a.recycle();
}
public Indicator getIndicator() {
return mIndicator;
}
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);
}
/**
* 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(".indicators")
.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 !" + indicatorName);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
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);
}
public void setIndicatorId(int indicatorId) {
String indicatorName = null;
switch (indicatorId) {
case BallPulse:
indicatorName = "BallPulseIndicator";
break;
case BallGridPulse:
indicatorName = "BallGridPulseIndicator";
break;
case BallClipRotate:
indicatorName = "BallClipRotateIndicator";
break;
case BallClipRotatePulse:
indicatorName = "BallClipRotatePulseIndicator";
break;
case SquareSpin:
indicatorName = "SquareSpinIndicator";
break;
case BallClipRotateMultiple:
indicatorName = "BallClipRotateMultipleIndicator";
break;
case BallPulseRise:
indicatorName = "BallPulseRiseIndicator";
break;
case BallRotate:
indicatorName = "BallRotateIndicator";
break;
case CubeTransition:
indicatorName = "CubeTransitionIndicator";
break;
case BallZigZag:
indicatorName = "BallZigZagIndicator";
break;
case BallZigZagDeflect:
indicatorName = "BallZigZagDeflectIndicator";
break;
case BallTrianglePath:
indicatorName = "BallTrianglePathIndicator";
break;
case BallScale:
indicatorName = "BallScaleIndicator";
break;
case LineScale:
indicatorName = "LineScaleIndicator";
break;
case LineScaleParty:
indicatorName = "LineScalePartyIndicator";
break;
case BallScaleMultiple:
indicatorName = "BallScaleMultipleIndicator";
break;
case BallPulseSync:
indicatorName = "BallPulseSyncIndicator";
break;
case BallBeat:
indicatorName = "BallBeatIndicator";
break;
case LineScalePulseOut:
indicatorName = "LineScalePulseOutIndicator";
break;
case LineScalePulseOutRapid:
indicatorName = "LineScalePulseOutRapidIndicator";
break;
case BallScaleRipple:
indicatorName = "BallScaleRippleIndicator";
break;
case BallScaleRippleMultiple:
indicatorName = "BallScaleRippleMultipleIndicator";
break;
case BallSpinFadeLoader:
indicatorName = "BallSpinFadeLoaderIndicator";
break;
case LineSpinFadeLoader:
indicatorName = "LineSpinFadeLoaderIndicator";
break;
case TriangleSkewSpin:
indicatorName = "TriangleSkewSpinIndicator";
break;
case Pacman:
indicatorName = "PacmanIndicator";
break;
case BallGridBeat:
indicatorName = "BallGridBeatIndicator";
break;
case SemiCircleSpin:
indicatorName = "SemiCircleSpinIndicator";
break;
default:
indicatorName = "BallPulseIndicator";
break;
}
setIndicator(indicatorName);
}
}