LoadingDrawable前言

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
源碼地址:傳送門

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,650評論 4 61
  • 接入前先了解一下USB OTG的概念 USB OTG:USB On-The-Go通??s寫為USB OTG,是USB...
    gstansen閱讀 23,333評論 7 23
  • 事情兩年后,我自閉癥加重,社交有了陰影。每每浮現(xiàn)帶著心痛。 不是滋味的—— 這兩年被我稱為三觀不正、惡意欺騙的幾個...
    死傲驕閱讀 617評論 0 0
  • 親密關(guān)系中,壞的比好的更有力量。所以要保持滿意的親密關(guān)系,人們得維持5:1的獎賞-代價比。 而且人們常常沒有注意到...
    玩兒_溫暖閱讀 225評論 0 0

友情鏈接更多精彩內(nèi)容