自定义动画(仿Win10加载动画)——优化

此为前文章写的仿Win10加载动画的优化版


源代码

已更新到github

优化分析

原生 自定义高仿(v1版)
这里写图片描述 这里写图片描述

一直觉得自己写的与原生的有差别,经过仔细对比观察,发现:

  1. 原生的圆点出发位置不是都在底部,而是第一个在底部,后面的紧接着前面一个,像球在管子里一样
  2. 圆点结束的位置就是该圆点开始的位置
  3. 经过比对,发现一个周期的时间是7500ms,非7000ms

经过优化后的对比:

原生 自定义高仿(v1.1版)
这里写图片描述 这里写图片描述

优化后的时间校正图:
这里写图片描述

核心代码

不可否认,原生更加自然。为了自然,尝试了过去掉顶部的两段匀速运动,直接用四段三阶贝塞尔曲线,原理上是行的通的。但测试过N多参数,后来还是觉得不够自然,放弃了。最终还是选择原来的方法。

起始角度的计算
第一个圆点在最底部,第二个与第一个相差一个圆点对旋转中心所占的角度,后面也是圆点也是一样,与前一个圆点相差此角度。此角度可通过圆点半径与轨迹半径计算:

// 计算圆点对旋转中心所占的角度
 float trackR = halfSize - dotR;
 dotDegree = (float) Math.toDegrees(2 * Math.asin(dotR / trackR));

参数与之前相比,有了很大调整(最主要的就是参数,调了无数遍才调出来……):

/**
 * 创建动画
 *
 * @param view 需执行的控件
 * @param index 该控件执行的顺序
 * @return 该控件的动画
 */
private Animator createViewAnim(final View view, final int index) {
    long duration = 7500; // 一个周期(2圈)一共运行7500ms,固定值

    // 最小执行单位时间
    final float minRunUnit = duration / 100f;
    // 最小执行单位时间所占总时间的比例
    double minRunPer = minRunUnit / duration;
    // 在插值器中实际值(Y坐标值),共8组
    final double[] trueRunInOne = new double[]{
            0,
            0,
            160 / 720d,
            190 / 720d,
            360 / 720d,
            520 / 720d,
            550 / 720d,
            1
    };
    // 动画开始的时间比偏移量。剩下的时间均摊到每个圆点上
    final float offset = (float) (index * (100 - 86) * minRunPer / (mDotViews.length - 1));
    // 在差值器中理论值(X坐标值),与realRunInOne对应
    final double[] rawRunInOne = new double[]{
            0,
            offset + 0,
            offset + 11 * minRunPer,
            offset + 32 * minRunPer,
            offset + 43 * minRunPer,
            offset + 54 * minRunPer,
            offset + 75 * minRunPer,
            offset + 86 * minRunPer
    };
    logI("minRunUnit=%f, minRunPer=%f, offset=%f", minRunUnit, minRunPer, offset);

    // 各贝塞尔曲线控制点的Y坐标
    final float p1_2 = calculateLineY(rawRunInOne[2], trueRunInOne[2], rawRunInOne[3], trueRunInOne[3], rawRunInOne[1]);
    final float p1_4 = calculateLineY(rawRunInOne[2], trueRunInOne[2], rawRunInOne[3], trueRunInOne[3], rawRunInOne[4]);
    final float p1_5 = calculateLineY(rawRunInOne[5], trueRunInOne[5], rawRunInOne[6], trueRunInOne[6], rawRunInOne[4]);
    final float p1_7 = calculateLineY(rawRunInOne[5], trueRunInOne[5], rawRunInOne[6], trueRunInOne[6], rawRunInOne[7]);

    // A 创建属性动画:绕着中心点旋转2圈
    ObjectAnimator objAnim = ObjectAnimator.ofFloat(view, "rotation", -dotDegree * index, 720 - 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]) {
                // 1 等待开始
                if (view.getVisibility() != INVISIBLE) {
                    view.setVisibility(INVISIBLE);
                }
                return 0;

            } else if (input < rawRunInOne[2]) {
                if (view.getVisibility() != VISIBLE) {
                    view.setVisibility(VISIBLE);
                }
                // 2 底部 → 左上角:贝赛尔曲线1
                // 先转换成[0, 1]范围
                input = calculateNewPercent(rawRunInOne[1], rawRunInOne[2], 0, 1, input);
                return calculateBezierQuadratic(trueRunInOne[1], p1_2, trueRunInOne[2], input);

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

            } else if (input < rawRunInOne[4]) {
                // 4 顶部 → 底部:贝赛尔曲线2
                input = calculateNewPercent(rawRunInOne[3], rawRunInOne[4], 0, 1, input);
                return calculateBezierQuadratic(trueRunInOne[3], p1_4, trueRunInOne[4], input);

            } else if (input < rawRunInOne[5]) {
                // 5 底部 → 左上角:贝赛尔曲线3
                input = calculateNewPercent(rawRunInOne[4], rawRunInOne[5], 0, 1, input);
                return calculateBezierQuadratic(trueRunInOne[4], p1_5, trueRunInOne[5], input);

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

            } else if (input < rawRunInOne[7]) {
                // 7 顶部 → 底部:贝赛尔曲线4
                input = calculateNewPercent(rawRunInOne[6], rawRunInOne[7], 0, 1, input);
                return calculateBezierQuadratic(trueRunInOne[6], p1_7, trueRunInOne[7], input);

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

        }
    });
    return objAnim;
}

猜你喜欢

转载自blog.csdn.net/a10615/article/details/52757177