一、概述

上篇博客介绍了View动画的简单使用和基本工作原理原理,这篇来学习下属性动画。和View动画不同的是,属性动画不再简单的使用平移、旋转、缩放、透明度这4种变换,代替它们的是ValueAnimator、ObjectAnimator等概念。

二、运行截图

三、TimeInterpolator和TypeEvaluator

在真正学习属性动画之前,我们需要理解TimeInterpolator和TypeEvaluator这两个概念。

1.TimeInterpolator

中文翻译为时间插值器。它的作用是根据当前时间流逝的百分比来计算当前某个属性改变的百分比。查看结构,发现只有一个方法。

float getInterpolation(float input);

当然,它是一个接口,我们使用更多的是它的实现类。常用的实现类有LinearInterpolator、AccelerateInterpolator、DecelerateInterpolator等等。下面就简单分析下LinearInterpolator、AccelerateInterpolator两个插值器

查看LinearInterpolator源码

public class LinearInterpolator implements Interpolator {public LinearInterpolator() {}public LinearInterpolator(Context context, AttributeSet attrs) {}public float getInterpolation(float input) {return input;}
}

源码很简单, 核心方法是getInterpolation(),我们可以将其理解成数学中的函数。

y=x ;

查看AccelerateInterpolator源码

/*** An interpolator where the rate of change starts out slowly and * and then accelerates.**/public class AccelerateInterpolator implements Interpolator {private final float mFactor;private final double mDoubleFactor;public AccelerateInterpolator() {mFactor = 1.0f;mDoubleFactor = 2.0;}/*** Constructor* * @param factor Degree to which the animation should be eased. Seting*        factor to 1.0f produces a y=x^2 parabola. Increasing factor above*        1.0f  exaggerates the ease-in effect (i.e., it starts even*        slower and ends evens faster)*/public AccelerateInterpolator(float factor) {mFactor = factor;mDoubleFactor = 2 * mFactor;}public AccelerateInterpolator(Context context, AttributeSet attrs) {TypedArray a =context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator);mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f);mDoubleFactor = 2 * mFactor;a.recycle();}public float getInterpolation(float input) {if (mFactor == 1.0f) {return input * input;} else {return (float)Math.pow(input, mDoubleFactor);}}}

同样,我们查看getInterpolation()方法,同样,我们可以将其视为数学中的函数y等于x的n次方法。

2.TypeEvaluator

TypeEvaluator中文翻译为类型估算器。和TimeInterpolator类似,它也是一个接口,而我们使用的也是它的实现类。它一共有4个实现类,ArgbEvaluator、FloatEvaluator、IntEvaluator、RectEvaluator。下面就其中一种分析–RectEvaluator。查看源码:

public class IntEvaluator implements TypeEvaluator<Integer> {public Integer evaluate(float fraction, Integer startValue, Integer endValue) {int startInt = startValue;return (int)(startInt + fraction * (endValue - startInt));}
}

由其源码可以知道它主要的作用是根据当前属性改变的百分比来获取改变后的属性值。

TimeInterpolator和TypeEvaluator的配合使用可以帮助我们实现好多复杂的效果

四、属性动画入门

简单做一个属性动画的入门例子,这里我们采用java代码来实现,当然xml也可以实现,比较简单,就不演示了。

//创建集合对象AnimatorSet animatorSet = new AnimatorSet() ;animatorSet.playTogether(ObjectAnimator.ofFloat(btn_attr, "translationX",0, 50),ObjectAnimator.ofFloat(btn_attr, "translationY",0, 50));animatorSet.setDuration(3000);animatorSet.start();

使用animatorset来实现平移的效果。注意,这里的平移可是实现了真正位置上的平移,不像View动画。

五、属性动画的工作原理

首先从Animator的start()方法作为入口来分析,

@Overridepublic void start() {// See if any of the current active/pending animators need to be canceledAnimationHandler 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<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;Log.d("ObjectAnimator", "   Values[" + i + "]: " +pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());}}super.start();}

首先,第一行代码,我们需要知道sAnimationHandler是什么,由于ObjectAnimator是ValueAnimator的子类,从ValueAnimator的源码可以知道它是一个本地线程,主要的作用是不同的线程有不同的AnimatorHandler。如果还是不理解ThreadLocal的概念,请看ThreadLocal的源码分析

protected static ThreadLocal<AnimationHandler> sAnimationHandler =new ThreadLocal<AnimationHandler>();

通过sAnimationHandler获取AnimationHandler对象,我们再来看下AnimationHandler又是什么

protected static class AnimationHandler implements Runnable {// The per-thread list of all active animations/** @hide */protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();// Used in doAnimationFrame() to avoid concurrent modifications of mAnimationsprivate final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();// The per-thread set of animations to be started on the next animation frame/** @hide */protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();/*** Internal per-thread collections used to avoid set collisions as animations start and end* while being processed.* @hide*/protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();private final Choreographer mChoreographer;private boolean mAnimationScheduled;private AnimationHandler() {mChoreographer = Choreographer.getInstance();}/*** Start animating on the next frame.*/public void start() {scheduleAnimation();}private void doAnimationFrame(long frameTime) {// mPendingAnimations holds any animations that have requested to be started// We're going to clear mPendingAnimations, but starting animation may// cause more to be added to the pending list (for example, if one animation// starting triggers another starting). So we loop until mPendingAnimations// is empty.while (mPendingAnimations.size() > 0) {ArrayList<ValueAnimator> pendingCopy =(ArrayList<ValueAnimator>) mPendingAnimations.clone();mPendingAnimations.clear();int count = pendingCopy.size();for (int i = 0; i < count; ++i) {ValueAnimator anim = pendingCopy.get(i);// If the animation has a startDelay, place it on the delayed listif (anim.mStartDelay == 0) {anim.startAnimation(this);} else {mDelayedAnims.add(anim);}}}// Next, process animations currently sitting on the delayed queue, adding// them to the active animations if they are readyint numDelayedAnims = mDelayedAnims.size();for (int i = 0; i < numDelayedAnims; ++i) {ValueAnimator anim = mDelayedAnims.get(i);if (anim.delayedAnimationFrame(frameTime)) {mReadyAnims.add(anim);}}int numReadyAnims = mReadyAnims.size();if (numReadyAnims > 0) {for (int i = 0; i < numReadyAnims; ++i) {ValueAnimator anim = mReadyAnims.get(i);anim.startAnimation(this);anim.mRunning = true;mDelayedAnims.remove(anim);}mReadyAnims.clear();}// Now process all active animations. The return value from animationFrame()// tells the handler whether it should now be endedint numAnims = mAnimations.size();for (int i = 0; i < numAnims; ++i) {mTmpAnimations.add(mAnimations.get(i));}for (int i = 0; i < numAnims; ++i) {ValueAnimator anim = mTmpAnimations.get(i);if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {mEndingAnims.add(anim);}}mTmpAnimations.clear();if (mEndingAnims.size() > 0) {for (int i = 0; i < mEndingAnims.size(); ++i) {mEndingAnims.get(i).endAnimation(this);}mEndingAnims.clear();}// If there are still active or delayed animations, schedule a future call to// onAnimate to process the next frame of the animations.if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {scheduleAnimation();}}// Called by the Choreographer.@Overridepublic void run() {mAnimationScheduled = false;doAnimationFrame(mChoreographer.getFrameTime());}private void scheduleAnimation() {if (!mAnimationScheduled) {mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);mAnimationScheduled = true;}}}

从源码我们可以知道它是一个Runnable对象,里面封装了好多集合,用来存放当前动画、演示动画、等待动画等。再回到star()方法,如果handler不为null,则依次获取这些动画将其取消,最后调用父类的start方法。

@Overridepublic void start() {start(false);}

然后又调用start(false)

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;AnimationHandler animationHandler = getOrCreateAnimationHandler();animationHandler.mPendingAnimations.add(this);if (mStartDelay == 0) {// This sets the initial value of the animation, prior to actually starting it runningsetCurrentPlayTime(0);mPlayingState = STOPPED;mRunning = true;notifyStartListeners();}animationHandler.start();}

从源码可以知道,它是运行在有Looper的线程当中的,首先,根据通过getOrCreateAnimationHandler()方法获取AnimationHandler对象,有则创建并且将其赋值,否则直接获取。然后通过AnimationHandler将动画加到mPendingAnimations延时动画里去。最后调用AnimationHandler的start()方法,即执行AnimationHandler的run()方法

    private static AnimationHandler getOrCreateAnimationHandler() {AnimationHandler handler = sAnimationHandler.get();if (handler == null) {handler = new AnimationHandler();sAnimationHandler.set(handler);}return handler;}
@Overridepublic void run() {mAnimationScheduled = false;doAnimationFrame(mChoreographer.getFrameTime());}

接着,我们看下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 timemSeekTime = -1;}}// 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()方法,

boolean animationFrame(long currentTime) {boolean done = false;switch (mPlayingState) {case RUNNING:case SEEKED:float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;if (fraction >= 1f) {if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {// Time to repeatif (mListeners != null) {int numListeners = mListeners.size();for (int i = 0; i < numListeners; ++i) {mListeners.get(i).onAnimationRepeat(this);}}if (mRepeatMode == REVERSE) {mPlayingBackwards = !mPlayingBackwards;}mCurrentIteration += (int)fraction;fraction = fraction % 1f;mStartTime += mDuration;} else {done = true;fraction = Math.min(fraction, 1.0f);}}if (mPlayingBackwards) {fraction = 1f - fraction;}animateValue(fraction);break;}return done;}

前面还是一些计算,当动画应该结束时返回true,否则返回false,方法内部计算出时间比率fraction,然后调用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);}}}

首先通过插值器获取一个按照一定规律的fraction,默认为AccelerateDecelerateInterpolator

private TimeInterpolator mInterpolator = sDefaultInterpolator;
// The time interpolator to be used if none is set on the animationprivate static final TimeInterpolator sDefaultInterpolator =new AccelerateDecelerateInterpolator();

接着,依次遍历mValues,那么mValues又是什么呢?

/*** The property/value sets being animated.*/PropertyValuesHolder[] mValues;

那么mValues是什么时候设置的呢?还记得我们在使用属性动画的时候调用的ofXxx方法吗?

public static ValueAnimator ofInt(int... values) {ValueAnimator anim = new ValueAnimator();anim.setIntValues(values);return anim;}

首先创建了ValueAnimator对象,然后调用setIntValues()方法

public void setIntValues(int... values) {if (values == null || values.length == 0) {return;}if (mValues == null || mValues.length == 0) {setValues(PropertyValuesHolder.ofInt("", values));} else {PropertyValuesHolder valuesHolder = mValues[0];valuesHolder.setIntValues(values);}// New property/values/target should cause re-initialization prior to startingmInitialized = false;}

接着又调用了setIntValues方法

public void setIntValues(int... values) {mValueType = int.class;mKeyframeSet = KeyframeSet.ofInt(values);}
public static KeyframeSet ofInt(int... values) {int numKeyframes = values.length;IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];if (numKeyframes == 1) {keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);} else {keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);for (int i = 1; i < numKeyframes; ++i) {keyframes[i] =(IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);}}return new IntKeyframeSet(keyframes);}

KeyframeSet的ofInt主要是创建一个IntKeyframe数组,最后将其返回。表示一组Keyframe,而Keyframe从字面可以理解为关键帧。再回到animateValue方法,依次遍历,并调用。该方法主要完成的事为:

  1. A:通过Interpolator计算出动画运行时间的分数。
  2. B:变量ValueAnimator中的mValues[i].calculateValue(fraction)(也就是 PropertyValuesHolder对象数组)计算当前动画的值。
  3. C:调用animation的onAnimationUpdate(…)通知animation更新的消息
void calculateValue(float fraction) {mAnimatedValue = mKeyframeSet.getValue(fraction);}

根据evaluator获取每一帧所对应的属性值。这里主要的是这个PropertyValuesHolder对象,我们可以从这个类的注释中可以看出。PropertyValuesHolder内部维持了一个KeyFrameSet和TypeEvaluator

/*** This class holds information about a property and the values that that property* should take on during an animation. PropertyValuesHolder objects can be used to create* animations with ValueAnimator or ObjectAnimator that operate on several different properties* in parallel.*/public class PropertyValuesHolder implements Cloneable {

主要在动画执行过程中hold属性和值的信息。主要是用来被ObjectAnimator和ValueAnimator来操作不同的属性。那么PropertyValuesHolder又是如何调用set和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 NPEreturn;}}kf.setValue(mGetter.invoke(target));} catch (InvocationTargetException e) {Log.e("PropertyValuesHolder", e.toString());} catch (IllegalAccessException e) {Log.e("PropertyValuesHolder", e.toString());}}
/*** Utility function to get the getter from targetClass*/private void setupGetter(Class targetClass) {mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);}

接着,当动画的下一帧来临的时候,又会调用setAnimatedValue()方法,当然主要还是通过反射。

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

六、小结

这里对属性动画工作流程的几个重要类进行总结

  • ObjectValue:ObjectAnimator我们可以将其理解为外界API接口,ObjectAnimator持有PropertyValuesHolder作为存储关于将要进行动画的具体对象(通常是View类型的控件)的属性和动画期间需要的值。
  • PropertyValueHolder:PropertyValueHolder又使用KeyframeSet来保存animator从开始到结束期间关键帧的值。
  • KeyframeSet:保存每一帧的值。

OK,这篇关于属性动画就介绍到这里了。

源码下载

Android动画完全解析--属性动画相关推荐

  1. Android源码解析(一)动画篇-- Animator属性动画系统

    Android源码解析-动画篇 Android源码解析(一)动画篇-- Animator属性动画系统 Android源码解析(二)动画篇-- ObjectAnimator Android在3.0版本中 ...

  2. android开发笔记之属性动画

    属性动画简单介绍 作用对象:任意 Java 对象 不再局限于 视图View对象 实现的动画效果:可自定义各种动画效果 不再局限于4种基本变换:平移.旋转.缩放 & 透明度 特点 作用对象进行了 ...

  3. Android VectorDrawable 矢量图+属性动画 使用总结

    代码已经同步到GitHub 然后看一下效果图: 前两个图标是让android的组件使用VectorDrawable 后面的是动画效果 后面会详细介绍. 什么是VectorDrawable Vector ...

  4. Android动画之Property属性动画

    2019独角兽企业重金招聘Python工程师标准>>> 为什么引入属性动画? 大家都知道Android常见的动画有tween动画,frame动画.但是随着人们对动画的要求不断提高, ...

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

    前言 本文为Android动画系列的最后一篇文章,通过对源码的分析,能够让大家更深刻地理解属性动画的工作原理,这有助于我们更好地使用属性动画.但是,由于动画的底层实现已经深入到jni层,并且涉及到显示 ...

  6. Android动画框架(二)----属性动画

    转载请注明出处:http://blog.csdn.net/fishle123/article/details/50705928 Android提供三种形式动画:视图动画,帧动画,属性动画.其中属性动画 ...

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

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

  8. Android Studio属性动画,Android开发-RecyclerView-AndroidStudio(六)属性动画(3)AddDuration

    RecyclerView增加数据: MyAdapter.java: package com.iwanghang.recyclerviewdemo; import android.content.Con ...

  9. Flutter 动画全解析(动画四要素、动画组件、隐式动画组件原理等)

    本文通过拆解 Flutter 中动画的实现方式以及原理来介绍动画实现的整个过程. 1. 动画四要素 动画在各个平台的实现原理都基本相同,是在一段时间内一系列连续变化画面的帧构成的.在 Flutter ...

最新文章

  1. CentOS6.9中搭建FTP服务器
  2. 几个常见的网络故障分析
  3. ubuntu18docker下安装MySQL
  4. 计算机玩游戏不能全屏,玩游戏屏幕两边有黑条?教你简单几步轻松解决-win7玩游戏不能全屏...
  5. 两轮平衡机器人送披萨,旋转跳跃!
  6. C#使用Xamarin开发可移植移动应用进阶篇(9.混淆代码,防止反编译)
  7. 2018大数据学习路线从入门到精通
  8. 数据结构之图的存储结构:邻接多重表
  9. kafka可视化客户端工具(Kafka Tool)的使用
  10. Fiddler实现IOS手机抓取https报文
  11. CCCC-GPLT L1-033. 出生年 天梯赛
  12. 容器和泛型 容器重点掌握
  13. 案例应用|如何借助SPC软件实现汽车零配件品质管理
  14. 自定义View: 九宫格图形解锁(Kotlin版)
  15. 机器学习笔记~HDF5 library version mismatched error与ImportError: 'save_model' requires h5py问题解决
  16. JavaWeb学习-案例练习-图书管理前台-9-分页功能实现
  17. 【Android】Broadcast
  18. C#为窗体控件设置透明色问题
  19. mysql数据表关联_MySQL表关联的常用方式有哪几种
  20. zz一个研究生毕业以后的人生规划(转自天涯虚拟社区)

热门文章

  1. visio常用快捷键_Visio快捷键
  2. js获取微信号_前端js可以直接获取到微信用户基本信息吗
  3. 经常打电话的人用什么耳机好?通话质量好的蓝牙耳机推荐
  4. php rsa 模数 指数,密码:使用模数和指数生成RSA私钥
  5. 完美实现苹果轮廓检测opencv-python检测图像轮廓处理
  6. Openvpn 客户端路由配置
  7. 带T和Z的时间字符串转Long型
  8. C#支付宝当面付扫码支付开发,包括demo代码的修改和蚂蚁金服开发平台的配置
  9. 用户登录很重要,实现游戏陪玩app源码注册功能
  10. 开启全面屏体验 | 手势导航 (一)