Android Loading動畫分析設計

sheng18 7年前發布 | 9K 次閱讀 安卓開發 Android開發 移動開發

android6.0上有了很炫酷的開機動畫,雖不知其實現原理是什么,但感覺扎心了!!有時間一定去翻一下源碼,本著對loading動畫的熱愛,后來找到了 LoadingDrawable庫 ,還是很炫酷的,簡要分析:

loadingdrawable.gif

原理

android中的動畫最后都是實現canvas上繪制,這些動畫也不例外,不得不說作者真是運用 Drawable , ValueAnimator , Canvas 到了極致。庫中有很多個實現,下面選scenery中的DayNightLoading動畫分析其實現過程。

  • 整體設計流程

    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" />

    發現自定義屬性DayNightLoadingRenderer(懷疑作者多寫個er >..<),通過這里傳入的參數,在構造方法中使用工廠方法 LoadingRendererFactory.createLoadingRenderer(context, loadingRendererId) 構造相應的 LoadingRenderer ,它就是主角,通過 setLoadingRenderer() 創建 LoadingDrawable ,LoadingDrawable繼承于Drawable,最后調用 setImageDrawable 顯示。

    剝洋蔥一樣一層一層分析,LoadingView就是基本自定義view的流程,進入LoadingDrawable發現:

    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太多了,我承認自己很low這個沒見過...,啪啪啪(鼠標聲)點進源碼看看這是什么鬼,是 android.graphics.drawable.Drawable 中的靜態接口,注釋寫的很明白 如果想做一個動畫的drawable,這個drawable又繼承于{@link android.graphics.drawable.Drawable Drawable},那么就實現這個接口就好了,自定義的drawable通過setCallback后能schedule并execute動畫的變化 ,那就很明確了,這就是個動畫Drawable用來不斷繪制自己的接口,自定義Drawable需要set。 LoadingDrawable 中為了完成動畫的各個狀態變化,直接實現了 Animatable 接口。

    接下來看 LoadingRenderer 類,是個抽象類,那肯定就是為了后續拓展各種Renderer而設計成抽象類。類中定義了缺省的大小和動畫時間,如何實現動畫的插值器呢,這里使用了 ValueAnimator ,是屬性動畫的一種,其缺省插值器是 AccelerateDecelerateInterpolator ,通過 mRenderAnimator.addUpdateListener 更新動畫的當前值,并重繪,具體為監聽過程調用 computeRender((float) animation.getAnimatedValue()); 與 invalidateSelf(); 。

    這里順帶提一下 LoadingRendererFactory ,工廠類,工廠類采用 SparseArray 存放id與對應的類,通過反射獲取相應的類構造函數,從而獲取相應的對象。

    到了關鍵人物 DayNightLoadingRenderer ,繼承抽象類LoadingRenderer。

  • 分析DayNightLoadingRenderer

    mInitSun$MoonCoordinateY 關鍵變量1,太陽和月亮升起的起始位置。

    mMaxSun$MoonRiseDistance 關鍵變量2,上升的最大距離。

    那么問題來了,drawable是如何知道什么時候繪制什么曲線的呢,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);
          }

是按照太陽和月亮的當前高度與初始位置高度 的 相對位置判斷如何繪制圖形。具體canvas的知識這里就不詳細介紹了,網上很多。

還記得剛才 computeRender((float) animation.getAnimatedValue()) 與 invalidateSelf() 吧,這里實現了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來判斷當前動畫的進度,當前類中定義了動畫的一些關鍵點,比如: SUN_DECREASE_DURATION_OFFSET 、 STAR_RISE_START_DURATION_OFFSET 等,這些代表太陽落下、星星升起等等。通過關鍵點的判斷改變太陽、月亮、星星的一些屬性及太陽、月亮的y軸位置。最后不斷重新計算render,不斷invalidateSelf,實現整個動畫。

總結

熟悉drawable、valueAnimator、callback是基礎,但是實現動畫的具體變換計算、如何繪制才是重點,這得一點點調出來,實在佩服。這兩天看看計算過程,容易講的話,再寫一篇。

 

來自:http://www.jianshu.com/p/646b3b42c471

 

 本文由用戶 sheng18 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!