首先明确啥是动画?你可以理解成若干不同图片随着时间不断交替成为焦点的过程,说简单点就是"量子阅读法",你还可以把动画理解成使用某多种手段将一张图片进行各种变换。那么这两种理解方式对应android里的动画就是帧动画和属性动画,当然帧动画和补间动画属于视图动画,所以android动画可以分为两大类:视图动画和属性动画。
View动画操作对象是view,而属性对象操作的是任何对象甚至是无对象。
View动画基本上只支持4中动画效果,也就是最简单的平移、旋转、缩放、透明度。当然如果你不满足这4中基本动画效果,你也可以自定义view效果,基本思路就是继承Animation抽象性类,重写两个方法:initialize和applyTransformation方法,在applyTransformation中进行相关矩阵变换,为了简化matrix变化,使用camera简化变换过程。总体来说自定义View动画不是很难,而且套路比较固定。
当然view动画也不是一无是处,他在viewGroup中控制子view的出场退场效果,他在Activity中实现Activity之间的切换动画。
引用一个大佬对view动画的总结:
View动画还有一个致命的缺陷,就是它只是改变了View的显示效果而已,而不会真正去改变View的属性。什么意思呢?比如说,现在屏幕的左上角有一个按钮,然后我们通过补间动画将它移动到了屏幕的右下角,现在你可以去尝试点击一下这个按钮,点击事件是绝对不会触发的,因为实际上这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。
下面我们重点介绍属性动画,它在实际中使用比较多。
字面上的意思就是动态改变对象的属性值来实现动画效果,比如,让某个对象的宽度在10秒内增大50dp。别看这句话很短,但是实现起来需要考虑一下几个问题:
下面我们带着这几个思考继续开始学习吧
常用的几种属性动画有:ValueAnimator、ObjectAnimator、AnimatorSet类。其中ObjectAnimator继承了ValueAnimator。ValueAnimator是属性动画的核心类
属性动画的运行机制是通过不断地对属性值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。
ValueAnimator使用起来十分简单:
比如我想将一个值从0变到1,时长5000ms,就可以这样写:
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
anim.setDuration(5000);
anim.start();
这里我们可以回答开头提出来的第2点思考,如何控制这个速度,如果我不想他变化很快或者太慢。android提供了一个叫做插值器的东东Interpolator,他用来控制改变属性值得变化速率。常见的有AccelerateDecelerateInterpolator(开始和结束变化慢、中间变化快)、LinearInterpolator(匀速变化)、BounceInterpolator(结束的时候出现反弹变化效果) 。说白了,插值器通过数学函数将一段时间段进行(非)线性流逝,从而实现渐变的动画效果。
anim.setInterpolator(new BounceInterpolator());//反弹插值器
接下来我们可以回答开头提出来的第3点思考,如何感知对象属性变化了?只有感知变化了我才好决定去做变化依赖的操作,比如我要实现一个button的宽度从0变化到100dp,ValueAnimator类不断set这个button的宽度属性,而没有进行其他改变button宽度的行为,所以逻辑时先得监听到属性值发生变化了然后去在ui层面上改变button的宽度。
好在android可以在添加监听器AnimatorUpdateListener实现对属性值的监听:
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取当前width变化到的值
curWidth= (int) anim.getAnimatedValue();
//更新ui
invalidate();
}
});
所以ValueAnimator只需要我们提供起始值、终点值已经自己选取一个插值器(也可以自己定义,android已经做到完全不需要我们自己定义了),最后提供一个自定义的监听器实现属性值改变后的后操作。
相比ValueAnimator,ObjectAnimator可能是我们开发中最常使用到的,因为ValueAnimator提供的ObjectAnimator都能实现,ObjectAnimator可以对任何对象直接做任何操作,这就是他强大之处。
比如我要对一个对象向右平移一个对象宽度:
ObjectAnimator.ofFloat(targetObj,"translationX",targetObj.getWidth()).setDuration(5000).setInterpolator(new BounceInterpolator()).start();
是不是感觉代码十分清爽,ObjectAnimator也不需要像ObjectAnimator需要设置还设置什么属性监听器,他做到了对对象属性值改变、动画时长、插值器设置链式构造。
当然ObjectAnimator也提供了用于监听动画播放过程的监听器AnimatorListener/AnimatorAdaptor,用于对动画开始、结束、取消等一些状态的回调。
下面我们可以回答第2点思考,ObjectAnimator是如何对对象进行属性赋值的?答案是通过调用该对象的getter和setter方法,getter方法可以不用提供,但是如何用户没有提供对象的初始值,那么系统就会使用getter方法找对象的初始值,若没getter方法就会报错。
所以这就可以解释得通为啥button并没有width这个属性值,但还是可以使用动画作用于width上,那是因为他有getWidth和setWidth方法。
我们借用《Android开发艺术探索》中的例子,改变button的宽度:点击button,改变button宽度:
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_button:
changeButton();
break;
}
}
private void changeButton() {
ObjectAnimator.ofInt(bt,"width",500).setDuration(5000).start();
}
运行后点击button并没有反应,那是咋会事?
因为button提供的setWidth方法并不是改变的button宽度的直接方法,所以 ObjectAnimator.ofInt(bt,"width",500).setDuration(5000).start();
显然不对(直接调用setWidth方法)。那么遇到这种摸不着头脑的情况,官方给出了3中方法:
显然大部分时第一种方法并不靠谱,最简单的就是第二种方式,这里给出:
private void changeButton() {
ViewWapper wapper=new ViewWapper(bt);
ObjectAnimator.ofInt(wapper,"width",500).setDuration(5000).start();
}
//定义一个button的包装类
class ViewWapper{
private View mView;
public ViewWapper(View mView){
this.mView=mView;
}
public int getWidth(){
return mView.getLayoutParams().width;
}
public void setWidth(int width){
mView.getLayoutParams().width=width;
mView.requestLayout();
}
}
看懂了ValueAnimator原理,我们也可以用监听器实现:
private void changeButton() {
ValueAnimator animator = ValueAnimator.ofInt(bt.getLayoutParams().width, bt.getLayoutParams().width+500);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
bt.getLayoutParams().width=(int) animation.getAnimatedValue();
bt.requestLayout();
}
});
animator.setDuration(5000);
animator.setInterpolator(new LinearInterpolator());
animator.start();
}
至此,动画如何操作对象属性的流程及原理我们都弄清楚了。
最后一个类,他是提供对ObjectAnimator的集合,实现对目标各种不同的操作方式。
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofInt(obj,"rotationX",0,360),
ObjectAnimator.ofInt(obj,"rotationY",0,360),
ObjectAnimator.ofInt(obj,"translationX",0,100),
ObjectAnimator.ofInt(obj,"translationY",0,100),
...
);
set.setDuration(3000).start();
所有的动画类都是通过.start()方法开始的,我们以ObjectAnimator.ofInt(wapper,"width",500).setDuration(5000).start()
为例,进入ObjectAnimator中的start()方法:
#ObjectAnimator
public void start() {
//将与当前动画相同的动画取消掉
AnimationHandler.getInstance().autoCancelBasedOn(this);
//下面不用管,都是日志操作
if (DBG) {
Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
for (int i = 0; i < mValues.length; ++i) {
PropertyValuesHolder pvh = mValues[i];
Log.d(LOG_TAG, " Values[" + i + "]: " +
pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
pvh.mKeyframes.getValue(1));
}
}
//调用ValueAnimator的start方法
super.start();
}
#ValueAnimator
private void start(boolean playBackwards) {
//这里表示属性动画得运行在looper线程中,否则抛异常
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// 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;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
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);
}
}
}
代码最好调用了setCurrentPlayTime或者是setCurrentFraction方法,区别就是是否立刻进行动画操作还是延时一下,
public void setCurrentFraction(float fraction) {
//初始化
initAnimation();
fraction = clampFraction(fraction);
mStartTimeCommitted = true; // do not allow start time to be compensated for jank
if (isPulsingInternal()) {
long seekTime = (long) (getScaledDuration() * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
// Only modify the start time when the animation is running. Seek fraction will ensure
// non-running animations skip to the correct start time.
mStartTime = currentTime - seekTime;
} else {
// If the animation loop hasn't started, or during start delay, the startTime will be
// adjusted once the delay has passed based on seek fraction.
mSeekFraction = fraction;
}
mOverallFraction = fraction;
final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
animateValue(currentIterationFraction);
}
首先进行初始化,对每一个mValues元素初始化,mValues是一个PropertyValuesHolder对象
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}
在PropertyValuesHolder中
#PropertyValuesHolder
void init() {
if (mEvaluator == null) {
// We already handle int and float automatically, but not their Object
// equivalents
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :
null;
}
if (mEvaluator != null) {
// KeyframeSet knows how to evaluate the common types - only give it a custom
// evaluator if one has been set on this class
mKeyframes.setEvaluator(mEvaluator);
}
}
就是初始化计算器Evaluator,如果是动画参数是Integer就将计算器设置为IntEvaluator,若是float型就设置FloatEvaluator,若都不是就让用户自己去定义计算器。
初始化完毕后,最后落到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);
}
}
}
后面给对象属性赋值的逻辑很乱,但是可以知道,之后通过反射机制来调用对象的getter和setter方法。
通过属性动画控制对象动画十分简洁,并没有特别难的思想在里面,唯一一个就是监听对象属性值得变化而已。