Android 中有三种动画,分别是补间动画,帧动画,属性动画,优缺点如下:
补间动画用法简单,支持4种动画效果,不用担心内存泄漏,OOM等问题。缺点就是动画效果较少,有时不能满足需求,而且补间动画是对view的影像做动画,并没有真正改变view的状态,原来的view位置大小状态都是保持不变的。
帧动画用法简单,多张图片一帧一帧的播放组成动画。缺点是图片数量过多过大,容易出现OOM。
属性动画优化了补间动画的缺点,可以实现多样式的动画,以及改变view的状态。缺点android3.0以下会有兼容问题,无限循环的动画需要在Activity退出时及时停止,避免出现内存泄漏。
上面简单的介绍了Android中的动画,下面就来介绍以下属性动画的使用
属性动画可以对任何对象做动画,也可以没有对象,同时动画效果也得到加强。属性动画中有ValueAnimator、ObjectAnimator、AnimatorSet等概念。
ObjectAnimator的使用
需求:将Button x坐标在2秒内向右移动100 px,然后右回到原点。
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mButton,"translationX",0f,100f,0f);
objectAnimator.setDuration(2000);
objectAnimator.start();
AnimatorSet的使用
需求1:Button在移动时,同时进行缩小还原,旋转,渐变还原的过程。
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofFloat(btnSet,"translationX",0f,300,0f),
ObjectAnimator.ofFloat(btnSet,"rotationX",0f,360f),
ObjectAnimator.ofFloat(btnSet,"scaleX",1,0.5f,1f),
ObjectAnimator.ofFloat(btnSet,"alpha",1,0.5f,1f)
);
animatorSet.setDuration(4000).start();
从源码得知,AnimatorSet 部分api调用使用了Builder设计模式,如果想设置动画先后执行的顺序,可以先调用play,然后根据需求调用下面的方法。
after(Animator anim)
:将现有动画插入到传入的动画之后执行after(long delay)
:将现有动画延迟指定毫秒后执行before(Animator anim)
: 将现有动画插入到传入的动画之前执行with(Animator anim)
:将现有动画和传入的动画同时执行
//沿x轴放大
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(btnSet, "scaleX", 1f, 2f, 1f);
//沿y轴放大
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(btnSet, "scaleY", 1f, 2f, 1f);
//移动
ObjectAnimator translationXAnimator = ObjectAnimator.ofFloat(btnSet, "translationX", 0f, 200f, 0f);
//透明动画
ObjectAnimator animator = ObjectAnimator.ofFloat(btnSet, "alpha", 1f, 0f, 1f);
AnimatorSet set = new AnimatorSet();
//同时沿X,Y轴放大,且改变透明度,然后移动
set.play(scaleXAnimator).with(scaleYAnimator).with(animator).before(translationXAnimator);
//都设置3s,也可以为每个单独设置
set.setDuration(3000);
set.start();
需求2:动画效果改变Button的宽度,在2秒内宽度先变成0,然后在还原。
ViewWrapper wrapper = new ViewWrapper(btnWarpper);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(wrapper,"Width",btnWarpper.getWidth(),0f,btnWarpper.getWidth());
objectAnimator.setDuration(2000);
objectAnimator.start();
public class ViewWrapper {
public View viewWrapper;
public ViewWrapper(View viewWrapper){
this.viewWrapper = viewWrapper;
}
public void setWidth(float width){
viewWrapper.getLayoutParams().width = (int) width;
viewWrapper.requestLayout();
}
public float getWidth(){
return viewWrapper.getLayoutParams().width;
}
}
需求3:实现一个球自由落体运动
这部分我们需要了解TimeInterpolator 时间插值器和TypeEvaluator估值器,插值器已经有很多实现,一般不需要重写,它的作用是根据时间流逝的百分比计算出属性改变的百分比,从而在估值器中计算出当前时间估算的属性值。
ObjectAnimator objectAnimator = new ObjectAnimator();
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setObjectValues(new PointF(0,0));
objectAnimator.setEvaluator(new TypeEvaluator<PointF>() {
@Override
public PointF evaluate(float v, PointF pointF, PointF t1) {
PointF point = new PointF();
point.x = 200*v*2;
point.y = 0.5f*200*v*2*v*2;
return point;
}
});
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
PointF pointF = (PointF) valueAnimator.getAnimatedValue();
if(y==0){
y = circleView.getY();
}
circleView.setX(pointF.x);
circleView.setY(pointF.y+y);
}
});
objectAnimator.setDuration(2000);
objectAnimator.start();
ValueAnimator的使用
使用valueAnimator实现需求1
ValueAnimator animator = ValueAnimator.ofFloat(0f,300,0f);
animator.setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
btnValueAnimator.setTranslationX((Float) valueAnimator.getAnimatedValue());
}
});
animator.start();
以上是属性动画的一些基本用法。当然属性动画也可以使用xml文件实现,暂时就不介绍了。下面介绍一下属性动画的原理。
属性动画要求作用的对象提供该属性的set、get方法,属性动画根据你传递的该属性的初始值和最终值,以动画效果多次调用set方法,每次传递给set方法的值是不一样的,随着时间的推移越来越接近最终值,最后等于最终值。下面根据源码来解析属性动画,首先找到一个入口,就用ObjectAnimator.start()方法开始。
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));
}
}
super.start();
}
可以看到调用的是super.start(),也就是ValueAnimator的start()方法。
private void start(boolean playBackwards) {
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);
}
}
}
可以看出属性动画必须执行在有looper的线程中,往下执行到了setCurrentFraction(mSeekFraction),然后在往下就是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方法,当动画的每一帧到来的时候通过反射调用对象的set方法设置属性值。