您的位置:首页 > 移动开发 > Android开发

android三种动画实现原理及使用

2017-02-08 23:18 453 查看
android动画目前分为三种:

Tween Animation View动画,是通过对场景里的对象不断做图像变换(平移、缩放、旋转、透明度)从而产生动画效果,她是一种渐进式动画,并且View动画支持自定义。

Drawable Animation 帧动画,通过顺序播放一系列图像从而产生动画效果,可以理解为图片切换动画。图片过多时容易造成OOM。

Property Animation 属性动画,这也是在android3.0之后引进的动画,在手机的版本上是android4.0就可以使用这个动画,通过动态的改变对象的属性从而达到动画效果。

Tween Animation

View动画的四种变换效果对应Animation的四个子类:TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation,这些动画可以通过XML来定义,也可以通过代码动态定义。

TranslateAnimation

view在水平方向和垂直方向进行移动动画效果

public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)


fromXDelta和fromYDelta分别表示在X和Y轴上面的起始坐标,(0,0)这个表示的就是当前view的坐标,toXDelta和toYDelta分别表示最终目标,如果只是X轴移动或者Y轴移动,那么可以把对应不移动的坐标设置为0。

RotateAnimation

旋转动画效果

public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue,int pivotYType, float pivotYValue)


其中的第3、5个参数有几种情况可选:Animation.RELATIVE_TO_PARENT,相对于父控件,1f表示整个父控件的宽度或者是高度,0.5f表示父控件的高度或者宽度的一半,Animation.RELATIVE_TO_SELF,相对于自身控件,Animation.ABSOLUTE:这个表示的是绝对坐标。

前面两个参数是旋转的角度,后面四个参数用来定义旋转的圆心。

ScaleAnimation

进行放大缩小的动画效果

public ScaleAnimation(float fromX, float toX, float fromY, float toY,
int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)


与旋转动画一样,缩放动画也可以设置缩放的中心点,前面四个参数设置起始缩放比例和终止缩放比例。fromX:起始X坐标上的伸缩尺寸,toX:结束X坐标上的伸缩尺寸,fromY:起始Y坐标上的伸缩尺寸,toY:结束Y坐标上的伸缩尺寸,关于这个伸缩尺寸,0表示的就是看不见,1表示原始大小,依此类推,1.5表示的就是1.5倍

AlphaAniamtion

透明度动画效果

public AlphaAnimation(float fromAlpha, float toAlpha)


fromAlpha表示的是动画初始时的透明度,toAlpha表示的是动画结束时的透明度,这个取值范围是0~1,0表示的是完全透明,1表示的是完全不透明

动画公共方法

setDuration(long durationMillis)

这个表示的是设置动画的显示时间,就是这个动画从初始状态到结束状态所需要的时间,durationMillis参数为动画显示时间的长短,单位是毫秒。

setStartOffset(long startOffset)

这个表示的是动画的开始时间,startOffset表示就是动画的开始时间,单位毫秒(有时候我们在startAnimation之后可能不希望立即取执行这个动画,需要等待一会再进行动画就可以使用这个)

setFillAfter(boolean fillAfter)

这个表示的动画结束之后是否保留结束的位置,这个值默认是flase表示动画结束之后不保留动画的位置,就是说在我们进行动画效果结束之后又会自动恢复到原始的状态,true表示就是保留动画结束时的状态,这个值一般都是要设置为true的

startAnimation(Animation animation)

这个是在view对象上使用的,表示开始进行动画,参数就是我们上面说的那四种(注意上面的四种类型都是继承于Animation的),比如我现在有一个ImageView对象image,那么我现在要开始动画时就是用imageView.startAnimation(rotateAnimation);就可以进行动画了,

setInterpolator(Interpolator i)

这个表示的设置动画的变化速度,这里android提供很多类型的Interpolator类型的变化器:

setInterpolator(new AccelerateInterpolator(float factor):这个AccelerateInterpolator表示的是加速,factor参数可以设置为加速的倍数,数值越大动画的速度越快,当然也可以是默认的new AccelerateInterpolator()默认这个参数为1。

还有其他的几种变速器:

AccelerateDecelerateInterpolator()表示先加速后减速

DecelerateInterpolator(float factor) 表示减速

CycleInterpolator()表示速率呈正弦曲线变化

LinearInterpolator ( ) 表示匀速

OvershootInterpolator()超越,最后超出目的值然后缓慢改变到目的值

BounceInterpolator()跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100

AnticipateOvershootInterpolator 反向加超越,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值

AnticipateInterpolator 反向 ,先向相反方向改变一段再加速播放

-

下面来探究下AccelerateInterpolator 的源码

/**
* An interpolator where the rate of change starts out slowly and
* and then accelerates.
*
*/
@HasNativeInterpolator
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
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) {
this(context.getResources(), context.getTheme(), attrs);
}

/** @hide */
public AccelerateInterpolator(Resources res, Theme theme, AttributeSet attrs) {
TypedArray a;
if (theme != null) {
a = theme.obtainStyledAttributes(attrs, R.styleable.AccelerateInterpolator, 0, 0);
} else {
a = res.obtainAttributes(attrs, R.styleable.AccelerateInterpolator);
}

mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
mDoubleFactor = 2 * mFactor;
setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}

public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}

/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateInterpolator(mFactor);
}
}


上述源码除了构造函数外,重要的函数就是:

public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}


跟踪一下这个方法调用是在Animation.java类中的getTransformation()方法

/**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
* otherwise.
*
* @param currentTime Where we are in the animation. This is wall clock time.
* @param outTransformation A transformation object that is provided by the
*        caller and will be filled in by the animation.
* @return True if the animation is still running
*/
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}

final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}

final boolean expired = normalizedTime >= 1.0f || isCanceled();
mMore = !expired;

if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (NoImagePreloadHolder.USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}

if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}

final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}

if (expired) {
if (mRepeatCount == mRepeated || isCanceled()) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}

if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}

mStartTime = -1;
mMore = true;

fireAnimationRepeat();
}
}

if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}

return mMore;
}


上面就是getTransformation()方法,在这个方法中也看到之前设置的getStartOffset()以及mDuration等等

在上面发现这么一句话

final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);


其中的参数:

normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);


按照上面的代码我们也可以自定义我们符合我们自己的Interpolator,定义方法就是写一个类继承于Interpolator接口,并且去实现getInterpolation()方法,在该方法里面做相应的运算。

自定义Tween Animation

自定义View动画只需要继承Animation这个抽象类,然后重写它的initialize和applyTranformation方法,在initialize方法中做一些初始化工作,,在applyTransformation中进行相应的矩阵变换即可,很多时候采用Camera来简化矩阵变换的过程。

Drawable Animation

帧动画是顺序播放一组预先定义好的图片,类似于电影播放,不同于View动画,系统提供了另外一个类AnimationDrawable来使用帧动画,首先通过XML来定义一个AnimationgDrawable:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android = "http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/home1" android:duration="500" />
<item android:drawable="@drawable/home2" android:duration="500" />
<item android:drawable="@drawable/home_press" android:duration="500" />
</animation-list>


然后用ImageView来作为动画的载体:

ImageView iv_frame = (ImageView) findViewById(R.id.iv_frame_animation);
iv_frame.setBackgroundResource(R.drawable.frame_animation);
AnimationDrawable drawable = (AnimationDrawable) iv_frame.getBackground();
drawable.start();


便可实现帧动画了,但帧动画比较容易引起OOM,所以在使用帧动画时,应尽量避免使用尺寸较大的图片。

Property Animation

属性动画和View动画不同,它对作用对象进行了扩展,属性动画可以对任何对象做动画,动画的效果也也得到了加强,可以实现更加绚丽的动画效果。

原理:

属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递 给set方法的值都不一样,在动画结束时传递的就是最终值。

所以我们对object的属性“zoky”做动画,要使动画生效,必须满足两个条件:

1. objcet必须提供setZoky的方法,还有getZoky方法(若无初始值)

2. object的setZoky对属性Zoky所做的改变必须通过某种方法反映出来,比如带来UI改变等。

当目标对象没有get和set属性的方法时,有三种解决方法:

1. 给目标对象加上get和set方法,如果有权限的话。

2. 用一个类用组合的方式来包装原始对象,然后在类里面实现get和set方法

private static class  ViewWrapper{
private View mTarget;
public ViewWrapper(View target){
mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}


3.使用ValueAnimator,监听动画过程,在ValueAnimationUpdate方法中提取fraction(当前进度占整个动画过程的比例),转换为对象的变化值,然后赋给目标对象,实现属性的改变。

ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (int) animation.getAnimatedValue();
float fraction = animation.getAnimatedFraction();
targetView.getLayoutParams().width = mEvaluator.evaluate(fraction,start,end);
tragetView.requestLayout();
}
});


相关的类与概念

Time interpolation:时间差值,定义动画的变化率,更View动画的LinearInterpolator、AccelerateDecelerateInterpolator等类似。

Animator sets:动画集合,你可以定义一组动画,一起执行或者按照一定的顺序执行。

ObjectAnimator 动画的执行类

ValueAnimator 动画执行类

AnimatorInflater 加载属性动画的XML文件

TimeInterpolator 时间插值器

TypeEvaluator 类型估值,主要用于设置动画操作
fbe3
属性的值。

我们也可以在XML中定义属性动画,这样可以更加直观的体现动画的关系:在XML中,标签对应AnimatorSet,对应ValueAnimator,而则对应ObjectAnimator。标签的android:ordering属性有两个可选值,“together”和“sequentially”,前一个表示动画集合中的子动画同时播放,后一个表示子动画按照前后顺序依次播放,andorid:ordering默认属性是“together”.

- android:propertyName - 表示属性动画的作用对象的属性的名称;

- android:duration - 表示动画的时长;

- android: valueFrom - 表示属性的起始值

- android:valueTo - 表示属性的结束值

- android: startOffset - 表示动画延迟播放的时间

- android : repeatCount - 表示动画的重复次数

- android : repeatMode - 表示动画的重复模式,有两个选项,“restart”和“reverse”,分别表示连续重复和逆向重复,逆向重复就是第一次播放完之后,第二次会倒着播放。第三次重头开始。

- android: value Type - 表示android: propertyName 所指定属性的类型,有“intType”和“floatType” 两个可选项。

<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:duration="900"
android:propertyName="scaleX"
android:valueTo="2.0"
android:valueType="floatType"/>
<objectAnimator
android:duration="900"
android:propertyName="scaleY"
android:valueTo="2.0"
android:valueType="floatType"/>
<objectAnimator
android:duration="900"
android:propertyName="translationX"
android:valueTo="400"
android:valueType="floatType"/>
</set>


在需要调用时,只需:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.property_animator);
set.setTarget(tv_animatior);
set.start();


即可

属性动画的监听器

属性动画主要有两个监听接口:AnimatorUpdateListerer和AnimatorListener。

public static interface AnimatorListener {
/**
* <p>Notifies the start of the animation.</p>
*
* @param animation The started animation.
*/
void onAnimationStart(Animator animation);

/**
* <p>Notifies the end of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* @param animation The animation which reached its end.
*/
void onAnimationEnd(Animator animation);

/**
* <p>Notifies the cancellation of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* @param animation The animation which was canceled.
*/
void onAnimationCancel(Animator animation);

/**
* <p>Notifies the repetition of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationRepeat(Animator animation);
}


它监听动画的开始、结束、取消和重复播放,同时,系统还提供了AnimatorListenerAdapter适配器类,我们就可有选择的实现上面的4个方法了。

public static interface AnimatorUpdateListener {
/**
* <p>Notifies the occurrence of another frame of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationUpdate(ValueAnimator animation);

}


每播放一帧,onAnimationUpdate就会被调用一次。

PropertyAnimation原理

属性动画的启动时从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(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();
}


上述代码首先依次判断了当前动画、等待的动画、延迟的动画中是否有和当前动画相同的动画,若有就把相同的动画取消掉,,之后调用父类Valu的start方法。

private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mPlayingBackwards = playBackwards;
if (playBackwards && mSeekFraction != -1) {
if (mSeekFraction == 0 && mCurrentIteration == 0) {
// special case: reversing from seek-to-0 should act as if not seeked at all
mSeekFraction = 0;
} else if (mRepeatCount == INFINITE) {
mSeekFraction = 1 - (mSeekFraction % 1);
} else {
mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
}
mCurrentIteration = (int) mSeekFraction;
mSeekFraction = mSeekFraction % 1;
}
if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
(mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
// if we were seeked to some other iteration in a reversing animator,
// figure out the correct direction to start playing based on the iteration
if (playBackwards) {
mPlayingBackwards = (mCurrentIteration % 2) == 0;
} else {
mPlayingBackwards = (mCurrentIteration % 2) != 0;
}
}
int prevPlayingState = mPlayingState;
mPlayingState = STOPPED;
mStarted = true;
mStartedDelay = false;
mPaused = false;
updateScaledDuration(); // in case the scale factor has changed since creation time
AnimationHandler animationHandler = getOrCreateAnimationHandler();
animationHandler.mPendingAnimations.add(this);
if (mStartDelay == 0) {
// This sets the initial value of the animation, prior to actually starting it running
if (prevPlayingState != SEEKED) {
setCurrentPlayTime(0);
}
mPlayingState = STOPPED;
mRunning = true;
notifyStartListeners();
}
animationHandler.start();
}


最后调用了animationHandler的start方法,是一个Runnable方法,然后与JNI层交互,之后回调doAnimationFrame方法

final boolean doAnimationFrame(long frameTime) {
if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
if (mSeekFraction < 0) {
mStartTime = frameTime;
} else {
long seekTime = (long) (mDuration * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
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);
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
}
// 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方法,然后该方法内部调用animateValue方法,

/**
* This method is called with the elapsed fraction of the animation during every
* animation frame. This function turns the elapsed fraction into an interpolated fraction
* and then into an animated value (from the evaluator. The function is called mostly during
* animation updates, but it is also called when the <code>end()</code>
* function is called, to set the final value on the property.
*
* <p>Overrides of this method must call the superclass to perform the calculation
* of the animated value.</p>
*
* @param fraction The elapsed fraction of the animation.
*/
@CallSuper
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方法将会被调用,如PropertyValuesHolder中的setupValue方法,发现get方法是通过反射调用的方法实现的。

private void setupValue(Object target, Keyframe kf) {
if (mProperty != null) {
Object value = convertBack(mProperty.get(target));
kf.setValue(value);
}
try {
if (mGetter == null) {
Class targetClass = target.getClass();
setupGetter(targetClass);
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
Object value = convertBack(mGetter.invoke(target));
kf.setValue(value);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}


同理,set方法也是在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());
}
}
}


参考资料:Android开发艺术探索、
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息