package com.wang.avi;
|
|
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 com.example.pickerviewlibrary.R;
|
import com.wang.avi.indicators.BallPulseIndicator;
|
|
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, Color.WHITE);
|
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 !");
|
} 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);
|
}
|
|
|
}
|