自定義View實現的28種效果的載入中樣式
阿新 • • 發佈:2019-01-15
GithHub地址:https://github.com/81813780/AVLoadingIndicatorView
該開源庫實現了28種載入中樣式,但是我們一般專案中為了統一風格,所有頁面的載入中效果都是一致的,所以我們一般也只會用到其中的一兩種效果,如果集成了該庫,但我們只用到了其中一種或者兩種效果,那麼就顯得有點太浪費了,因此我們可以通過分析原始碼實現,只新增我們需要的效果即可,因此也就有了本篇文章,手動整合也很簡單,此處作簡要記錄。
1,新增核心類:AVLoadingIndicatorView,繼承自View,程式碼如下:
public class AVLoadingIndicatorView extends View {
private static final String TAG="AVLoadingIndicatorView";
// 當xml中沒有指定樣式時,預設使用該樣式,xml中沒有指定顏色時,預設為白色(本類中110行設定的預設為白色)
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);
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() {