android属性动画分析
来源:互联网 发布:手机卡iphone7在线软件 编辑:程序博客网 时间:2024/06/05 22:53
上班太忙,一直想写个博客去记录,各种没动力,万事开头难,今天终于克服重重阻力来写第一篇博客。
动画一直是个坑,入坑深似海,用法相信都用过,由于属性动画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),这个方法里面
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); } } }
其中 if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
判断是否在UI线程执行,在UI线程就调用了Handler的消息通知。最终调用的是原生方法,从底层去绘制,属性动画的主线流程原理大概就是这样,由于篇幅问题,还有很多方法没细讲。有不明白的朋友可以和我交流一下,互相学习
- Android属性动画分析
- Android属性动画分析
- android属性动画分析
- Android属性动画深入分析
- Android属性动画深入分析
- 深入分析Android属性动画
- Android属性动画深入分析
- Android属性动画深入分析
- Android 动画分析之属性动画
- Android 属性动画的原理分析
- Android属性动画ValueAnimator源码简单分析
- Android属性动画ObjectAnimator源码简单分析
- Android属性动画AnimatorSet源码简单分析
- Android属性动画ObjectAnimator源码简单分析
- Android动画--属性动画
- android动画 -- 属性动画
- Android动画-属性动画
- Android动画【属性动画】
- Python基础概念_12_编程风格
- c++优先队列,小根堆
- java递归实现最大公约数和最小公倍数
- 使用jquery.qrcode生成二维码
- Android开发之EditText
- android属性动画分析
- 【Webservice】 Eclipse根据wsdl文件自动生成webservice的调用客户端
- 彻底理解webservice SOAP WSDL
- 欢迎使用CSDN-markdown编辑器
- ofbiz添加物品到购物车失败Could not find a valid price for the product with ID [XX-XXXX], not adding to cart.
- 关于帝国cms跳转手机模板代码
- android
- 如何把局域网内不同数据库的两个表的数据进行传输?
- java 书单