LoadingDrawable

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)不同的效果. 下面先看看他所涉及的類:

LoadingDrawable包結(jié)構(gòu)

真的是很簡(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í).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雏门,一起剝皮案震驚了整個(gè)濱河市熄阻,隨后出現(xiàn)的幾起案子浙芙,更是在濱河造成了極大的恐慌垮刹,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吓坚,死亡現(xiàn)場(chǎng)離奇詭異凿掂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)斯稳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門海铆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人挣惰,你說(shuō)我怎么就攤上這事卧斟。” “怎么了憎茂?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵珍语,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我竖幔,道長(zhǎng)板乙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任拳氢,我火速辦了婚禮募逞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饿幅。我一直安慰自己凡辱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布栗恩。 她就那樣靜靜地躺著透乾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪磕秤。 梳的紋絲不亂的頭發(fā)上乳乌,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音市咆,去河邊找鬼汉操。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蒙兰,可吹牛的內(nèi)容都是我干的磷瘤。 我是一名探鬼主播芒篷,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼采缚!你這毒婦竟也來(lái)了针炉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤扳抽,失蹤者是張志新(化名)和其女友劉穎篡帕,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸呢,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡镰烧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了楞陷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怔鳖。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖固蛾,靈堂內(nèi)的尸體忽然破棺而出败砂,到底是詐尸還是另有隱情,我是刑警寧澤魏铅,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站坚芜,受9級(jí)特大地震影響览芳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鸿竖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一沧竟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缚忧,春花似錦悟泵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至球榆,卻和暖如春朽肥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背持钉。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工衡招, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人每强。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓始腾,卻偏偏與公主長(zhǎng)得像州刽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浪箭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容