Android属性动画学习笔记

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

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属性动画完全解析(下)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265

推荐阅读更多精彩内容