属性动画源码分析
Posted 花花young
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了属性动画源码分析相关的知识,希望对你有一定的参考价值。
前言
android动画分为帧动画、View动画、属性动画,其实帧动画也是View动画的一种,只不过它和平移、旋转等常见的View动画表现形式上不同。View动画是一种渐进式动画,而帧动画则是通过顺序播放一系列图像从而产生动画效果,然而图片过大就会发生OOM,所以尽量不要使用帧动画。本文主要讲解View动画使用、ObjectAnimator源码来进行讲解。
View动画
View动画有四个子类:TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation,这四种动画既可以通过xml来定义,也可以通过代码来动态构建。对于View动画来说,建议采用xml来定义动画,这是因为xml格式的动画可读性更好。
ok,来段代码温习一下:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:shareInterpolator="true"
android:duration="2000">
<!--
android:shareInterpolator 表示集合中的动画是否和集合共享一个插值器
-->
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.5" />
<rotate
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" />
<scale
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.8"
android:toYScale="0.8" />
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="300"
android:toYDelta="400"/>
</set>
在代码中开启动画
Animation monkeyAnim = AnimationUtils.loadAnimation(this,R.anim.view_anim);
monkey.startAnimation(monkeyAnim);
用法很简单,当然Animation类还有一些其它的方法,这里就不在赘述。
除了系统提供的这四种动画,我们还可以自定义View动画,在实际的开发很少用到。自定义动画需要继承Animation抽象类
public class CustomAnimation extends Animation
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight)
super.initialize(width, height, parentWidth, parentHeight);
@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
super.applyTransformation(interpolatedTime, t);
在initialize方法中做一些初始化的操作,在applyTransformation中进行相应的矩阵变换,很多时候需要采用Camera来简化矩阵变换的过程。在ApiDemo里面提供了一个自定义Animation类Rotate3dAnimation,拿来学习一下
/**
* 这个动画也添加了z轴上的唯一来提高旋转的效果
*/
public class Rotate3dAnimation extends Animation
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;
public Rotate3dAnimation(float fromDegrees, float toDegrees,
float centerX, float centerY, float depthZ, boolean reverse)
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
mCenterX = centerX;
mCenterY = centerY;
mDepthZ = depthZ;
mReverse = reverse;
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight)
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
@Override
protected void applyTransformation(float interpolatedTime, Transformation t)
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
if (mReverse)
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
else
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
代码很简单,这里我们简单说一下preTranslate和postTranslate的用法
- preTranslate方法的作用是在旋转之前先把图片向上移动图片高度的一半的距离,再向左移动图片宽度一半的距离,这样图片就关于x、y轴对称了,然后再进行旋转的变换
- postTranslate方法是在变换之后再将图片向下移动图片高度的一半的距离,再向右移动宽度一半的距离,也就是回到了原来的位置,这样图片显示出来的结果就是对称的了。原理也很简单,旋转中心还是(0,0),只不过我们移动图片,这样进行旋转变换的时候就会得到对称的结果了
这种操作和Canvas的save、restore有类似之处
View动画大致就这些,另外还有两个View动画的其他使用场景:
- LayoutAnimation控制子元素的出场的效果
xml的定义
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.5"
android:animationOrder="random"
android:animation="@anim/anim_item"/>
<!--
android:delay 表示子元素动画时间的延迟,比如子元素入场时间是300ms,那么0.5表示每个子元素都延迟150ms
总体来说,第一个子元素延迟150ms开始播放入场动画,第二个子元素延迟300ms开始播放入场动画,以此类推
android:animationOrder 表示子元素动画的顺序,有三种选项:normal、reverse和random
normal按顺序显示,reverse逆向显示 ranom随机顺序
-->
在布局中直接在ViewGroup使用android:layoutAnimation=”@anim/anim_layout”即可,当然我们也可以在代码中动态控制
Animation animation = AnimationUtils.loadAnimation(this,R.anim.anim_layout);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
container.setLayoutAnimation(controller);
- Activity的切换效果
主要用到overridePendingTransition(int enterAnim, int exitAnim)这个方法,这个方法必须在startActivity(intent)或者finish()之后调用才生效。
ok,View动画就over了,接下来开启本文的重头戏ObjectAnimator的源码分析
ObjectAnimator源码分析
ObjectAnimator简单使用
属性动画是Api 11新加入的功能,和View动画不同,它对作用对象进行了扩展,属性动画可以对任何对象做动画,甚至还可以没有对象。属性动画的效果也比View动画也得到了加强。如果要兼容低版本可以使用nineoldandroids库,网址:http://nineoldandroids.com
简单创建方式
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(button,"width",50,500);
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.setDuration(2000);
objectAnimator.start();
当然你也可以在xml里面定义
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:duration="200"
android:propertyName="scale"
android:valueFrom="0"
android:valueTo="1.0"
android:valueType="floatType" />
<objectAnimator
android:duration="200"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType" />
</set>
在属性动画中最重要的还是插值器和估值器,通过两个同时使用才计算出每个时刻的Value,介绍一下
TimeInterpolator 时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比
系统预置的有:
- LineaerInterpolar 线性插值器 匀速动画
- AccelerateDecelerateInterpolator 加速减速插值器 动画两头慢中间快
- DecelerateInterpolator 减速插值器 动画越来越慢
TypeEvaluator 类型估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值
系统预置的有:
- IntEvaluator 针对整型属性
- FloatEvaluator 针对浮点型属性
- ArgbEvaluator 针对Color属性
ObjectAnimator源码分析
属性动画要求动画作用的对象提供该属性的set方法,属性动画就会根据你传递该属性的初始值和最终值,通过反射多次调用set方法。那我们先从ObjectAnimator.ofFloat开始分析
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
第一行只是初始化了ObjectAnimator并存储了target、propertyName值,我们重点是要查看values的去向,OK,进入anim.setFloatValues(value)方法
@Override
public void setFloatValues(float... values)
if (mValues == null || mValues.length == 0)
if (mProperty != null)
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
else
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
else
super.setFloatValues(values);
我们没有使用mProperty,所以mProperty为null,进而执行setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));最终调用了FloatPropertyValueHolder的构造方法
public FloatPropertyValuesHolder(String propertyName, float... values)
super(propertyName); // 将propertyName存入FloatPropertyValueHolder里面
setFloatValues(values);
进入FloatPropertyValueHolder.setFloatValue(value)
@Override
public void setFloatValues(float... values)
super.setFloatValues(values);
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
通过调用super.setFloatValue(value),之后将FloatKeyframeSet赋值,由此我们可以猜想super.setFloatValue()方法是生成KeyframeSet对象
public void setFloatValues(float... values)
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
mValueType内部保存了我们调用时候的类型,然后调用KeyframeSet.ofFloat方法生成该对象
public static KeyframeSet ofFloat(float... values)
int numKeyframes = values.length;
if (numKeyframes == 1) // 当ofFloat参数中的可变数组传入的是一个则自动加上判断
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0]))
badValue = true;
else // 生成keyframe数组并存入FloatKeyframeSet中
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;
return new FloatKeyframeSet(keyframes);
这里说明了我们在可变参数传一个的时候为什么动画是从0开始?原来在内部在初始化的时候进行了处理,初始化操作就到这里,最后来个图理解一下
在动画框架里面PropterValueHolder数组至少会有一个,每个PropterValueHolder代表着一种动画,当需要定义多个动画时可以为ValueAnimator传入多个PropertyValueHolder,我们可以这样认为:
ValueAnimator是给某个控件设置动画,PropertyValueHolder是给该控件设置做什么样动画,KeyframeSet是给该控件该动画设置每个时间段的速率
接下来我们开始进入objectAnimator.start()方法,最终调用ValueAnimator的start方法
private void start(boolean playBackwards)
if (Looper.myLooper() == null) // 判断当前线程是否有Looper,因为这是更新UI操作所以要在主线程或在开启Looper的子线程执行
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
......
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale)); // AnimationHandler和系统底层的信号量有关,当产生一次信号量则执行一次这个回调方法,最后会进行分析
if (mStartDelay == 0 || mSeekFraction >= 0)
......
// frame after the start delay.
startAnimation(); // 当startDelay为0的时候则立即调用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);
针对AnimationHandler的代码文章结尾在进行分析,现在我们的目的是弄清楚getter/setter方法是如何得到的,OK,进入startAnimation()方法
private void startAnimation()
mAnimationEndRequested = false;
initAnimation(); // 初始化动画操作
mRunning = true;
......
进入initAnimation()
/**
在执行动画第一帧的时候这个方法快速调用,如果startDelay为非0,则这个方法在这个delay最后的时候调用,这个方法包括设置Evaluator和setter/getter方法
*/
@CallSuper
@Override
void initAnimation()
if (!mInitialized) // 是否进行初始化
final Object target = getTarget();
if (target != null)
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i)
mValues[i].setupSetterAndGetter(target); -->1
super.initAnimation(); -->2
这里分两个线索进行分析:1、setupSetterAndGetter方法 2、super.initAnimation方法
- 这里对每个PropertyValueHolder都进行了遍历操作,因为对于每个PropertyValueHolder可能会做不同的动画,自然而然生成的setter/getter方法也不一样,进入setupSetterAndGetter方法
void setupSetterAndGetter(Object target)
if (mProperty != null)
try
......
catch (ClassCastException e)
mProperty = null;
// 这里不能使用else,因为在上面的代码catch语句将mProperty置为null了
if (mProperty == null)
Class targetClass = target.getClass(); // 目标对象class对象
if (mSetter == null)
setupSetter(targetClass); // 生成Setter方法
List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++)
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) // 如果keyframe里面没有值则通过get方法重新进行设置
if (mGetter == null)
setupGetter(targetClass); //生成getter方法
if (mGetter == null)
// 避免后面的代码发生NPE
return;
try
Object value = convertBack(mGetter.invoke(target));
kf.setValue(value);
kf.setValueWasSetOnStart(true);
在这个方法重点也就是在setupSetter/setupGetter,这两个形成方式都是一样的,这里只研究setupSetter方法,进入setupSetter(targetClass)方法
@Override
void setupSetter(Class targetClass)
......
synchronized (sJNISetterPropertyMap)
HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
boolean wasInMap = false;
if (propertyMap != null)
wasInMap = propertyMap.containsKey(mPropertyName);
......
if (!wasInMap)
String methodName = getMethodName("set", mPropertyName);
try
// 通过JNI来获取setter方法
mJniSetter = nGetFloatMethod(targetClass, methodName);
catch (NoSuchMethodError e)
if (propertyMap == null)
propertyMap = new HashMap<String, Long>();
sJNISetterPropertyMap.put(targetClass, propertyMap);
propertyMap.put(mPropertyName, mJniSetter);
if (mJniSetter == 0)
// 不能通过JNI快速找到方法,只能通过反射来得到
super.setupSetter(targetClass);
通过JNI方式来获取setter方法,如果找到则存入缓存propertyMap中,避免了多次通过JNI的方式来获取setter方法,进而调用setupSetterOrGetter方法
private Method setupSetterOrGetter(Class targetClass,
HashMap<Class, HashMap<String, Method>> propertyMapMap,
String prefix, Class valueType)
Method setterOrGetter = null;
synchronized(propertyMapMap)
HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
boolean wasInMap = false;
if (propertyMap != null)
wasInMap = propertyMap.containsKey(mPropertyName);
// 先查看缓存是否存在mPropertyName对应的setter、getter方法
if (wasInMap) // 如果存在则在缓存中取
setterOrGetter = propertyMap.get(mPropertyName);
if (!wasInMap) // 如果不存在则反射得到
setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
if (propertyMap == null)
propertyMap = new HashMap<String, Method>();
propertyMapMap.put(targetClass, propertyMap);
// 然后再存入缓存中,方便相同propertyName再次通过反射得到
propertyMap.put(mPropertyName, setterOrGetter);
return setterOrGetter;
进入getPropertyFunction()方法
private Method getPropertyFunction(Class targetClass, String prefix, Class valueType)
Method returnVal = null;
String methodName = getMethodName(prefix, mPropertyName);
Class args[] = null;
if (valueType == null)
try
returnVal = targetClass.getMethod(methodName, args);
catch (NoSuchMethodException e)
// 没有getter时也不会报错,内部catch了
else
args = new Class[1];
Class typeVariants[];
if (valueType.equals(Float.class))
typeVariants = FLOAT_VARIANTS;
else if (valueType.equals(Integer.class))
typeVariants = INTEGER_VARIANTS;
else if (valueType.equals(Double.class))
typeVariants = DOUBLE_VARIANTS;
else
typeVariants = new Class[1];
typeVariants[0] = valueType;
for (Class typeVariant : typeVariants)
args[0] = typeVariant;
try
returnVal = targetClass.getMethod(methodName, args);
return returnVal;
catch (NoSuchMethodException e)
// 没有setter时也不会报错,内部catch了
return returnVal;
ok,就这样getter/setter方法的对象就被创建,在动画过程中不断的去执行Method.invoke方法来达到不断调用setter的方法的目的
- setupSetterAndGetter方法已经分析完毕,紧接着这个方法后面调用了super.initAnimation()方法,最终调用的是PropertyValueHolder.init()方法
/**
* 被ValueAnimator调用的内部方法去设置TypeEvaluator,将用于计算动画属性
*/
void init()
if (mEvaluator == null)
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :
null;
if (mEvaluator != null)
mKeyframes.setEvaluator(mEvaluator);
这里为KeyframeSet设置了估值器,我们在查看源码会看到ValueAnimator、PropertyValueHolder、KeyframeSet都有Evaluator的属性,这是为什么呢?这三者都能实现相应的动画
来看看ValueAnimator的setEvaluator方法做了什么事情
public void setEvaluator(TypeEvaluator value)
if (value != null && mValues != null && mValues.length > 0)
mValues[0].setEvaluator(value);
我们会发现它只是给PropertyValueHolder数组中的第一个设置了,其实这样做也是合理的,毕竟在使用属性动画的时候这个PropertyValueHolder数组中至少会有一个,当你想给其它设置的话只需要调用PropertyValueHolder的setEvaluator方法就可以了
然而在一般动画中我们都不会去设置Evaluator,好在在调用KeyframeSet的方法时已经做了相应的处理并不需要我们去担心,好了,进入FloatKeyframeSet一探究竟
@Override
public float getFloatValue(float fraction)
if (mNumKeyframes == 2)
......
if (mEvaluator == null)
return firstValue + fraction * deltaValue;
else
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue();
if (fraction <= 0f)
......
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
else if (fraction >= 1f)
......
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i)
......
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
prevKeyframe = nextKeyframe;
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
看到如此贴心的代码今后用属性框架也放心了不少。
在上面已经似乎没看到调用Method.invoke方法,那动画是怎么更新的呢?在Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,和底层代码进行通信就是Choreographer机制,此类用于同Vsync机制配合来实现统一调度
当Vsync信号发出的时候 会回调mFrameCallback对象中的doFrame,其实在这个方法就是去调用setter方法来完成动画操作。如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成
在ValueAnimator.start方法时,调用了下面的代码
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
进入animationHandler.addAnimationFrameCallback
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay)
if (mAnimationCallbacks.size() == 0)
getProvider().postFrameCallback(mFrameCallback);
if (!mAnimationCallbacks.contains(callback))
mAnimationCallbacks.add(callback);
if (delay > 0)
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
getProvider().postFrameCallback方法最终调用了mChoreographer.postFrameCallback(callback);将AnimationHandler和Choreographer进行关联,当有VSYNC信号时则触发mFrameCallback对象中的doFrame
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback()
@Override
public void doFrame(long frameTimeNanos)
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0)
getProvider().postFrameCallback(this);
;
进入doAnimationFrame(getProvider().getFrameTime());
private void doAnimationFrame(long frameTime)
int size = mAnimationCallbacks.size();
long currentTime = SystemClock.uptimeMillis();
for (int i = 0; i < size; i++)
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null)
continue;
if (isCallbackDue(callback, currentTime))
callback.doAnimationFrame(frameTime);
......
cleanUpList();
可见最终调用的AnimationFrameCallback的doAnimationFrame方法,在上面animationHandler.addAnimationFrameCallback传入的this,所以实现方法在ValueAnimator类中
/**
* 一帧执行动画
*/
public final void doAnimationFrame(long frameTime)
AnimationHandler handler = AnimationHandler.getInstance();
......
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished)
endAnimation(); // 结束动画
进入animateBasedOnTime方法
/**
* 这个返回值表明这个动画是否结束(这个发生在当当前动画用时超过这个动画持续时间,包含重复的次数)
*/
boolean animateBasedOnTime(long currentTime)
boolean done = false;
if (mRunning)
..... 判断临界值用于将done置为true,也就是动画结束
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
animateValue(currentIterationFraction);
return done;
进入ObjectAnimator.animateValue方法
@CallSuper
@Override
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;
super.animateValue(fraction); // 最终调用mFloatKeyframes.getFloatValue(fraction);从FloatKeyFrameSet中得到相应的值
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i)
mValues[i].setAnimatedValue(target); // 调用invoke方法将值设置进去
进入FloatPropertyValueHolder.setAnimatedValue方法
@Override
void setAnimatedValue(Object target)
......
if (mJniSetter != 0) //如果jni得到set不为空
nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
return;
if (mSetter != null) // 如果jni得到set为空并且mSetter不为空才使用反射进行设置
try
mTmpValueArray[0] = mFloatAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
至此,ObjectAnimator的源码分析就先到这里
以上是关于属性动画源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Android属性动画ValueAnimator源码简单分析