如何防止开发者选项关闭动画后使其失效

知其所以然。

Posted by YSY on August 3, 2018

很多用户为了手机用起来感觉上快,在开发者选项里把系统动画给关了,即把那3个缩放比例改成了0,系统默认一般是1x。个人建议调到0.5x就很合适了,没必要关闭,否则某些动效比较多的App体验会很差。 本文的问题就是,当开发者选项中的动画缩放比例被全部调成0后,App中部分设置了duration的属性动画就会失效,表现为直接从开头跳到了结尾,没有动画过程。

我们先分析下为什么会这样。 看ValueAnimator(/frameworks/base/core/java/android/animation/ValueAnimator.java)的部分源码:

public final boolean doAnimationFrame(long frameTime) {
    ......
 
    if (mLastFrameTime < 0) {
        if (mSeekFraction >= 0) {
            long seekTime = (long) (getScaledDuration() * mSeekFraction); // 开发者选项改后此处为0
            mStartTime = frameTime - seekTime;
            mSeekFraction = -1;
        }
        mStartTimeCommitted = false;
    }
    mLastFrameTime = frameTime;
     
    final long currentTime = Math.max(frameTime, mStartTime);
    boolean finished = animateBasedOnTime(currentTime);
 
    if (finished) {
        endAnimation();
    }
    return finished;
}
 
boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        final long scaledDuration = getScaledDuration(); // 开发者选项改后此处为0
        ......
        if (scaledDuration == 0) {
            // 0 duration animator, ignore the repeat count and skip to the end
            done = true;
        } ......
    }
    return done;
}
 
 
private long getScaledDuration() {
    return (long)(mDuration * sDurationScale); // 开发者选项修改直接影响sDuration的值
}

动画绘制是否结束取决于animateBasedOnTime方法,源码也注释到,如果duration为0,会忽略repeat count直接跳到结束状态。这里的sDurationScale也就是开发者选项中设置的动画缩放倍数。 我们只要在动画初始化后,通过反射来重置一下这个静态变量即可:

private void resetAnimatorDurationScale() {
    try {
        Field field = ValueAnimator.class.getDeclaredField("sDurationScale");
        field.setAccessible(true);
        if (field.getFloat(null) == 0) {
            field.setFloat(null, 1);
        }
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

但很显然,系统会在应用进程重启后第一次调用getWindowManagerService时重设这个值,所以必须得每次启动后都设置一次(比如在主Activity的onCreate中)。