android6.0上有了很炫酷的開機(jī)動(dòng)畫袖扛,雖不知其實(shí)現(xiàn)原理是什么,但感覺扎心了J攻锰!有時(shí)間一定去翻一下源碼,本著對(duì)loading動(dòng)畫的熱愛妓雾,后來找到了LoadingDrawable庫娶吞,還是很炫酷的,簡(jiǎn)要分析:
原理
android中的動(dòng)畫最后都是實(shí)現(xiàn)canvas上繪制械姻,這些動(dòng)畫也不例外妒蛇,不得不說作者真是運(yùn)用Drawable
,ValueAnimator
楷拳,Canvas
到了極致绣夺。庫中有很多個(gè)實(shí)現(xiàn),下面選scenery中的DayNightLoading動(dòng)畫分析其實(shí)現(xiàn)過程欢揖。
-
整體設(shè)計(jì)流程
LoadingView是最終在layout文件中的自定義view陶耍,繼承與ImageView,使用如下:
<app.dinus.com.loadingdrawable.LoadingView
android:id="@+id/day_night_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#ff071c28"
app:loading_renderer="DayNightLoadingRenderer" />
發(fā)現(xiàn)自定義屬性DayNightLoadingRenderer(懷疑作者多寫個(gè)er >..<)她混,通過這里傳入的參數(shù)烈钞,在構(gòu)造方法中使用工廠方法LoadingRendererFactory.createLoadingRenderer(context, loadingRendererId)
構(gòu)造相應(yīng)的LoadingRenderer
泊碑,它就是主角,通過setLoadingRenderer()
創(chuàng)建LoadingDrawable
毯欣,LoadingDrawable繼承于Drawable馒过,最后調(diào)用setImageDrawable
顯示。
剝洋蔥一樣一層一層分析酗钞,LoadingView就是基本自定義view的流程腹忽,進(jìn)入LoadingDrawable發(fā)現(xiàn):
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);
}
};
Android中的Callback太多了,我承認(rèn)自己很low這個(gè)沒見過...砚作,啪啪啪(鼠標(biāo)聲)點(diǎn)進(jìn)源碼看看這是什么鬼窘奏,是android.graphics.drawable.Drawable
中的靜態(tài)接口,注釋寫的很明白如果想做一個(gè)動(dòng)畫的drawable葫录,這個(gè)drawable又繼承于{@link android.graphics.drawable.Drawable Drawable}着裹,那么就實(shí)現(xiàn)這個(gè)接口就好了,自定義的drawable通過setCallback后能schedule并execute動(dòng)畫的變化
压昼,那就很明確了求冷,這就是個(gè)動(dòng)畫Drawable用來不斷繪制自己的接口瘤运,自定義Drawable需要set窍霞。LoadingDrawable
中為了完成動(dòng)畫的各個(gè)狀態(tài)變化,直接實(shí)現(xiàn)了Animatable
接口拯坟。
接下來看LoadingRenderer
類但金,是個(gè)抽象類,那肯定就是為了后續(xù)拓展各種Renderer而設(shè)計(jì)成抽象類郁季。類中定義了缺省的大小和動(dòng)畫時(shí)間冷溃,如何實(shí)現(xiàn)動(dòng)畫的插值器呢,這里使用了ValueAnimator
梦裂,是屬性動(dòng)畫的一種似枕,其缺省插值器是AccelerateDecelerateInterpolator
,通過mRenderAnimator.addUpdateListener
更新動(dòng)畫的當(dāng)前值年柠,并重繪凿歼,具體為監(jiān)聽過程調(diào)用computeRender((float) animation.getAnimatedValue());
與invalidateSelf();
。
這里順帶提一下LoadingRendererFactory
冗恨,工廠類答憔,工廠類采用SparseArray
存放id與對(duì)應(yīng)的類,通過反射獲取相應(yīng)的類構(gòu)造函數(shù)掀抹,從而獲取相應(yīng)的對(duì)象虐拓。
到了關(guān)鍵人物DayNightLoadingRenderer
,繼承抽象類LoadingRenderer傲武。
-
分析DayNightLoadingRenderer
mInitSun$MoonCoordinateY
關(guān)鍵變量1蓉驹,太陽和月亮升起的起始位置城榛。
mMaxSun$MoonRiseDistance
關(guān)鍵變量2,上升的最大距離戒幔。
那么問題來了吠谢,drawable是如何知道什么時(shí)候繪制什么曲線的呢,draw中給出答案:
if (mSunCoordinateY < mInitSun$MoonCoordinateY) {
canvas.drawCircle(arcBounds.centerX(),
mSunCoordinateY, mSun$MoonRadius, mPaint);
}
if (mMoonCoordinateY < mInitSun$MoonCoordinateY) {
int moonSaveCount = canvas.save();
canvas.rotate(mMoonRotation, arcBounds.centerX(), mMoonCoordinateY);
canvas.drawPath(createMoonPath(arcBounds.centerX(), mMoonCoordinateY), mPaint);
canvas.restoreToCount(moonSaveCount);
}
是按照太陽和月亮的當(dāng)前高度與初始位置高度 的 相對(duì)位置判斷如何繪制圖形诗茎。具體canvas的知識(shí)這里就不詳細(xì)介紹了工坊,網(wǎng)上很多。
還記得剛才computeRender((float) animation.getAnimatedValue())
與invalidateSelf()
吧敢订,這里實(shí)現(xiàn)了computeRender:
if (renderProgress <= SUN_RISE_DURATION_OFFSET) {
...
}
if (renderProgress <= SUN_ROTATE_DURATION_OFFSET && renderProgress > SUN_RISE_DURATION_OFFSET) {
...
...
}
...
...
if (renderProgress <= MOON_DECREASE_END_DURATION_OFFSET && renderProgress > MOON_DECREASE_START_DURATION_OFFSET) {
...
}
這里清一色通過renderProgress來判斷當(dāng)前動(dòng)畫的進(jìn)度王污,當(dāng)前類中定義了動(dòng)畫的一些關(guān)鍵點(diǎn),比如:SUN_DECREASE_DURATION_OFFSET
楚午、STAR_RISE_START_DURATION_OFFSET
等昭齐,這些代表太陽落下、星星升起等等矾柜。通過關(guān)鍵點(diǎn)的判斷改變太陽阱驾、月亮、星星的一些屬性及太陽怪蔑、月亮的y軸位置里覆。最后不斷重新計(jì)算render,不斷invalidateSelf缆瓣,實(shí)現(xiàn)整個(gè)動(dòng)畫喧枷。
總結(jié)
熟悉drawable、valueAnimator弓坞、callback是基礎(chǔ)隧甚,但是實(shí)現(xiàn)動(dòng)畫的具體變換計(jì)算、如何繪制才是重點(diǎn)渡冻,這得一點(diǎn)點(diǎn)調(diào)出來戚扳,實(shí)在佩服。這兩天看看計(jì)算過程族吻,容易講的話帽借,再寫一篇。