Android动画-帧动画、补间动画、属性动画
帧动画(Frame Animation)
定义: 帧动画就是按照顺序播放一帧一帧的照片达到动画的效果。
我们可以看一下实现过程:在drawable目录下新建frame_list.xml
<?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/frame_animation_1"
android:duration="300" />
<item
android:drawable="@drawable/frame_animation_2"
android:duration="300" />
<item
android:drawable="@drawable/frame_animation_3"
android:duration="300" />
</animation-list>
具体代码中使用如下:
@BindView(R.id.image_container)
ImageView imageContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animation);
ButterKnife.bind(this);
}
@OnClick(R.id.frame_animation_start)
public void onStartFrameAnimation() {
imageContainer.setImageResource(R.drawable.frame_list);
AnimationDrawable animationDrawable = (AnimationDrawable)imageContainer.getDrawable();
animationDrawable.start();
}
@OnClick(R.id.frame_animation_end)
public void onEndFrameAnimation() {
AnimationDrawable animationDrawable = (AnimationDrawable)imageContainer.getDrawable();
animationDrawable.stop();
}
- Activity中使用imageContainer作为播放的容器
- (AnimationDrawable)imageContainer.getDrawable() 能获取到帧动画的对象进行播放/停止操作
备注:
帧动画使用简单方便,但是注意在比较复杂的时候图片比较多的时候容易发生oom,因此尽量避免使用尺寸较大的图片。
补间动画(Tween Animation)
定义:与前面写了帧动画不同,帧动画是通过连续播放图片来模拟动画效果,而补间动画开发者只需指定动画开始,以及动画结束"关键帧",
而动画变化的"中间帧"则由系统计算并补齐!它只是去改变了视觉效果,并不会改变控件本身的属性。
补间动画包括基本的平移动画,缩放动画,旋转动画和透明度动画,我们也可以将这些基本动画进行组合在一起。
android支持的补间动画类型
AlphaAnimation:透明度渐变效果,创建时许指定开始以及结束透明度,还有动画的持续 时间,透明度的变化范围(0,1),0是完全透明,1是完全不透明;对应<alpha/>标签!
ScaleAnimation:缩放渐变效果,创建时需指定开始以及结束的缩放比,以及缩放参考点, 还有动画的持续时间;对应<scale/>标签!
TranslateAnimation:位移渐变效果,创建时指定起始以及结束位置,并指定动画的持续 时间即可;对应<translate/>标签!
RotateAnimation:旋转渐变效果,创建时指定动画起始以及结束的旋转角度,以及动画 持续时间和旋转的轴心;对应<rotate/>标签
AnimationSet:组合渐变,就是前面多种渐变的组合,对应<set/>标签
每种动画都可以通过两种方式去实现,代码实现和xml文件实现,个人肯定倾向于代码去做。
xml文件实现
xml文件如下:
translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:startOffset ="1000"
android:fillAfter = "false"
android:repeatMode= "reverse"
android:repeatCount = "infinite"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXDelta="0"
android:toXDelta="500"
android:fromYDelta="0"
android:toYDelta="0">
<!--duration:动画时长
startOffset:动画延迟开始时间
fillAfter:动画结束后是否停留在结束状态
repeatMode:重复播放动画模式restart代表正序重放,reverse代表倒序回放
repeatCount:重放次数(+1),为infinite时无限重复
interpolator:插值器:选择动画变化的模式,有匀速,加速,减速,先加速在减速。。。
以上是所有补间动画共有的属性,以下是平移动画特有的属性
fromXDelta:平移动画在水平方向x初始值
toXDelta:平移动画在水平方向x结束值
fromYDelta:平移动画在竖直方向y初始值
toYDelta:平移动画在竖直方向y结束值-->
</translate>
Activity中实现如下:
@OnClick(R.id.transfer_animation_xml)
public void onTransferXML() {
Animation animation = AnimationUtils.loadAnimation(this, R.anim.animation_translate);
mImageContainer.startAnimation(animation);
}
代码实现
通过代码写一个补件动画也是很简单的:
@OnClick(R.id.transfer_animation_java)
public void onTransferJava() {
Animation transferAnimation = new TranslateAnimation(0, 500, 0, 0);
transferAnimation.setRepeatMode(Animation.RESTART);
transferAnimation.setDuration(3000);
transferAnimation.setRepeatCount(3);
mImageContainer.startAnimation(transferAnimation);
}
动画结束后 组件的位置:
不改变动画的属性,所以最后位置也不会改变。
是否可以组件:
不同的补间动画可以重叠在一起:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="1000"
android:fromAlpha="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toAlpha="1" />
<translate
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:startOffset="3000"
android:toXDelta="500"
android:toYDelta="0" />
<rotate
android:duration="3000"
android:fromDegrees="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:repeatMode="restart"
android:toDegrees="360" />
<scale
android:duration="3000"
android:fromXScale="1"
android:fromYScale="1"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="4000"
android:toXScale="0.5"
android:toYScale="0.5" />
</set>
备注: 以上所有的动画都可以设置listener监听其执行过程
属性动画(Property Animator)
定义:通过控制view的属性来实现动画
1、为什么引入 Property Animator(属性动画)
我提出一个假设:请问大家,如何利用补间动画来将一个控件的背景色在一分钟内从绿色变为红色?这个效果想必没办法仅仅通过改变控件的渐入渐出、移动、旋转和缩放来实现吧,而这个效果是可以通过 Property Animator 完美实现的 这就是第一个原因:Property Animator 能实现补间动画无法实现的功能 大家都知道,补间动画和逐帧动画统称为 View Animation,也就是说这两个动画只能对派生自 View 的控件实例起作用;而 Property Animator 则不同,从名字中可以看出属性动画,应该是作用于控件属性的!正因为属性动画能够只针对控件的某一个属性来做动画,所以也就造就了他能单独改变控件的某一个属性的值!比如颜色!这就是 Property Animator 能实现补间动画无法实现的功能的最重要原因。 我们得到了第二点不同:View Animation 仅能对指定的控件做动画,而 Property Animator 是通过改变控件某一属性值来做动画的。 假设我们将一个按钮从左上角利用补间动画将其移动到右下角,在移动过程中和移动后,这个按钮都是不会响应点击事件的。这是为什么呢?因为补间动画仅仅转变的是控件的显示位置而已,并没有改变控件本身的值。View Animation 的动画实现是通过其 Parent View 实现的,在 View 被 drawn 时 Parents View 改变它的绘制参数,这样虽然 View 的大小或旋转角度等改变了,但 View 的实际属性没变,所以有效区域还是应用动画之前的区域;我们看到的效果仅仅是系统作用在按钮上的显示效果,利用动画把按钮从原来的位置移到了右下角,但按钮内部的任何值是没有变化的,所以按钮所捕捉的点击区域仍是原来的点击区域。(下面会举例来说明这个问题) 这就得到了第三点不同:补间动画虽能对控件做动画,但并没有改变控件内部的属性值。而 Property Animator 则是恰恰相反,Property Animator 是通过改变控件内部的属性值来达到动画效果的
ValueAnimator
简单的实现,实际场景中可以在listener中去更新view的属性。 下面的代码是通过改变属性让view向右下方移动400像素
@OnClick(R.id.value_animation)
public void onValueAnimation() {
int left = mImageContainer.getLeft();
int right = mImageContainer.getRight();
int top = mImageContainer.getTop();
int bottom = mImageContainer.getBottom();
Log.d(TAG, "onValueAnimation: left=" + mImageContainer.getLeft() + " right=" + mImageContainer.getRight()
+ " top=" + mImageContainer.getTop() + " bottom=" + mImageContainer.getBottom());
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.setDuration(4000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (Integer) animation.getAnimatedValue();
Log.d(TAG, "onAnimationUpdate: " + value);
mImageContainer.layout(left + value, top +value, right + value, bottom + value);
}
});
animator.start();
}
ObjectAnimator
ObjectAnimator 是对ValueAnimator的封装与扩展,上面的例子开发者还需要关心view的update的过程,ObjectAnimator则是将该过程封装起来对开发者不可见。
ValueAnimator 有个缺点,就是只能对数值对动画计算。我们要想对哪个控件操作,需要监听动画过程,在监听中对控件操作。这样使用起来相比补间动画而言就相对比较麻烦。 为了能让动画直接与对应控件相关联,以使我们从监听动画过程中解放出来,谷歌的开发人员在 ValueAnimator 的基础上,又派生了一个类 ObjectAnimator; 由于 ObjectAnimator 是派生自 ValueAnimator 的,所以 ValueAnimator 中所能使用的方法,在 ObjectAnimator 中都可以正常使用。 但 ObjectAnimator 也重写了几个方法,比如 ofInt(),ofFloat()等。我们先看看利用 ObjectAnimator 重写的 ofFloat 方法如何实现一个动画:(改变透明度)
改变透明度的属性动画
@OnClick(R.id.object_animation_simple)
public void onObjectAnimationSimple() {
ObjectAnimator animator = ObjectAnimator.ofFloat(mPointView,"alpha",1,0,1);
animator.setDuration(2000);
animator.start();
}
通过set函数改变自定义view属性
public class MyPointView extends View {
private int mRadius;
public MyPointView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(300,300, mRadius, paint);
super.onDraw(canvas);
}
public int getPointRadius(){
return 50;
}
public void setPointRadius(int radius){
mRadius = radius;
invalidate();
}
}
// activity中测试代码
@OnClick(R.id.object_animation_selfview)
public void onObjectAnimationSelfView() {
ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);
animator.setDuration(2000);
animator.start();
}
动画集合
AnimationSet
// 通过AnimationSet播呼吸的View效果,AnimationSet里面的所有动画会同时启动,setStartOffset 可以控制延迟播放
Animation animation1 = new AlphaAnimation(1, 1);
animation1.setDuration(5000);
animation1.setStartOffset(5000);
animation1.setFillEnabled(false);
animation1.setFillBefore(false);
animation1.setFillAfter(false);
animation1.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Log.d("lsfzlj1234", "onAnimationStart | animation1");
}
@Override
public void onAnimationEnd(Animation animation) {
Log.d("lsfzlj1234", "onAnimationEnd | animation1");
}
@Override
public void onAnimationRepeat(Animation animation) {
Log.d("lsfzlj1234", "onAnimationRepeat | animation1");
}
});
Animation animation2 = new AlphaAnimation(1, 0);
animation2.setDuration(5000);
animation2.setStartOffset(10000);
animation2.setFillEnabled(false);
animation2.setFillBefore(false);
animation2.setFillAfter(false);
animation2.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Log.d("lsfzlj1234", "onAnimationStart | animation2");
}
@Override
public void onAnimationEnd(Animation animation) {
Log.d("lsfzlj1234", "onAnimationEnd | animation2");
}
@Override
public void onAnimationRepeat(Animation animation) {
Log.d("lsfzlj1234", "onAnimationRepeat | animation2");
}
});
Animation animation3 = new AlphaAnimation(0, 0);
animation3.setDuration(5000);
animation3.setStartOffset(16000);
animation3.setFillEnabled(false);
animation3.setFillBefore(false);
animation3.setFillAfter(false);
animation3.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
Log.d("lsfzlj1234", "onAnimationStart | animation3");
}
@Override
public void onAnimationEnd(Animation animation) {
Log.d("lsfzlj1234", "onAnimationEnd | animation3");
}
@Override
public void onAnimationRepeat(Animation animation) {
Log.d("lsfzlj1234", "onAnimationRepeat | animation3");
}
});
AnimationSet animationSet = new AnimationSet(false);
animationSet.addAnimation(animation);
animationSet.addAnimation(animation1);
animationSet.addAnimation(animation2);
//animationSet.addAnimation(animation3);
view.setAnimation(animationSet);
animationSet.startNow();
备注: AnimationSet并不支持循环播放
AnimatorSet
private void initAnim() {
View view = findViewById(R.id.Anim_test);
final ObjectAnimator animator0 = ObjectAnimator.ofFloat(view, "alpha",0,1);
animator0.setDuration(1000);
final ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "alpha",1,1);
animator1.setDuration(10000);
final ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "alpha",1,0);
animator2.setDuration(1000);
final ObjectAnimator animator3 = ObjectAnimator.ofFloat(view, "alpha",0,0);
animator3.setDuration(10000);
animator3.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
// 设置循环播放
startAnim(animator0, animator1, animator2, animator3);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
);
startAnim(animator0, animator1, animator2, animator3);
}
private void startAnim(ObjectAnimator animator0, ObjectAnimator animator1, ObjectAnimator animator2, ObjectAnimator animator3) {
AnimatorSet animatorSet = new AnimatorSet();
// 设置顺序播放
animatorSet.playSequentially(animator0, animator1, animator2, animator3);
animatorSet.start();
}
性能对比
代码地址: https://github.com/lsfzlj/AndroidTestNeil/tree/develop
参考:
ValueAnimator 基本使用
Android动画之帧动画,补间动画和属性动画
https://wiki.jikexueyuan.com/project/android-animation/7.html
梦想不是浮躁,而是沉淀和积累