Android属性动画

发布于 2021-3-31 11:41
浏览
0收藏

一、简介
在Android3.0为了取代传统诞生的产物,相较于传统动画有着很灵活的优势。
**传统动画:**仅仅只能实现平移、缩放、旋转、淡入淡出这四种效果,并且对Button实现从左上角平移到右下角,点击右下角的Button时,并不能触发click事件,也就是说传统的补间动画并未对View的属性做到修改;以及传统动画仅仅只限于对View及其子类做动画。
**属性动画:**可扩展性较高,实现简单,不仅仅可以实现上述传统动画的四种效果,也可以实现ObjectAnimator对任意对象实现动画效果,并且真正意义上的对属性进行了改变。

二、属性动画的用法
2.1 ValueAnimator

ValueAnimator是属性动画的一个核心类,在第一节中说到了Animator是对value属性值进行操作的,初始值和结束值之间的过渡动画就是通过ValueAnimator.ofFloat(0f,1f)这句关键代码传入的,由于没有传入属性的具体值,因此需要通过addUpdateListener设置监听来观察value值的变化;参数设置完毕后,必须要调用animator.start()启动动画。

public void scale(View view) {
        //创建ValueAnimator,并且实现value从0-1的变化
        ValueAnimator animator = ValueAnimator.ofFloat(0f,1f);
        //设置动画持续时间为1000
        animator.setDuration(1000);
        //监听动画value值的改变,当value变化时回调onAnimationUpdate
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Log.d("kkjj","animation-->"+animation.getAnimatedValue());
            }
        });
        //启动动画
        animator.start();
    }


但属性动画是非常灵活的,我们也可以设置多个动画中间值,如下:

 public void scale(View view) {
        //创建ValueAnimator,并且实现value从0-1的变化
        ValueAnimator animator = ValueAnimator.ofFloat(0f,1f,5f,3f,10f);


除了上述可以设置属性动画的API以外,还有其他很多可供开发者选择,如下所示:setRepeatCount和setRepeatMode是重复播放相关的

        //设置动画重复次数
        animator.setRepeatCount(5);
        //设置动画重复播放的模式是从头开始RESTART,还是从倒播动画REVERSE,默认RESTART
        animator.setRepeatMode(ValueAnimator.RESTART);


2.2 ObjectAnimator

第一节中说到属性动画可以对任意对象实现动画效果,而不仅仅只限于View。在属性动画中提供了另一个核心类ObjectAnimator,其继承于ValueAnimator。

相比于ValueAnimator,ObjectAnimator从名字上看似乎更加适合开发者,因为ValueAnimator只不过是对value值进行动画变化,但使用场景似乎有限,因此Google推出了其ObjectAnimator,它可以对任意对象的任意属性实现动画效果。但虽然其功能强大,由于其继承自ValueAnimator,底层还是基于ValueAnimator完成的,由于继承关系,ValueAnimator的一些API,在ObjectAnimator上也能够使用。

其用法和ValueAnimator差不多,这里不做过多说明。这里需要说明以下两个API的作用,其原理在第三节源码解析叙述。
TypeEvaluator:估值器,设置属性值从初始值过渡到结束值 的变化具体数值。
Interpolator:插值器,设置属性值从初始状态,到结束状态的变化规律,有匀速、加速、减速。

public void scale1(View view) {
        Button button = (Button)view;
        ObjectAnimator animator = (ObjectAnimator) ObjectAnimator.ofFloat(button,"alpha",0f,1f);
        animator.setDuration(3000);
        //设置重复次数
        animator.setRepeatCount(5);
        //设置重复模式,默认为RESTART
        animator.setRepeatMode(ObjectAnimator.REVERSE);
        //设置插值器
        animator.setInterpolator(new Interpolator() {
            @Override
            public float getInterpolation(float input) {
                return input * input;
            }
        });
        //设置估值器
        animator.setEvaluator(new TypeEvaluator() {
            @Override
            public Object evaluate(float fraction, Object startValue, Object endValue) {
                float t = 5 * fraction*1.0f;
                PointF pointF = new PointF();
                pointF.x = 150 * t;
                pointF.y = 0.5f * 98.0f * t * t;
                return pointF;
            }
        });
        animator.start();
    }


2.3 多属性动画

上面代码都是改变某一个属性的动画,例如有如下场景:将Button的x,y坐标从左上角移动到右下角,在这个过程中同时将Button的透明度从1改变到0。

public void scale3(View view) {
        //通过PropertyValuesHolder分装多个属性值
        PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX",0f,320f);
        PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY",0f,480f);
        PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha",1f,0f);
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder((Button)view,holder1,holder2,holder3);
        animator.setDuration(3000);
        //设置重复次数
        animator.setRepeatCount(5);
        //设置重复模式,默认为RESTART
        animator.setRepeatMode(ObjectAnimator.REVERSE);
        animator.start();
    }


2.4 组合动画

也可以实现组合动画,比如先移动Button的y坐标,再移动x坐标,最后实现透明度的变换。
AnimatorSet 中提供play方法,它会返回一个Animator.Builder的构建者,并且Animator.Builder提供了如下四个方法。

/**
     * after(Animator anim)   将现有动画插入到传入的动画之后执行
     * after(long delay)   将现有动画延迟指定毫秒后执行
     * before(Animator anim)   将现有动画插入到传入的动画之前执行
     * with(Animator anim)   将现有动画和传入的动画同时执行
     * @param view
     */
    public void scale4(View view) {
        ObjectAnimator yAnimator = ObjectAnimator.ofFloat((Button)view,"scaleY",0f,480f);
        ObjectAnimator xAnimator = ObjectAnimator.ofFloat((Button)view,"scaleX",0f,320f);
        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat((Button)view,"alpha",1f,0f);
        AnimatorSet set = new AnimatorSet();
        set.play(yAnimator).after(xAnimator).with(alphaAnimator);
        set.setDuration(3000);
        set.start();
    }


三、源码解析
3.1 ValueAnimator源码

(1)先从ValueAnimator animator = ValueAnimator.ofFloat(0f,1f,5f,3f,10f);开始,可以看出在ofFloat中创建了对象,但ValueAnimator是空的,没什么可看的
疑问:ofFloat传入的参数代表什么?

public static ValueAnimator ofFloat(float... values) {
        //创建了ValueAnimator对象,并且将我们传入的关键帧传入setFloatValues
        ValueAnimator anim = new ValueAnimator();
        anim.setFloatValues(values);
        return anim;
    }
public ValueAnimator() {
    }

 

public void setFloatValues(float... values) {
        //当开发者未传入values时,直接返回
        if (values == null || values.length == 0) {
            return;
        }
        //第一次进入时,mValue为空,进入此方法
        if (mValues == null || mValues.length == 0) {
         //PropertyValuesHolder.ofFloat初始化了KeyFrameSet,并且将关键帧存储起来了
            setValues(PropertyValuesHolder.ofFloat("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setFloatValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }


此处propertyName传入的“”,返回的是PropertyValuesHolder的子类FloatPropertyValuesHolder

public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }


属性名最终被保存在了PropertyValuesHolder的mPropertyName属性中

public FloatPropertyValuesHolder(String propertyName, float... values) {
            super(propertyName);
            setFloatValues(values);
        }


继续往下看FloatPropertyValuesHolder的setFloatValues,在这里将value值交给父类PropertyValuesHolder的setFloatValues处理

@Override
        public void setFloatValues(float... values) {
        
            super.setFloatValues(values);
            mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
        }

 

PropertyValuesHolder初始化了setFloatValues和KeyframeSet,这里的KeyFrameSet是保存了所有关键帧的集合

public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
    }


这里主要是根据开发者传入的values值,为每一个value值创建一个关键帧,然后将这些关键帧封装到了KeyframeSet中。

public static KeyframeSet ofFloat(float... values) {
        boolean badValue = false;
        //获取我们传入的Values数组长度
        int numKeyframes = values.length;
        //创建数组
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        //若只有一个关键帧,则直接创建两个帧:起始帧和结束帧
        if (numKeyframes == 1) {
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
            if (Float.isNaN(values[0])) {
                badValue = true;
            }
        } else {
        //否则根据传入的所有Value值创建一个关键帧,然后保存在KeyframeSet中
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] =
                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
                if (Float.isNaN(values[i])) {
                    badValue = true;
                }
            }
        }
        if (badValue) {
            Log.w("Animator", "Bad value (NaN) in float animator");
        }
        return new FloatKeyframeSet(keyframes);
    }


总结1:
开发者传入的values目的是为了创建关键帧KeyFrame,最终将传入的所有Value值分装成了关键帧,封装到了KeyFrameSet集合中,根据ofFloat传入属性名称ValueAnimator animator = ValueAnimator.ofFloat(0f,1f,5f,3f,10f);,为每一中属性名创建一个PropertyValuesHolder对象,并且PropertyValuesHolder内部持有KeyFrameSet的对象

回到ValueAnimator的setObjectValues方法,此处想为什么会有mValues == null为空和不为空两种情况了,其实想想在2.3节中的多属性动画可知,当开发者创建了多属性动画时,第创建第二个PropertyValuesHolder对象时,mValues 就不为空了。

public void setObjectValues(Object... values) {
        if (values == null || values.length == 0) {
            return;
        }
        //这里的mValues是PropertyValuesHolder数组
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofObject("", null, values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setObjectValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }


(2)animator.start();是怎么播放动画的呢?

public void start() {
  //为false表示设置动画不反者播放
        start(false);
    }

private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        //可以看到由于默认传入的playBackwards为false,此处以下部分diamagnetic不用关注
        mReversing = playBackwards;
        mSelfPulse = !mSuppressSelfPulseRequested;
        // Special case: reversing from seek-to-0 should act as if not seeked at all.
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                // Calculate the fraction of the current iteration.
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
  //表示动画开始、暂停等等属性
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        // Resets mLastFrameTime when start() is called, so that if the animation was running,
        // calling start() would put the animation in the
        // started-but-not-yet-reached-the-first-frame phase.
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        //动画开始播放时间
        mStartTime = -1;
        addAnimationCallback(0);
  //mStartDelay 延时播放时间,mSeekFraction动画进度跳到的百分比,mReversing是否反向播放
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();
            //如果跳到的百分比为-1,则从0秒开始播放
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
               //设置到需要跳到的播放百分比
                setCurrentFraction(mSeekFraction);
            }
        }
    }
    
public void setCurrentPlayTime(long playTime) {
        float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
        setCurrentFraction(fraction);
    }


调用animateValue(currentIterationFraction);

public void setCurrentFraction(float fraction) {
        initAnimation();
        fraction = clampFraction(fraction);
        mStartTimeCommitted = true; // do not allow start time to be compensated for jank
        if (isPulsingInternal()) {
            long seekTime = (long) (getScaledDuration() * fraction);
            long currentTime = AnimationUtils.currentAnimationTimeMillis();
            // Only modify the start time when the animation is running. Seek fraction will ensure
            // non-running animations skip to the correct start time.
            mStartTime = currentTime - seekTime;
        } else {
            // If the animation loop hasn't started, or during start delay, the startTime will be
            // adjusted once the delay has passed based on seek fraction.
            mSeekFraction = fraction;
        }
        mOverallFraction = fraction;
        final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
        animateValue(currentIterationFraction);
    }


在ValueAnimator中最终回调到onAnimationUpdate,我们添加监听的位置

void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }


3.2 ObjectAnimator解析
下面这句代码的作用其实和ValueAnimator差不多,通过alpha属性创建PropertyValuesHolder对象,里面保存着0f,1f关键帧的KeyFrameSet集合,KeyFrameSet有持有各关键帧。

ObjectAnimator animator = ObjectAnimator.ofFloat(button,"alpha",0f,1f);


重点关注start方法,可以看到最后一句又回调到ValueAnimator的start方法

@Override
    public void start() {
    //判断如果当前动画,等待的动画和延迟的动画中有和当前动画相同的动画,就把相同的动画取消掉
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        //调用父类的start方法
        super.start();
    }


回到ValueAnimator的start,看到一句startAnimation();重点代码

private void start(boolean playBackwards) {
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }
public void setCurrentPlayTime(long playTime) {
        float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
        setCurrentFraction(fraction);
    }
public void setCurrentFraction(float fraction) {
        initAnimation();
        fraction = clampFraction(fraction);
        mStartTimeCommitted = true; // do not allow start time to be compensated for jank
        if (isPulsingInternal()) {
            long seekTime = (long) (getScaledDuration() * fraction);
            long currentTime = AnimationUtils.currentAnimationTimeMillis();
            // Only modify the start time when the animation is running. Seek fraction will ensure
            // non-running animations skip to the correct start time.
            mStartTime = currentTime - seekTime;
        } else {
            // If the animation loop hasn't started, or during start delay, the startTime will be
            // adjusted once the delay has passed based on seek fraction.
            mSeekFraction = fraction;
        }
        mOverallFraction = fraction;
        final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
        animateValue(currentIterationFraction);
    }


经过一系列调用,最终调用到子类ObjectAnimator的animateValue

void animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up. Note: we allow null target if the
            /// target has never been set.
            cancel();
            return;
        }
       //先调用父类的animateValue
        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].setAnimatedValue(target);
        }
    }


最终调用到PropertyValuesHolder的setAnimatedValue,这里关注 mSetter.invoke(target, mTmpValueArray);,先说明这句代码的意义:通过反射调用target View的mSetter方法。

void setAnimatedValue(Object target) {
            if (mIntProperty != null) {
                mIntProperty.setValue(target, mIntAnimatedValue);
                return;
            }
            if (mProperty != null) {
                mProperty.set(target, mIntAnimatedValue);
                return;
            }
            if (mJniSetter != 0) {
                nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
                return;
            }
            if (mSetter != null) {
                try {
                    mTmpValueArray[0] = mIntAnimatedValue;
                    mSetter.invoke(target, mTmpValueArray);
                } catch (InvocationTargetException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                } catch (IllegalAccessException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                }
            }
        }


那么mSetter是怎么来的呢?通过以下这句代码拼接而来的,为了方便理解,我自己写了一个setupSetterOrGetter方法

mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);


就是通过将属性名的第一个字母大写,然后和"set"和属性名拼接即可

public void setupSetterOrGetter(WeakReference<View> target) {
        char firstLetter = Character.toUpperCase(mPropertyName.charAt(0));
        String theRest = mPropertyName.substring(1);
        String methodName="set"+ firstLetter + theRest;
        try {
            mSetter = View.class.getMethod(methodName, float.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }


总结:也就是从这里可以得出,属性动画最终是通过反射调用Object的各set方法对Object的各属性设置。

3.3 插值器和估值器
第一次学习属性动画时,当听到别人说插值器和估值器感觉是个很复杂、很高大上的东西,其实不然。
其作用就是:插值器负责计算动画执行的百分比,估值器负责根据百分比来计算属性值。插值器计算完动画执行的百分比后,将Fraction交给估值器计算属性的百分比。

(1)TimeInterpolator:

public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

public interface Interpolator extends TimeInterpolator {
    // A new interface, TimeInterpolator, was introduced for the new android.animation
    // package. This older Interpolator interface extends TimeInterpolator so that users of
    // the new Animator-based animations can use either the old Interpolator implementations or
    // new classes that implement TimeInterpolator directly.
}


系统给开发者提供了好几种插值器,现在我们直接看看LinearInterpolator ,这里很容易的理解,getInterpolation的输入和返回成线性比例,开发这也可以自定义插值器,只需要继承Interpolator ,重写getInterpolation。如博主子啊2.2中缩写的input*input,播放速度与时间成平方的关系。

@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolator {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactory.createLinearInterpolator();
    }
}


(2)TypeEvaluator估值器

public interface TypeEvaluator<T> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value.
     * @param endValue   The end value.
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public T evaluate(float fraction, T startValue, T endValue);

}


系统也给开发者定义了很多估值器,如颜色属性的ArgbEvaluator,float类型的FloatEvaluator

Android属性动画-开源基础软件社区
这里直接看看FloatEvaluator的evaluate方法

public class FloatEvaluator implements TypeEvaluator<Number> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type <code>float</code> or
     *                   <code>Float</code>
     * @param endValue   The end value; should be of type <code>float</code> or <code>Float</code>
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        //根据属性的起始值+估值器计算出的动画执行百分比*(结束值-起始值)
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}


————————————————
版权声明:本文为博主「google忠实粉丝」的原创文章

收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐