安卓动画分类
- 逐帧动画「Drawable Animation、Frame Animation」
- 补间动画「View Animaiton、Tweened Animation」:补间动画只能修改 View 组件的部分属性,动画效果是绘制出来的,组件的状态并没有改变
- 属性动画「Property Animation」:可以修改 View 组件的任何属性,组件的状态也会改变
Drawable Animation
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:duration="100">
<layer-list>
<item android:drawable="@drawable/login_loading_00" />
<item android:drawable="@drawable/login_loading_10" />
</layer-list>
</item>
<item android:duration="100">
<layer-list>
<item android:drawable="@drawable/login_loading_01" />
<item android:drawable="@drawable/login_loading_11" />
</layer-list>
</item>
</animation-list>
其中,android:oneshot = “true” 表示该动画只播放一次,等于 false 则循环播放。<item/> 标签定义各个帧显示的图片,显示顺序依照 <item/> 定义顺序,<layer-list/> 内包含的图片将层叠起来,在同一帧中一起显示,item 的二级标签支持上面列出的所有 Drawable
private AnimationDrawable loadingAnimation;
loadingAnimation.stop();
loadingAnimation.start();
View Animation
补间动画效果由四个因素决定:初始状态、结束状态、持续时间、Interpolator
关键类:AlphaAnimation,RotateAnimation,ScaleAnimation,TranslateAnimation、AnimationSet。其中,AnimationSet用于控制一组动画的执行顺序
代码实现
TranslateAnimation tAnim = new TranslateAnimation(0, 400, 0, 0);
RotateAnimation rAnima = new RotateAnimation(0, 70);
ScaleAnimation sAnima = new ScaleAnimation(0, 5, 0, 5);
AlphaAnimation aAnima = new AlphaAnimation(1.0f, 0.0f);
tAnim.setDuration(2000);
tAnim.setInterpolator(new AccelerateDecelerateInterpolator());
translation.startAnimation(tAnim);
XML 实现
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<scale
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="0.6"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"//设置fillAfter为true,动画将保持结束的状态
android:duration="1000" />
<set
android:interpolator="@android:anim/accelerate_interpolator"
android:startOffset="1000">
<scale … />
<rotate
android:fromDegrees="0"
android:toDegrees="60"
android:pivotX="50%"
android:pivotY="50%"
android:duration="400" />
</set>
</set>
Animation anim = AnimationUtils.loadAnimation(AnimaXmlActivity.this, R.anim.myanim);
img.startAnimation(anim);
补间动画原理
核心本质是在一定时间内,通过 Matrix 矩阵变换计算刷新区域,不断刷新父视图的过程。在动画播放过程中,如果是硬件加速场景:第一帧刷新父视图和动画视图,后面仅刷新父视图,另外在动画过程中,不会触发动画视图的兄弟视图的 onDraw 方法。如果是软件绘制,则同时刷新父视图和所有子视图
补间动画并没有对 View 的原始坐标进行修改,只是在绘制的时候根据相应的 Animation 进行变换,所以事件响应还是在之前的位置。而属性动画会改变 View 的相应属性,所以响应事件是在改变之后的位置上
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
public void draw(Canvas canvas) {
drawBackground(canvas);
onDraw(canvas);
dispatchDraw(canvas);
onDrawForeground(canvas);
}
protected void dispatchDraw(Canvas canvas) {
final long drawingTime = getDrawingTime();
for (int i = 0; i < childrenCount; i++) {
drawChild(canvas, child, drawingTime);
}
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
if (drawingWithRenderNode) {
// 首次setAnimation时调用view.invalidate,后面直接调用parent.invalidate
renderNode = updateDisplayListIfDirty();
if (!renderNode.hasDisplayList()) {
// Uncommon, but possible.
renderNode = null;
drawingWithRenderNode = false;
}
}
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
}
// 判断是否还需要播放动画,如果继续播放动画,刷新Parent
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, Animation a, boolean scalingRequired) {
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
if (more) {
final RectF region = parent.mInvalidateRegion;
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region, invalidationTransform);
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
parent.invalidate(left, top, left + (int) (region.width() + .5f), top + (int) (region.height() + .5f));
}
}
- 当调用 View.startAnimation 时动画并没有马上就执行,而是通过 invalidate 层层通知到 ViewRootImpl 发起一次遍历 View 树的请求,而这次请求会等到接收到最近一帧到了的信号时才去发起遍历 View 树绘制操作
- 从 DecorView 开始遍历,绘制流程在遍历时会调用到 View.draw 方法,当该方法被调用时,如果 View 有绑定动画,那么会去调用applyLegacyAnimation,这个方法是专门用来处理动画相关逻辑的
- 在 applyLegacyAnimation 这个方法里,如果动画还没有执行过初始化,先调用动画的初始化方法 initialized,同时调用 onAnimationStart 通知动画开始了,然后调用 getTransformation 来根据当前时间计算动画进度,紧接着调用 applyTransformation 并传入动画进度来应用动画
- getTransformation 这个方法有返回值,如果动画还没结束会返回 true,动画已经结束或者被取消了返回 false。所以 applyLegacyAnimation 会根据 getTransformation 的返回值来决定是否通知 ViewRootImpl 再发起一次遍历请求,返回值是 true 表示动画没结束,那么就去通知 ViewRootImpl 再次发起一次遍历请求。然后当下一帧到来时,再从 DecorView 开始遍历 View 树绘制,重复上面的步骤,这样直到动画结束
- 有一点需要注意,动画是在每一帧的绘制流程里被执行,所以动画并不是单独执行的,也就是说,如果这一帧里有一些 View 需要重绘,那么这些工作同样是在这一帧里遍历 View 树的过程中完成的。每一帧只会发起一次 perfromTraversals 操作
Property Animation
通过动画的方式改变对象的属性,动画的执行类: ObjectAnimator、ValueAnimator、AnimatorSet
- AnimatorSet 用于控制一组动画的执行顺序
- AnimatorInflater 用于加载属性动画的XML文件
- TypeEvaluator 估值器
- TimeInterpolator 插值器
- PropertyValuesHolder 实现一个动画更改多个效果
属性动画通过Choreographer.postFrameCallback实现
private void start(boolean playBackwards) {
addAnimationCallback(0);
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
}
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
}
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
ObjectAnimator
1、ObjectAnimator 提供了 ofInt、ofFloat、ofObject,这几个方法都是设置动画作用的元素、作用的属性、动画开始、结束的属性值,当只设置一个属性值的时候,会认为当然对象的属性值为开始(getPropName反射获取),设置的值为终点,如果设置两个,则一个为开始、一个为结束。动画更新的过程中,会不断调用 setPropName 更新元素的属性,所有使用 ObjectAnimator 更新的属性,必须得有 getter(设置属性值的时候)和 setter 方法
ObjectAnimator.ofFloat(view, "rotationX", 0.0F, 360.0F).setDuration(500).start();
2、如果操作的属性方法内部没有调用 View 的重绘,比如 setRotationX,则需要手动调用
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
view.postInvalidate();
view.invalidate();
}
});
3、通过 ObjectAnimator 让 View 既缩小、又淡出(属性scaleX、scaleY、alpha)
public void rotateAnimRun(final View view) {
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "zhy", 1.0F, 0.0F).setDuration(500);
anim.start();
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float cVal = (Float) animation.getAnimatedValue();
view.setAlpha(cVal);
view.setScaleX(cVal);
view.setScaleY(cVal);
}
});
}
4.使用 PropertyValuesHolder 实现一个动画更改多个效果:
public void propertyValuesHolder(View view) {
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY,pvhZ).setDuration(1000).start();
}
ValueAnimator
ValueAnimator 本身并没有在属性上做操作,因此,不需要操作对象的属性一定要有 getter 和 setter 方法,你可以自己根据当前动画的计算值,来操作任何属性
public void verticalRun( View view) {
ValueAnimator animator = ValueAnimator.ofFloat(0, mScreenHeight - mBlueBall.getHeight());
animator.setDuration(1000).start();
animator.setInterpolator(value)
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mBlueBall.setTranslationY((Float) animation.getAnimatedValue());
}
});
}
实现抛物线的效果,水平方向100px/s,垂直方向加速度200px/s*s:
public void paowuxian(View view) {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setObjectValues(new PointF(0, 0));
valueAnimator.setDuration(3000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
//fraction = t / duration
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
PointF point = new PointF();
point.x = 200 * fraction * 3;
point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3);
return point;
}
});
valueAnimator.start();
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF point = (PointF) animation.getAnimatedValue();
mBlueBall.setX(point.x);
mBlueBall.setY(point.y);
}
});
}
监听事件:对于动画,一般都是一些辅助效果,比如我要删除个元素,我可能希望是个淡出的效果,但是最终还是要删掉,并不是你透明度没有了,还占着位置,所以我们需要知道动画如何结束
public void fadeOut(View view) {
ObjectAnimator anim = ObjectAnimator.ofFloat(mBlueBall, "alpha", 0.5f);
anim.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationRepeat(Animator animation) { }
@Override
public void onAnimationEnd(Animator animation) { }
@Override
public void onAnimationCancel(Animator animation) { }
});
anim.start();
}
AnimatorListenerAdapter:有时候,我只要监听某个状态,这么长的代码我不能接受
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
AnimatorSet
public void togetherRun(View view) {
ObjectAnimator anim1 = ObjectAnimator.ofFloat(mBlueBall, "scaleX", 1.0f, 2f);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(mBlueBall, "scaleY", 1.0f, 2f);
ObjectAnimator anim3 = ObjectAnimator.ofFloat(mBlueBall, "x", cx , 0f);
ObjectAnimator anim4 = ObjectAnimator.ofFloat(mBlueBall, "x", cx);
//anim1,anim2,anim3,anim4接着执行
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(2000);
animSet.setInterpolator(new LinearInterpolator());
animSet.playTogether(anim1, anim2);
animSet.start();
//anim1,anim2,anim3同时执行,anim4接着执行
AnimatorSet animSet = new AnimatorSet();
animSet.play(anim1).with(anim2);
animSet.play(anim2).with(anim3);
animSet.play(anim4).after(anim3);
animSet.setDuration(1000);
animSet.start();
}
Xml 文件实现
View Animator 在 anim 文件夹下创建动画。Property Animator 在 animator 文件夹下创建动画
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
//orderring表示播放顺序,还有另一个值:sequentially(表示一个接一个执行)
<objectAnimator
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="0.5">
</objectAnimator>
<objectAnimator
android:duration="1000"
android:propertyName="scaleY"
android:valueFrom="1.0"
android:valueTo="0.5">
</objectAnimator>
</set>
Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scale);
//缩放、反转等默认是中心缩放,和对称轴为反转线,本例子以左上角为中心点
mMv.setPivotX(0);
mMv.setPivotY(0);
mMv.invalidate();
anim.setTarget(mMv);
anim.start();
布局动画(Layout Animations)
LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.APPEARING, null);
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null);
mGridLayout.setLayoutTransition(transition);
//LayoutTransition.APPEARING:当View在ViewGroup中出现时,对此View设置动画
//LayoutTransition.CHANGE_APPEARING:当一个View在ViewGroup中出现时,此View影响其他View的位置,对其他View设置的动画
//LayoutTransition.DISAPPEARING:当一个View在ViewGroup中消失时,对此View设置的动画
//LayoutTransition.CHANGE_DISAPPEARING:当一个View在ViewGroup中消失时,此View影响其他View的位置,对其他View设置的动画
TypeEvaluator 是属性动画特有的,插值器(Interpolator
)决定值的变化规律(匀速、加速blabla),即决定的是变化趋势;而具体变化数值则交给估值器
转场动画:五种实现方式,其中 XML 中定义的动画可以指定 pathInterceptor(贝塞尔插值器)
Android内置了 9 种内置的插值器实现
作用 | 资源ID | 对应的Java类 |
---|---|---|
加速 | @android:anim/accelerate_interpolator | AccelerateInterpolator |
减速 | @android:anim/decelerate_interpolator | DecelerateInterpolator |
匀速 | @android:anim/linear_interpolator | LinearInterpolator |
弹球效果 | @android:anim/bounce_interpolator | BounceInterpolator |
先加速再减速 | @android:anim/accelerate_decelerate_interpolator | AccelerateDecelerateInterpolator |
先退后再加速 | @android:anim/anticipate_interpolator | AnticipateInterpolator |
周期运动 | @android:anim/cycle_interpolator | CycleInterpolator |
快速完成动画,超出再回到结束样式 | @android:anim/overshoot_interpolator | OvershootInterpolator |
先退后再加速,超出终点后再回终点 | @android:anim/anticipate_overshoot_interpolator | AnticipateOvershootInterpolator |
@android:anim/anticipate_overshoot_interpolator |
参考文档