LoadingDrawable
LoadingDrawable是github上挺火的一個(gè)項(xiàng)目, 通過(guò)自定義Drawable來(lái)實(shí)現(xiàn)各式各樣的Loading動(dòng)畫. 現(xiàn)已經(jīng)有多種有意思的動(dòng)畫效果, 可以直接用在自己項(xiàng)目中, 或者仿照他的做法實(shí)現(xiàn)自己的loading動(dòng)畫.
LoadingDrawable使用
凡是好用的東西一般都使用非常簡(jiǎn)單, 當(dāng)然這個(gè)也不例外, 只要有一個(gè)ImageView, 在代碼中創(chuàng)建一個(gè)需要的LoadingDrawable, 再將drawable設(shè)給ImageView就可以了.
mIvMaterial= (ImageView) findViewById(R.id.material_view);
//使用自己需要的LoadingRenderer
mMaterialDrawable=newLoadingDrawable(newMaterialLoadingRenderer(this));
mIvMaterial.setImageDrawable(mMaterialDrawable);
LoadingDrawable分析
概述
LoadingDrawable通過(guò)自定義一個(gè)Drawable將不同的動(dòng)畫畫出來(lái), 其中的對(duì)于不同的動(dòng)畫對(duì)應(yīng)不同的LoadingRender
, 他們都是LoadingRender
的子類, 分別重寫了不同的計(jì)算和繪制的方法以實(shí)現(xiàn)不同的效果. 下面先看看他所涉及的類:
真的是很簡(jiǎn)捷清晰, 高亮是Drawable的子類,
render
包下是對(duì)不同動(dòng)畫的渲染器, 其中的LoadingRender
是基類, 實(shí)現(xiàn)了基本的邏輯和定義繪制計(jì)算接口. 上面的幾個(gè)包就是具體的動(dòng)畫實(shí)現(xiàn).
LoadingDrawable
直接來(lái)看LoadingDrawable的實(shí)現(xiàn):
//LoadingDrawable繼承自Drawable, 可以自定義不用交互的可見(jiàn)控件
//實(shí)現(xiàn)Animatable接口, 他就成為一個(gè)動(dòng)畫, 可以在合適的時(shí)機(jī)顯示或者停止
public class LoadingDrawable extends Drawable implements Animatable {
private LoadingRenderer mLoadingRender;
//定義一個(gè)Callback, 并將其傳遞給Render, 負(fù)責(zé)更新當(dāng)前視圖, 將其傳遞給Render可以避免Render去持有過(guò)多的引用
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(Drawable d) {
invalidateSelf();
}
@Override
public void scheduleDrawable(Drawable d, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable d, Runnable what) {
unscheduleSelf(what);
}
};
//構(gòu)造方法, 將Render保存在Drawable中, 方便后面將所有的計(jì)算繪制任務(wù)交給他
public LoadingDrawable(LoadingRenderer loadingRender) {
this.mLoadingRender = loadingRender;
this.mLoadingRender.setCallback(mCallback);
}
//直接將繪制的任務(wù)交給Render去做, 后面的好多方法也是類似的, 直接給Render去處理
@Override
public void draw(Canvas canvas) {
mLoadingRender.draw(canvas, getBounds());
}我是我
/*...省略部分代碼...*/
}
代碼量不大, 主要是將任務(wù)交給Render處理, 其中使用Callback的思想要學(xué)習(xí)一下.
LoadingRenderer
最核心的部分, 連接LoadingDrawable與各個(gè)具體動(dòng)畫, 規(guī)范各種動(dòng)畫接口的類, 就是LoadingRenderer
, 下面我們看看他都做了什么.
public abstract class LoadingRenderer {
public LoadingRenderer(Context context) {
//設(shè)置大小
setupDefaultParams(context);
//設(shè)置動(dòng)畫更新相關(guān)
setupAnimators();
}
//其中對(duì)不同動(dòng)畫的抽象都在這里定義, 在子類中實(shí)現(xiàn)這些方法以實(shí)現(xiàn)對(duì)應(yīng)動(dòng)畫
public abstract void draw(Canvas canvas, Rect bounds);
public abstract void computeRender(float renderProgress);
public abstract void setAlpha(int alpha);
public abstract void setColorFilter(ColorFilter cf);
public abstract void reset();
//這里的start其實(shí)就是start渲染動(dòng)畫
public void start() {
reset();
setDuration(mDuration);
mRenderAnimator.start();
}
public void stop() {
mRenderAnimator.cancel();
}
public boolean isRunning() {
return mRenderAnimator.isRunning();
}
public void setCallback(Drawable.Callback callback) {
this.mCallback = callback;
}
protected void invalidateSelf() {
mCallback.invalidateDrawable(null);
}
private void setupDefaultParams(Context context) {
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
final float screenDensity = metrics.density;
mWidth = DEFAULT_SIZE * screenDensity;
mHeight = DEFAULT_SIZE * screenDensity;
mStrokeWidth = DEFAULT_STROKE_WIDTH * screenDensity;
mCenterRadius = DEFAULT_CENTER_RADIUS * screenDensity;
mDuration = ANIMATION_DURATION;
}
private void setupAnimators() {
mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mRenderAnimator.setRepeatCount(Animation.INFINITE);
mRenderAnimator.setRepeatMode(Animation.RESTART);
//fuck you! the default interpolator is AccelerateDecelerateInterpolator
mRenderAnimator.setInterpolator(new LinearInterpolator());
mRenderAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//就是從這里把計(jì)算的任務(wù)與繪制的任務(wù)分開, 動(dòng)畫這部分只負(fù)責(zé)計(jì)算
//真正的畫出來(lái)是在draw里面
computeRender((float) animation.getAnimatedValue());
//通過(guò)CallBack通知Drable更新
invalidateSelf();
}
});
}
/*....省略部分代碼.....*/
public void setDuration(long duration) {
this.mDuration = duration;
mRenderAnimator.setDuration(mDuration);
}
}
LoadingRender對(duì)各種不同的動(dòng)畫進(jìn)行了抽象, 將動(dòng)畫的計(jì)算和繪制拆分出來(lái), 十分有利于后面不同動(dòng)畫的實(shí)現(xiàn), 另外還對(duì)一些默認(rèn)參數(shù)進(jìn)行設(shè)置. 真正的計(jì)算和繪制都在下面的子類中, 我們分析一個(gè)MaterialLoadingRenderer
.
MaterialLoadingRenderer
MaterialLoadingRenderer
是右上方的效果, 三種顏色交替過(guò)渡出現(xiàn), 圓先變大半圓再變小半圓, 然后整體還在轉(zhuǎn)動(dòng),,初分析感覺(jué)好難, 下面細(xì)細(xì)看其代碼, 不得不說(shuō)以前自己寫的動(dòng)畫都是什么玩意兒啊..下面看源碼
public void computeRender(float renderProgress) {
updateRingColor(renderProgress);
// Moving the start trim only occurs in the first 50% of a
// single ring animation
if (renderProgress <= START_TRIM_DURATION_OFFSET) {
//除定前半程比例, 這里相當(dāng)于把一個(gè)動(dòng)畫分成了兩個(gè)動(dòng)畫, 前半段是只移動(dòng)頭, 后半段只移動(dòng)尾巴
//這里把原來(lái)一共的比例換算到前半段的時(shí)間上來(lái)
float startTrimProgress = renderProgress / START_TRIM_DURATION_OFFSET;
//要開始的角度. 原始角度加已經(jīng)過(guò)了的角度
//向前伸出去的那個(gè)頭,加原始角度(在一次動(dòng)畫中他是不變的, 等于上一次動(dòng)畫結(jié)束的地方)
//再加最大的多半圈乘掃過(guò)的比例
mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
}
// Moving the end trim starts after 50% of a single ring
// animation completes
if (renderProgress > START_TRIM_DURATION_OFFSET) {
//超過(guò)一半的比例/后半程比例, 同上
float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
//尾巴所在的角度
mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress);
}
//要顯示的角度
if (Math.abs(mEndDegrees - mStartDegrees) > MIN_SWIPE_DEGREE) {
mSwipeDegrees = mEndDegrees - mStartDegrees;
}
//整個(gè)過(guò)程中畫布一一直慢慢的轉(zhuǎn)動(dòng)
mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
//不知道干啥的....
mRotationIncrement = mOriginRotationIncrement + (MAX_ROTATION_INCREMENT * renderProgress);
}
過(guò)程中設(shè)了畫筆的顏色, 顏色是動(dòng)畫前80%使用一個(gè)顏色, 后20%的時(shí)候使用
return ((startA + (int) (fraction * (endA - startA))) << 24)
| ((startR + (int) (fraction * (endR - startR))) << 16)
| ((startG + (int) (fraction * (endG - startG))) << 8)
| ((startB + (int) (fraction * (endB - startB))));
計(jì)算兩個(gè)顏色的過(guò)渡色, 就會(huì)產(chǎn)生過(guò)渡的顏色變化.
后面的計(jì)算基本如注釋描述. 直接看draw方法.
public void draw(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
//對(duì)Canvas進(jìn)行轉(zhuǎn)動(dòng), 產(chǎn)生畫的過(guò)程中首尾都在轉(zhuǎn)動(dòng)的效果
canvas.rotate(mGroupRotation, bounds.exactCenterX(), bounds.exactCenterY());
RectF arcBounds = mTempBounds;
arcBounds.set(bounds);
arcBounds.inset(mStrokeInset, mStrokeInset);
mPaint.setColor(mCurrentColor);
//繪制弧線, 就是Loading的主體
canvas.drawArc(arcBounds, mStartDegrees, mSwipeDegrees, false, mPaint);
canvas.restoreToCount(saveCount);
}
基本上是拿一到計(jì)算的數(shù)據(jù)進(jìn)行繪制就可以了, 記得每次都要將canvas進(jìn)行restore
. 別的方法就是與動(dòng)畫相關(guān)的: 開始, 停止之類, 不再分析.
學(xué)習(xí)一個(gè)簡(jiǎn)單的Loading圖就是這樣, 后面會(huì)再分析一個(gè)使用圖片的Loading圖, 就可以根據(jù)自己的需求進(jìn)行自定義各種動(dòng)畫了.
學(xué)習(xí)這個(gè)開源代碼最大的收獲就是感覺(jué)結(jié)果清晰, 每一個(gè)類, 每一個(gè)方法的責(zé)任都十分的明確, 十分值得我們學(xué)習(xí).