LoadingDrawable包含許多酷炫的加載動畫,可以與任何View配合使用作為加載動畫或者ProgressBar。項(xiàng)目的思路源于這個動畫鏈接
Note: 這個項(xiàng)目正處于更新階段, 將會不斷有新的加載動畫加入,歡迎關(guān)注我的Github, 獲取LoadingDrawable的最新動態(tài)。
項(xiàng)目概要
LoadingDrawable繼承Drawable(不懂的話可以先往后看)并實(shí)現(xiàn)接口Animatable編寫的動畫加載庫,本項(xiàng)目采用策略模式(Strategy),構(gòu)造函數(shù)必須傳入LoadingRenderer的子類,并通過回調(diào)Callback與LoadingRenderer進(jìn)行交互。LoadingRenderer主要負(fù)責(zé)LoadingDrawable的measure和draw。
Drawable
Drawable具有輕量級的、高效性、復(fù)用性強(qiáng)的特點(diǎn)。大家接觸Drawable的最常見形式應(yīng)該就是從res/drawable文件中讀取Drawable??????。跟View的不同之處在于Drawable無法接收事件或以其他形式與用戶交互。
Drawable提供了一些通用的控制繪制的方法。如下:
setBounds(Rect) 決定Drawable的繪制位置和大小, 所有的Drawable都應(yīng)該遵循這個規(guī)則,Drawable一般都是通過縮放的形式的返回setBounds(Rect)所指定的大小。此外一般都是通過getIntrinsicHeight()和getIntrinsicWidth()設(shè)置首選大小。
getPadding(Rect) 決定Drawable的內(nèi)間距。一般用于繪制在Drawable里面的內(nèi)容需要放到確定位置處,而不是開始位置的情況。
setState(int[]) 決定客戶端哪些狀態(tài)可繪制,如“focused”, "selected"等, 有些Drawables可以根據(jù)選定的狀態(tài)修改其繪制的圖像。定義StateListDrawable(xml中使用selector元素定義)時,我常用“selected”設(shè)置狀態(tài), 主要因?yàn)椴⒉皇撬械腣iew都有“checked”狀態(tài)。
setLevel(int) 允許客戶端提供一個單一連續(xù)控制器進(jìn)行修改當(dāng)前正在展示的Drawable,比如電池水平或進(jìn)度。有些Drawables可以根據(jù)當(dāng)前l(fā)evel修改其繪制的圖像。
Drawable.Callback 一般用于Drawable動畫的回調(diào)控制。所有的Drawable子類都應(yīng)該支持這個類,否則動畫將無法在View上正常工作(View是實(shí)現(xiàn)了這個接口與Drawable進(jìn)行交互的)。此外,這個類也是LoadingDrawable和LoadingRenderer之間交互的紐帶。
Animatable
Animatable 是Android針對支持Drawable動畫設(shè)計的接口。熟為人知的AnimationDrawable就是實(shí)現(xiàn)這個接口編寫的。
Animatable只提供了三個抽象方法。 如下:
isRunning() 表示動畫是否正在運(yùn)行。
start() 啟動Drawable的動畫。
stop() 停止Drawable的動畫。
LoadingDrawable
LoadingDrawable繼承Drawable并實(shí)現(xiàn)接口Animatable編寫的動畫加載庫。這個庫涉及了自定義Drawable時需要重寫的基本方法和編寫動畫的基本框架,LoadingDrawable像是一本書,敘述著動畫知識的篇章,LoadingDrawable也像是一個收集庫, 囊括著各種酷炫的動畫。
LoadingDrawable實(shí)現(xiàn)了什么 ?
圓形跳動系列
SwapLoadingRenderer
GuardLoadingRenderer
DanceLoadingRenderer
CollisionLoadingRenderer
-
圓形滾動系列
- GearLoadingRenderer
- WhorlLoadingRenderer
- LevelLoadingRenderer
- MaterialLoadingRenderer
-
風(fēng)景系列
- DayNightLoadingRenderer
- ElectricFanLoadingRenderer
-
動物系列
- FishLoadingRenderer
- GhostsEyeLoadingRenderer
-
物品系列
- BalloonLoadingRenderer
- WaterBottleLoadingRenderer
LoadingDrawable 涉及了什么知識 ?
1. Canvas的常見用法
2. Path的基本用法
3. PathMeasure的基本用法
4. Path之貝塞爾曲線
5. 貝賽爾曲線公式 (只需知道線性公式,二次方公式,三次方公式即可)
LoadingDrawable 核心代碼有哪些 ?
public class LoadingDrawable extends Drawable implements Animatable {
private LoadingRenderer mLoadingRender;
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);
}
};
public LoadingDrawable(LoadingRenderer loadingRender) {
this.mLoadingRender = loadingRender;
this.mLoadingRender.setCallback(mCallback);
}
@Override
public void draw(Canvas canvas) {
mLoadingRender.draw(canvas, getBounds());
}
@Override
public void setAlpha(int alpha) {
mLoadingRender.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mLoadingRender.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void start() {
mLoadingRender.start();
}
@Override
public void stop() {
mLoadingRender.stop();
}
@Override
public boolean isRunning() {
return mLoadingRender.isRunning();
}
@Override
public int getIntrinsicHeight() {
return (int) mLoadingRender.getHeight();
}
@Override
public int getIntrinsicWidth() {
return (int) mLoadingRender.getWidth();
}
}
LoadingDrawable內(nèi)部的實(shí)現(xiàn)完全委托給了一個LoadingRenderer類型的成員變量, 但并不是代理模式, 我只是將LoadingDrawable 內(nèi)部的行為統(tǒng)一的抽取到LoadingRenderer內(nèi)部,便于子類繼承進(jìn)行統(tǒng)一的實(shí)現(xiàn)。我設(shè)計LoadingDrawable的思路源于對Android源碼MaterialProgressDrawable的分析, 當(dāng)初感覺MaterialProgressDrawable內(nèi)部實(shí)現(xiàn)挺不錯,就是感覺代碼很亂,我就進(jìn)行了一次整理, 就產(chǎn)生了LoadingDrawable的雛形。 接下來就看一下LoadingRenderer的內(nèi)部實(shí)現(xiàn)吧 。
public abstract class LoadingRenderer {
private static final long ANIMATION_DURATION = 1333;
private static final float DEFAULT_SIZE = 56.0f;
private static final float DEFAULT_CENTER_RADIUS = 12.5f;
private static final float DEFAULT_STROKE_WIDTH = 2.5f;
protected float mWidth;
protected float mHeight;
protected float mStrokeWidth;
protected float mCenterRadius;
private long mDuration;
private Drawable.Callback mCallback;
private ValueAnimator mRenderAnimator;
public LoadingRenderer(Context context) {
setupDefaultParams(context);
setupAnimators();
}
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();
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) {
computeRender((float) animation.getAnimatedValue());
invalidateSelf();
}
});
}
protected void addRenderListener(Animator.AnimatorListener animatorListener) {
mRenderAnimator.addListener(animatorListener);
}
public void setCenterRadius(float centerRadius) {
mCenterRadius = centerRadius;
}
public float getCenterRadius() {
return mCenterRadius;
}
public void setStrokeWidth(float strokeWidth) {
mStrokeWidth = strokeWidth;
}
public float getStrokeWidth() {
return mStrokeWidth;
}
public float getWidth() {
return mWidth;
}
public void setWidth(float width) {
this.mWidth = width;
}
public float getHeight() {
return mHeight;
}
public void setHeight(float height) {
this.mHeight = height;
}
public long getDuration() {
return mDuration;
}
public void setDuration(long duration) {
this.mDuration = duration;
mRenderAnimator.setDuration(mDuration);
}
}
mWidth,mHeight決定著Drawable的首選大?。赡軙焕欤热鏘mageView設(shè)置不同的ScaleType或者作為View的背景的時候),mStrokeWidth和mCenterRadius是便于統(tǒng)一當(dāng)初Circle系列的Paint,不應(yīng)該被提取到基類,后期會優(yōu)化掉這些變量。
mRenderAnimator 是ValueAnimator類型的變量, 是LoadingRenderer渲染動畫的觸發(fā)器! 在onAnimationUpdate()方法里調(diào)用了computeRender(float)以及invalidateSelf()。computeRender(float)負(fù)責(zé)計算當(dāng)前所要繪制內(nèi)容的位置和大小以及色值等。invalidateSelf() 通過回調(diào)mCallback調(diào)用invalidateDrawable(null)觸發(fā)LoadingDrawable重新繪制。 LoadingDrawable的重新繪制又會調(diào)用LoadingRenderer的draw(Canvas, Rect)方法,然后子類draw(Canvas, Rect)方法通過computeRender(float)計算的數(shù)據(jù)實(shí)現(xiàn)繪制。
LoadingDrawable 相關(guān)的源碼分析文檔有哪些 ?
Circle系列源碼解析
Fish源碼解析
期待你參與LoadingDrawable源碼分析。 互相學(xué)習(xí),互相進(jìn)步 !!!
LoadingDrawable 實(shí)現(xiàn)效果圖是什么樣 ?
LoadingDrawable 如何使用 ?
在java中使用
LoadingView.setLoadingRenderer(LoadingRenderer);
在xml中使用
<app.dinus.com.loadingdrawable.LoadingView
android:id="@+id/level_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#fff1c02e"
app:loading_renderer="LevelLoadingRenderer"/>
關(guān)于我
我喜歡Android, 喜歡開源, 喜歡做動畫, 會不定期開源一些有用的項(xiàng)目。
我會第一時間在微博分享我在Github上面開源的項(xiàng)目,歡迎關(guān)注我的微博 dinus_developer
源碼地址:傳送門