这段时间正好要做些动画,于是把属性动画重新学习了一遍,做些总结
1. 前言
Android动画分为Frame Animation
,Tweened Animation
,Property Animation
既然已经有了前两种动画,为什么还要Property Animation
,核心点就是Property Animation
是改变对象的属性,不仅仅是对view本身做操作
2. 关键类的使用
-
ObjectAnimator
动画的执行类 -
ValueAnimator
动画的执行类 -
AnimatorSet
控制一组动画的执行 -
AnimatorInflater
加载属性动画的xml文件 -
TypeEvaluator
类型估值,主要用于设置动画操作属性的值。 -
TimeInterpolator
时间插值,定义动画变化率 -
LayoutTransition
布局动画,为布局的容器设置动画 -
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
还有ofInt
、ofFloat
、ofObject
,这几个方法都是设置动画作用的元素、作用的属性,动画开始、结束、以及中间的任意个属性值。
当设置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;
}
- 先看构造方法
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
- 再看
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
,我们来看ValueAnimator
的start()
函数
@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属性动画完全解析(下)
网友评论