前言

本文为Android动画系列的最后一篇文章,通过对源码的分析,能够让大家更深刻地理解属性动画的工作原理,这有助于我们更好地使用属性动画。但是,由于动画的底层实现已经深入到jni层,并且涉及到显示子系统,因此,深入地分析动画的底层实现不仅比较困难而且意义不大,因此,本文的分析到jni层为止。

Android动画系列:

属性动画的原理

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去拿属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多,下面看源码分析。

源码分析

首先我们要找一个入口,就从ObjectAnimator.ofInt(mButton, “width”, 500).setDuration(5000).start()开始吧,其他动画都是类似的。

看ObjectAnimator的start方法

@Override

public void start() {

// See if any of the current active/pending animators need to be canceled

AnimationHandler handler = sAnimationHandler.get();

if (handler != null) {

int numAnims = handler.mAnimations.size();

for (int i = numAnims - 1; i >= 0; i--) {

if (handler.mAnimations.get(i) instanceof ObjectAnimator) {

ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);

if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {

anim.cancel();

}

}

}

numAnims = handler.mPendingAnimations.size();

for (int i = numAnims - 1; i >= 0; i--) {

if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {

ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);

if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {

anim.cancel();

}

}

}

numAnims = handler.mDelayedAnims.size();

for (int i = numAnims - 1; i >= 0; i--) {

if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {

ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);

if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {

anim.cancel();

}

}

}

}

if (DBG) {

Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration());

for (int i = 0; i < mValues.length; ++i) {

PropertyValuesHolder pvh = mValues[i];

ArrayList keyframes = pvh.mKeyframeSet.mKeyframes;

Log.d("ObjectAnimator", " Values[" + i + "]: " +

pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +

keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());

}

}

super.start();

}

说明:上面的代码别看那么长,其实做的事情很简单,首先会判断一下,如果当前动画、等待的动画(Pending)和延迟的动画(Delay)中有和当前动画相同的动画,那么就把相同的动画给取消掉,接下来那一段是log,再接着就调用了父类的super.start()方法,

因为ObjectAnimator继承了ValueAnimator,所以接下来我们看一下ValueAnimator的Start方法

private void start(boolean playBackwards) {

if (Looper.myLooper() == null) {

throw new AndroidRuntimeException("Animators may only be run on Looper threads");

}

mPlayingBackwards = playBackwards;

mCurrentIteration = 0;

mPlayingState = STOPPED;

mStarted = true;

mStartedDelay = false;

mPaused = false;

AnimationHandler animationHandler = getOrCreateAnimationHandler();

animationHandler.mPendingAnimations.add(this);

if (mStartDelay == 0) {

// This sets the initial value of the animation, prior to actually starting it running

setCurrentPlayTime(0);

mPlayingState = STOPPED;

mRunning = true;

notifyStartListeners();

}

animationHandler.start();

}

说明:上述代码最终会调用AnimationHandler的start方法,这个AnimationHandler并不是Handler,它是个Runnable。看下它的代码,通过代码我们发现,很快就调到了jni层,不过jni层最终还是要调回来的。它的run方法会被调用,这个

Runnable涉及到和底层的交互,我们就忽略这部分,直接看重点:ValueAnimator中的doAnimationFrame方法

final boolean doAnimationFrame(long frameTime) {

if (mPlayingState == STOPPED) {

mPlayingState = RUNNING;

if (mSeekTime < 0) {

mStartTime = frameTime;

} else {

mStartTime = frameTime - mSeekTime;

// Now that we're playing, reset the seek time

mSeekTime = -1;

}

}

if (mPaused) {

if (mPauseTime < 0) {

mPauseTime = frameTime;

}

return false;

} else if (mResumed) {

mResumed = false;

if (mPauseTime > 0) {

// Offset by the duration that the animation was paused

mStartTime += (frameTime - mPauseTime);

}

}

// The frame time might be before the start time during the first frame of

// an animation. The "current time" must always be on or after the start

// time to avoid animating frames at negative time intervals. In practice, this

// is very rare and only happens when seeking backwards.

final long currentTime = Math.max(frameTime, mStartTime);

return animationFrame(currentTime);

}

注意到上述代码末尾调用了

animationFrame方法,而

animationFrame内部调用了

animateValue,下面看animateValue的代码

void animateValue(float fraction) {

fraction = mInterpolator.getInterpolation(fraction);

mCurrentFraction = fraction;

int numValues = mValues.length;

for (int i = 0; i < numValues; ++i) {

mValues[i].calculateValue(fraction);

}

if (mUpdateListeners != null) {

int numListeners = mUpdateListeners.size();

for (int i = 0; i < numListeners; ++i) {

mUpdateListeners.get(i).onAnimationUpdate(this);

}

}

}

上述代码中的calculateValue方法就是计算每帧动画所对应的属性的值,下面着重看一下到底是在哪里调用属性的get和set方法的,毕竟这个才是我们最关心的。

get方法:在初始化的时候,如果属性的初始值没有提供,则get方法将会被调用。

private void setupValue(Object target, Keyframe kf) {

if (mProperty != null) {

kf.setValue(mProperty.get(target));

}

try {

if (mGetter == null) {

Class targetClass = target.getClass();

setupGetter(targetClass);

if (mGetter == null) {

// Already logged the error - just return to avoid NPE

return;

}

}

kf.setValue(mGetter.invoke(target));

} catch (InvocationTargetException e) {

Log.e("PropertyValuesHolder", e.toString());

} catch (IllegalAccessException e) {

Log.e("PropertyValuesHolder", e.toString());

}

}

set方法:当动画的下一帧到来的时候,PropertyValuesHolder中的setAnimatedValue方法会将新的属性值设置给对象,调用其set方法

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());

}

}

}

总结

我觉得这篇源码分析写的逻辑有点混乱,希望不要给大家带来误导。从源码上来说,属性动画的源码逻辑层次有点跳跃,不过没关系,大家只要了解属性动画的工作原理就好,源码的作用在于让我们发现其工作原理的确如此。到此为止,Android动画系列已经全部完成,十分感谢大家阅读,希望能给大家带来一点帮助!

Android属性动画赏析,Android源码分析—属性动画的工作原理相关推荐

  1. Android源码分析—属性动画的工作原理

    转载请注明出处: http://blog.csdn.net/singwhatiwanna/article/details/17853275 前言 本文为Android动画系列的最后一篇文章,通过对源码 ...

  2. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  3. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  4. Android shortcut的使用及源码分析

    Android shortcut的使用及源码分析 最近遇到了一个切换国家码后部分应用的shortcut未更新的问题,就学习了shortcut的相关知识,在这里分享一下我了解的知识,希望能对大家有帮助. ...

  5. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

  6. Spring IOC 容器源码分析 - 余下的初始化工作

    1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...

  7. CoreCLR源码探索(八) JIT的工作原理(详解篇)

    在上一篇 我们对CoreCLR中的JIT有了一个基础的了解,这一篇我们将更详细分析JIT的实现. JIT的实现代码主要在https://github.com/dotnet/coreclr/tree/m ...

  8. Android之vold进程启动源码分析

    1.Vold (Volume Daemon)介绍 vold进程接收来自内核的外部设备消息,用于管理和控制Android平台外部存储设备,包括SD插拨.挂载.卸载.格式化等:当外部设备发生变化时,内核通 ...

  9. Android之rild进程启动源码分析

    Android 电话系统框架介绍 在android系统中rild运行在AP上,AP上的应用通过rild发送AT指令给BP,BP接收到信息后又通过rild传送给AP.AP与BP之间有两种通信方式: 1. ...

最新文章

  1. Pandownload惊喜复活!下载速度惊人!
  2. C# 多线程參数传递
  3. Ruby一些小case总结
  4. 开始学习吧,《算法》:动态连通性练习
  5. .Net Discovery系列之十一-深入理解平台机制与性能影响 (中)
  6. IntelliJ IDEA 显示行号方法
  7. FTP服务器和客户端源代码编写问题(ftp server client source)
  8. 从零搭建angular环境和初项目
  9. hihoCoder-1097-最小生成树一·Prim算法 (最小生成树)
  10. codeforce Gym 101102A Coins (01背包变形)
  11. 30 道 MySQL 面试题全放送!
  12. [ML学习笔记] 回归分析(Regression Analysis)
  13. 剑指offer-序列化二叉树
  14. 数据库访问优化法则详解之利用更多的资源
  15. PIM SM报文抓包和总结
  16. 测试opencl软件,我该如何测试OpenCL的可兼容性?
  17. python可以引流吗_你都用 Python 来做什么?
  18. 《小米商城》--购物车单条数据删除、购物车数量修改、清空购物车、查看地址功能、添加地址
  19. android之资源颜色汇总
  20. wps怎么免费导出简历_求职简历怎么写 个人简历怎么写 简历怎么制作

热门文章

  1. 古希腊的五大数学巨匠
  2. sqlserver2008未将对象引用设置到对象的实例_JVM | Java对象的创建、存储和访问详解...
  3. 如何实现一平台多系统_自动化设备数据采集系统如何实现
  4. 接二手java项目需要什么资料_师妹问双非本科如何进BAT,我告诉她Java后端路线...
  5. 计算机大学一个专业几个班,2016年兰州大学计算机类(含基地班、计算机科学与技术等4个专业)专业最低分是多少?...
  6. vue组件在ios不渲染_VueJS:点击后渲染新组件
  7. OpenShift 4 - 部署运行 MinIO 对象存储
  8. OpenShift 4 Tekton (2) - OpenShift Pipeline入门-用Pipeline部署应用
  9. JavaScript PopUp生成器
  10. 构建Docker直觉