上班太忙,一直想写个博客去记录,各种没动力,万事开头难,今天终于克服重重阻力来写第一篇博客。 动画一直是个坑,入坑深似海,用法相信都用过,由于属性动画API改动比较多,就选个API25的来分析下,其它版本类似。
究竟里面的源码究竟是怎样的?怎么去看?首先要猜想,如果你是谷歌的开发人员,要开发类似的功能,要怎么去做?
属性动画,顾名思义就是不断改变属性的值,达到动画的效果。
ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "translationX", 0f, 1000f);
这是属性动画的常规用法,就是把控件从x方向移动,0移动到1000,第二个参数是属性值,看过View的源码的朋友可能会知道,View里面有个方法是setTranslationX,也是改变x方向的移动的,那它们之间是否有联系?为什么我填"translationX"就能往x方向移动,写"translationY"就往y的方式移动,字符串是怎么被识别到的?最大的可能就是反射。点进去ofFloat方法看看
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
然后点进去ObjectAnimator构造方法看看
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
是个私有的构造方法,然后调用了两个set方法,我的猜想应该是设置属性的,分别点进去看看,首先是setTarget方法
public void setTarget(@Nullable Object target) {
final Object oldTarget = getTarget();
if (oldTarget != target) {
if (isStarted()) {
cancel();
}
mTarget = target == null ? null : new WeakReference<Object>(target);
// New target should cause re-initialization prior to starting
mInitialized = false;
}
}
可以看到view在这个方法里被赋值给mTarget里,所以先记住mTarget就是我们的View,还是个弱引用,防止内存泄漏。
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;
}
这个方法也是进行赋值的 ,关键在mPropertyName = propertyName。
所以ofFloat还调用了setFloatValues方法,
public 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);
}
}
前面if(mValues ==
null || ==0) 那个方法先不用看,那是特殊情况的处理,先看重点,调用了父类的setFloatValues方法,点进去看看
public void setFloatValues(float... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
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;
}
还是看重点,重点在else里面的代码块,发现有个PropertyValuesHolder类,这个类也是做动画常用的类,然后调用了它的setFloatValues方法
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}
mValueType从名字上可以知道是value的类型,这个成员变量属于类类型,这个类里面还有setIntValues,就是类型不同,关键是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);
}
这里明显可以看出,是在遍历这个values数组把value赋值到keyframe数组里面,还有计算了百分比,然后new 了FloatKeyframeSet,把keyframes传进入,最后调用了这个方法
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
// immutable list
mKeyframes = Arrays.asList(keyframes);
mFirstKeyframe = keyframes[0];
mLastKeyframe = keyframes[mNumKeyframes - 1];
mInterpolator = mLastKeyframe.getInterpolator();
}
这个方法主要是吧keyframes转成了数组,存在mKeyframes方法里面。
所以来个阶段总结,ObjectAnimator.ofFloat调用了之后做了这几个事情,主要都是赋值,把view设置到mTarget,把propertName设置到mPropertyName里面,把values存到mKeyframes里面,mKeyframes就是属性动画的关键帧。
要调用start()方法才会开启动画,所以我们先看一下start()方法里面做了什么。点进去可以看到,还调用了父类的start()方法,
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
// 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;
mLastFrameTime = 0;
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
if (mStartDelay == 0 || mSeekFraction >= 0) {
startAnimation();
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
代码很长,其他看不懂先跳过,主要看这个方法调用了startAnimation()方法,然后
startAnimation()方法调用了initAnimation()方法,然后常理是点进去initAnimation()方法,这里要注意了,很容易晕,点进去的是ValueAnimator的initAnimation()方法,实际调用的是ObjectAnimator的initAnimation()方法,所以是
void initAnimation() {
if (!mInitialized) {
// mValueType may change due to setter/getter setup; do this before calling super.init(),
// which uses mValueType to set up the default type evaluator.
final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(target);
}
}
super.initAnimation();
}
}
关键代码来了,是mValues[i].setupSetterAndGetter(target),mValues是PropertyValuesHolder的引用,target是view,所以我们进去PropertyValuesHolder查看这个方法。setupSetterAndGetter很长,就不帖代码了,主要里面调用了setupSetter方法,setupSetter调用了setupSetterOrGetter方法,点进去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);
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);
}
propertyMap.put(mPropertyName, setterOrGetter);
}
}
return setterOrGetter;
}
这里有个参数字符串prefix,值是"set",代码里面调用了getPropertyFunction方法,我们点进去看看
private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
// TODO: faster implementation...
Method returnVal = null;
String methodName = getMethodName(prefix, mPropertyName);
Class args[] = null;
if (valueType == null) {
try {
returnVal = targetClass.getMethod(methodName, args);
} catch (NoSuchMethodException e) {
// Swallow the error, log it later
}
} 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);
if (mConverter == null) {
// change the value type to suit
mValueType = typeVariant;
}
return returnVal;
} catch (NoSuchMethodException e) {
// Swallow the error and keep trying other variants
}
}
// If we got here, then no appropriate function was found
}
if (returnVal == null) {
Log.w("PropertyValuesHolder", "Method " +
getMethodName(prefix, mPropertyName) + "() with type " + valueType +
" not found on target class " + targetClass);
}
return returnVal;
}
这里明显看到是用到了反射把set和propertName拼成字符串,并把propertName的首字母改成大写,那就印证了我们一开始的猜想,比如传的是propertName是translationX,就会反射调用了view 的setTranslationX方法,并改变属性。所以调用完initAnimation()方法之后,主要是为PropertyValuesHolder类的mSetter变量赋值,然后再回到ValueAnimator的start()方法里,这里有两行关键代码
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
点进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(mFrameCallback)方法,点进去发现是个接口方法,首先我们看看getProvide()的具体类是什么
private AnimationFrameCallbackProvider getProvider() { if (mProvider == null) { mProvider = new MyFrameCallbackProvider(); } return mProvider; }
具体类是MyFrameCallbackProvider,点进这个类查看postFrameCallback()方法的具体实现,然后发现是调用了Choreographer的postFrameCallback()方法。层层调用,最终是调用了Choreographer的postCallbackDelayedInternal()方法private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { if (DEBUG_FRAMES) { Log.d(TAG, "PostCallback: type=" + callbackType + ", action=" + action + ", token=" + token + ", delayMillis=" + delayMillis); } synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) { scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }
里面有个判断,如果没设置动画延后时间,就会调用scheduleFrameLocked(now),这个方法里面
其中 if (isRunningOnLooperThreadLocked()) {private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame on vsync."); } // If running on the Looper thread, then schedule the vsync immediately, // otherwise post a message to schedule the vsync from the UI thread // as soon as possible. if (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { final long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } } }
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
判断是否在UI线程执行,在UI线程就调用了Handler的消息通知。最终调用的是原生方法,从底层去绘制,属性动画的主线流程原理大概就是这样,由于篇幅问题,还有很多方法没细讲。有不明白的朋友可以和我交流一下,互相学习