美文网首页Android资料学习艺术探索
Android属性动画学习笔记

Android属性动画学习笔记

作者: 梦sora | 来源:发表于2016-07-03 09:16 被阅读1712次

    这段时间正好要做些动画,于是把属性动画重新学习了一遍,做些总结

    1. 前言

    Android动画分为Frame AnimationTweened AnimationProperty Animation
    既然已经有了前两种动画,为什么还要Property Animation,核心点就是Property Animation是改变对象的属性,不仅仅是对view本身做操作

    2. 关键类的使用

    1. ObjectAnimator 动画的执行类
    2. ValueAnimator 动画的执行类
    3. AnimatorSet 控制一组动画的执行
    4. AnimatorInflater 加载属性动画的xml文件
    5. TypeEvaluator 类型估值,主要用于设置动画操作属性的值。
    6. TimeInterpolator 时间插值,定义动画变化率
    7. LayoutTransition 布局动画,为布局的容器设置动画
    8. ViewPropertyAnimator 为View的动画操作提供一种更加便捷的用法
    2.1 ObjectAnimator的使用
    ObjectAnimator.ofFloat(view, "translationY", 0f, 500f)
        .setDuration(500)
        .start();
    

    view在0.5秒向下滑动500px的效果

    2.2 ValueAnimator的使用
    ValueAnimator.ofFloat(0f, 1f)
        .setDuration(500)
        .start();
    

    属性0.5秒的从0变成1
    执行了好像什么都没发生啊,那我们添加个监听器看看

    ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
    anim.setDuration(500);
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float currentValue = (float) valueAnimator.getAnimatedValue();
            Log.d(TAG, "current value is " + currentValue);
        }
    });
    anim.start();
    

    日志如图


    确实在0.5秒内打印了(这边先提一下,打印的输出不是线性的,参见TimeInterpolator 时间插值)
    于是实现view在0.5秒向下滑动500px的效果
    ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
    anim.setTarget(view);
    anim.setDuration(500);
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            float currentValue = (float) valueAnimator.getAnimatedValue();
            view.setTranslationY(currentValue * 500);
        }
    });
    anim.start();
    
    2.3 AnimatorSet的使用
    ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", -500f, 0f);
    ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
    ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f);
    AnimatorSet animSet = new AnimatorSet();
    animSet.play(rotate).with(fadeInOut).after(moveIn);animSet.setDuration(5000);
    animSet.start();
    

    view先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作

    其实还有更简单的方式,实现一个动画更改多个效果:使用propertyValuesHolder,几个动画同时执行

    PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha",1f,0f, 1f);
    PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
    PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
    ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ).setDuration(1000).start();
    
    2.4 AnimatorInflater的使用

    加载xml中的属性动画
    在res下建立animator文件夹,然后建立res/animator/alpha.xml

    <?xml version="1.0" encoding="utf-8"?>
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="500"
        android:propertyName="alpha"
        android:valueFrom="1.0"
        android:valueTo="0.0"
        android:valueType="floatType" >
    </objectAnimator>
    
    Animator anim = AnimatorInflater.loadAnimator(this, R.animator.alpha);
    anim.setTarget(view);
    anim.start();
    

    view的一个0.5秒淡出效果

    2.5 TypeEvaluator的使用

    ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,那么这个平滑过度是怎么做到的呢?其实就是系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值

    public class FloatEvaluator implements TypeEvaluator {  
        public Object evaluate(float fraction, Object startValue, Object endValue) {  
            float startFloat = ((Number) startValue).floatValue();  
            return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
        }  
    }  
    

    ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的

    public class PointEvaluator implements TypeEvaluator{  
        @Override  
        public Object evaluate(float fraction, Object startValue, Object endValue) {  
            Point startPoint = (Point) startValue;  
            Point endPoint = (Point) endValue;  
            float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());  
            float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());  
            Point point = new Point(x, y);  
            return point;  
        }  
    }  
    

    重写了evaluate()方法

    Point point1 = new Point(0, 0);  
    Point point2 = new Point(300, 300);  
    ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);  
    anim.setDuration(5000);  
    anim.start();  
    

    通过对Point对象进行动画操作,从而实现整个自定义View的动画效果。

    2.6 TimeInterpolator的使用
    ValueAnimator anim = ObjectAnimator.ofFloat(view, "translationY", 0f, 500f);
    anim.setDuration(1000);
    anim.setInterpolator(new LinearInterpolator());
    anim.start();
    

    设置了一个匀速运动

    2.7 LayoutTransition的使用
    LayoutTransition transition = new LayoutTransition();  
        transition.setAnimator(LayoutTransition.CHANGE_APPEARING,  
                transition.getAnimator(LayoutTransition.CHANGE_APPEARING));  
        transition.setAnimator(LayoutTransition.APPEARING,  
                null);  
        transition.setAnimator(LayoutTransition.DISAPPEARING,  
                null);  
        transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,  
                null);  
        mGridLayout.setLayoutTransition(transition);  
    

    过渡的类型一共有四种:
    LayoutTransition.APPEARING 当一个View在ViewGroup中出现时,对此View设置的动画
    LayoutTransition.CHANGE_APPEARING 当一个View在ViewGroup中出现时,对此View对其他View位置造成影响,对其他View设置的动画
    LayoutTransition.DISAPPEARING 当一个View在ViewGroup中消失时,对此View设置的动画
    LayoutTransition.CHANGE_DISAPPEARING 当一个View在ViewGroup中消失时,对此View对其他View位置造成影响,对其他View设置的动画
    LayoutTransition.CHANGE 不是由于View出现或消失造成对其他View位置造成影响,然后对其他View设置的动画。
    注意动画到底设置在谁身上,此View还是其他View。

    2.8 ViewPropertyAnimator
    view.animate().x(500).y(500).setDuration(5000)  
            .setInterpolator(new BounceInterpolator());  
    
    2.9 Animator的监听器
    anim.addListener(new AnimatorListener() {  
        @Override  
        public void onAnimationStart(Animator animation) {  
        }  
      
        @Override  
        public void onAnimationRepeat(Animator animation) {  
        }  
      
        @Override  
        public void onAnimationEnd(Animator animation) {  
        }  
      
        @Override  
        public void onAnimationCancel(Animator animation) {  
        }  
    }); 
    

    可以监听到动画的各种事件,如果觉得不想用到这么多,可以用AnimatorListenerAdapter,这个抽象类有对AnimatorListener的空实现,这样就可以单独重写某个事件了

    anim.addListener(new AnimatorListenerAdapter() {  
    });  
    

    3. 关键类的详解

    3.1 ObjectAnimator

    上面用了ofFloat
    还有ofIntofFloatofObject,这几个方法都是设置动画作用的元素、作用的属性,动画开始、结束、以及中间的任意个属性值。
    当设置1个值,则为从当前属性开始改变
    当设置2个值,则一个为开始、一个为结束
    当设置多个值,则依次改变
    来看看ofFloat的具体实现,主要看propertyName参数

    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }
    
    1. 先看构造方法
    private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);}
    
    public void setPropertyName(@NonNull String propertyName) {
        // mValues could be null if this is being constructed piecemeal. Just record the
        // propertyName to be used later when setValues() is called if so.
        if (mValues != null) {
            PropertyValuesHolder valuesHolder = mValues[0];
            String oldName = valuesHolder.getPropertyName();
            valuesHolder.setPropertyName(propertyName);
            mValuesMap.remove(oldName);
            mValuesMap.put(propertyName, valuesHolder);
        }
        mPropertyName = propertyName;
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }
    

    mValuesMap.put(propertyName, valuesHolder);于是存入了一个map,key是propertyName,value是存有propertyName的PropertyValuesHolder

    1. 再看anim.setFloatValues(values);
    @Overridepublic void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }
    
    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);}
    
    public FloatPropertyValuesHolder(String propertyName, float... values) {
        super(propertyName);
        setFloatValues(values);
    }
    
    @Overridepublic void setFloatValues(float... values) {
        super.setFloatValues(values);
        mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
    }
    
    public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
    }
    

    这里有个KeyframeSet,是Keyframe的集合,而Keyframe叫做关键帧,为一个动画保存time/value(时间与值)对。再看KeyframeSet.ofFloat(values)

    public static KeyframeSet ofFloat(float... values) {
        boolean badValue = false;
        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 {
            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);
    }
    
    public static Keyframe ofFloat(float fraction) {
        return new FloatKeyframe(fraction);
    }
    public static Keyframe ofFloat(float fraction, float value) {
        return new FloatKeyframe(fraction, value);
    }
    

    value被拆分成了许多时间片fraction
    上面都是存储设置,这边还有个疑问,那就是我设置的propertyName是如何利用的呢,我们往下看

    因为ObjectAnimator extends ValueAnimator,我们来看ValueAnimatorstart()函数

    @Overridepublic void start() {
        start(false);
    }
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        mPlayingBackwards = playBackwards;
        if (playBackwards && mSeekFraction != -1) {
            if (mSeekFraction == 0 && mCurrentIteration == 0) {
                // special case: reversing from seek-to-0 should act as if not seeked at all            mSeekFraction = 0;
            } else if (mRepeatCount == INFINITE) {
                mSeekFraction = 1 - (mSeekFraction % 1);
            } else {
                mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
            }
            mCurrentIteration = (int) mSeekFraction;
            mSeekFraction = mSeekFraction % 1;
        }
        if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
                (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
            // if we were seeked to some other iteration in a reversing animator,
            // figure out the correct direction to start playing based on the iteration
            if (playBackwards) {
                mPlayingBackwards = (mCurrentIteration % 2) == 0;
            } else {
                mPlayingBackwards = (mCurrentIteration % 2) != 0;
            }
        }
        int prevPlayingState = mPlayingState;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        mPaused = false;
        updateScaledDuration();
     // in case the scale factor has changed since creation time
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually starting it running
            if (prevPlayingState != SEEKED) {
                setCurrentPlayTime(0);
            }
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();}
    

    setCurrentPlayTime(0)

    public void setCurrentPlayTime(long playTime) { 
       float fraction = mUnscaledDuration > 0 ? (float) playTime / mUnscaledDuration : 1; 
       setCurrentFraction(fraction);
    }
    public void setCurrentFraction(float fraction) {
        ...
        animateValue(fraction);
    }
    

    我们在看回ObjectAnimator

    @Overridevoid animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up.
            cancel();
            return;
        }
        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].setAnimatedValue(target);
        }
    }
    

    PropertyValuesHolder[] mValues我们再去PropertyValuesHolder中看

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

    因此ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法

    3.2 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);
    }
    

    getInterpolation()方法中接收一个input参数,这个参数的值会随着动画的运行而不断变化,不过它的变化是非常有规律的,就是根据设定的动画时长匀速增加,变化范围是0到1。也就是说当动画一开始的时候input的值是0,到动画结束的时候input的值是1,而中间的值则是随着动画运行的时长在0到1之间变化的。
    而input的值决定了fraction的值。input的值是由系统经过计算后传入到getInterpolation()方法中的,然后我们可以自己实现getInterpolation()方法中的算法,根据input的值来计算出一个返回值,而这个返回值就是fraction了。

    参考:
    Android 属性动画(Property Animation) 完全解析 (上)
    Android 属性动画(Property Animation) 完全解析 (下)
    Android 属性动画 源码解析 深入了解其内部实现
    Android属性动画完全解析(上)
    Android属性动画完全解析(中)
    Android属性动画完全解析(下)

    相关文章

      网友评论

        本文标题:Android属性动画学习笔记

        本文链接:https://www.haomeiwen.com/subject/lwoqjttx.html