此為前文章寫的仿Win10加載動畫的優化版
源代碼
已更新到github
優化分析
原生 | 自定義高仿(v1版) |
---|---|
一直覺得自己寫的與原生的有差别,經過仔細對比觀察,發現:
- 原生的圓點出發位置不是都在底部,而是第一個在底部,後面的緊接着前面一個,像球在管子裡一樣
- 圓點結束的位置就是該圓點開始的位置
- 經過比對,發現一個周期的時間是7500ms,非7000ms
經過優化後的對比:
原生 | 自定義高仿(v1.1版) |
---|---|
優化後的時間校正圖:
核心代碼
不可否認,原生更加自然。為了自然,嘗試了過去掉頂部的兩段勻速運動,直接用四段三階貝塞爾曲線,原理上是行的通的。但測試過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;
}