天天看點

自定義動畫(仿Win10加載動畫)——優化源代碼優化分析核心代碼

此為前文章寫的仿Win10加載動畫的優化版

源代碼

已更新到github

優化分析

原生 自定義高仿(v1版)
自定義動畫(仿Win10加載動畫)——優化源代碼優化分析核心代碼
自定義動畫(仿Win10加載動畫)——優化源代碼優化分析核心代碼

一直覺得自己寫的與原生的有差别,經過仔細對比觀察,發現:

  1. 原生的圓點出發位置不是都在底部,而是第一個在底部,後面的緊接着前面一個,像球在管子裡一樣
  2. 圓點結束的位置就是該圓點開始的位置
  3. 經過比對,發現一個周期的時間是7500ms,非7000ms

經過優化後的對比:

原生 自定義高仿(v1.1版)
自定義動畫(仿Win10加載動畫)——優化源代碼優化分析核心代碼
自定義動畫(仿Win10加載動畫)——優化源代碼優化分析核心代碼

優化後的時間校正圖:

自定義動畫(仿Win10加載動畫)——優化源代碼優化分析核心代碼

核心代碼

不可否認,原生更加自然。為了自然,嘗試了過去掉頂部的兩段勻速運動,直接用四段三階貝塞爾曲線,原理上是行的通的。但測試過N多參數,後來還是覺得不夠自然,放棄了。最終還是選擇原來的方法。

起始角度的計算

第一個圓點在最底部,第二個與第一個相差一個圓點對旋轉中心所占的角度,後面也是圓點也是一樣,與前一個圓點相差此角度。此角度可通過圓點半徑與軌迹半徑計算:

// 計算圓點對旋轉中心所占的角度
 float trackR = halfSize - dotR;
 dotDegree = (float) Math.toDegrees( * Math.asin(dotR / trackR));
           

參數與之前相比,有了很大調整(最主要的就是參數,調了無數遍才調出來……):

/**
 * 建立動畫
 *
 * @param view 需執行的控件
 * @param index 該控件執行的順序
 * @return 該控件的動畫
 */
private Animator createViewAnim(final View view, final int index) {
    long duration = ; // 一個周期(2圈)一共運作7500ms,固定值

    // 最小執行機關時間
    final float minRunUnit = duration / f;
    // 最小執行機關時間所占總時間的比例
    double minRunPer = minRunUnit / duration;
    // 在插值器中實際值(Y坐标值),共8組
    final double[] trueRunInOne = new double[]{
            ,
            ,
             / d,
             / d,
             / d,
             / d,
             / d,
            
    };
    // 動畫開始的時間比偏移量。剩下的時間均攤到每個圓點上
    final float offset = (float) (index * ( - ) * minRunPer / (mDotViews.length - ));
    // 在內插補點器中理論值(X坐标值),與realRunInOne對應
    final double[] rawRunInOne = new double[]{
            ,
            offset + ,
            offset +  * minRunPer,
            offset +  * minRunPer,
            offset +  * minRunPer,
            offset +  * minRunPer,
            offset +  * minRunPer,
            offset +  * minRunPer
    };
    logI("minRunUnit=%f, minRunPer=%f, offset=%f", minRunUnit, minRunPer, offset);

    // 各貝塞爾曲線控制點的Y坐标
    final float p1_2 = calculateLineY(rawRunInOne[], trueRunInOne[], rawRunInOne[], trueRunInOne[], rawRunInOne[]);
    final float p1_4 = calculateLineY(rawRunInOne[], trueRunInOne[], rawRunInOne[], trueRunInOne[], rawRunInOne[]);
    final float p1_5 = calculateLineY(rawRunInOne[], trueRunInOne[], rawRunInOne[], trueRunInOne[], rawRunInOne[]);
    final float p1_7 = calculateLineY(rawRunInOne[], trueRunInOne[], rawRunInOne[], trueRunInOne[], rawRunInOne[]);

    // A 建立屬性動畫:繞着中心點旋轉2圈
    ObjectAnimator objAnim = ObjectAnimator.ofFloat(view, "rotation", -dotDegree * index,  - dotDegree * index);
    // B 設定一個周期執行的時間
    objAnim.setDuration(duration);
    // C 設定重複執行的次數:無限次重複執行下去
    objAnim.setRepeatCount(ValueAnimator.INFINITE);
    // D 設定內插補點器
    objAnim.setInterpolator(new TimeInterpolator() {
        @Override
        public float getInterpolation(float input) {
            if (input < rawRunInOne[]) {
                // 1 等待開始
                if (view.getVisibility() != INVISIBLE) {
                    view.setVisibility(INVISIBLE);
                }
                return ;

            } else if (input < rawRunInOne[]) {
                if (view.getVisibility() != VISIBLE) {
                    view.setVisibility(VISIBLE);
                }
                // 2 底部 → 左上角:貝賽爾曲線1
                // 先轉換成[0, 1]範圍
                input = calculateNewPercent(rawRunInOne[], rawRunInOne[], , , input);
                return calculateBezierQuadratic(trueRunInOne[], p1_2, trueRunInOne[], input);

            } else if (input < rawRunInOne[]) {
                // 3 左上角 → 頂部:直線
                return calculateLineY(rawRunInOne[], trueRunInOne[], rawRunInOne[], trueRunInOne[], input);

            } else if (input < rawRunInOne[]) {
                // 4 頂部 → 底部:貝賽爾曲線2
                input = calculateNewPercent(rawRunInOne[], rawRunInOne[], , , input);
                return calculateBezierQuadratic(trueRunInOne[], p1_4, trueRunInOne[], input);

            } else if (input < rawRunInOne[]) {
                // 5 底部 → 左上角:貝賽爾曲線3
                input = calculateNewPercent(rawRunInOne[], rawRunInOne[], , , input);
                return calculateBezierQuadratic(trueRunInOne[], p1_5, trueRunInOne[], input);

            } else if (input < rawRunInOne[]) {
                // 6 左上角 → 頂部:直線
                return calculateLineY(rawRunInOne[], trueRunInOne[], rawRunInOne[], trueRunInOne[], input);

            } else if (input < rawRunInOne[]) {
                // 7 頂部 → 底部:貝賽爾曲線4
                input = calculateNewPercent(rawRunInOne[], rawRunInOne[], , , input);
                return calculateBezierQuadratic(trueRunInOne[], p1_7, trueRunInOne[], input);

            } else {
                // 8 消失
                if (view.getVisibility() != INVISIBLE) {
                    view.setVisibility(INVISIBLE);
                }
                return ;
            }

        }
    });
    return objAnim;
}