深入理解Android之动画

       Android 里动画从用途上讲,可以分为三类 View Animation(View动画)又称 Tween Animation(补间动画)、Drawable Animation (帧动画) 和 Property Animation(属性动画)。 这篇文章,我就介绍一下这三种类型的动画。

       目录:

(一)View动画

(二)帧动画

(三)属性动画


(一)View动画

       View动画是基于View的渐变动画,只改变了View的绘制效果,View的实际属性值并未改变

       View动画的对象是View,它支持4种动画效果

  • TranslateAnimation(平移动画)
  • ScaleAnimation(缩放动画)
  • RotateAnimation(旋转动画)
  • AlphaAnimation(透明度动画)

        并提供了AnimationSet动画集合。实现原理是每次绘图时View所在的ViewGroup中的dispathDraw,流程如下图:

深入理解Android之动画        深入理解Android之动画

        除了这四种典型的动画效果外,帧动画本质上也属于View动画。但是帧动画的表现形式和这4种动画不太一样,因此通常单拎出来归为一类。

        这四种动画既可以通过XML来定义,也可以通过代码来动态创建。

        使用XML之前我们首先需要创建XML文件,路径为:res/anim/filename.xml。

1. 透明度动画

  • 代码实现:
AlphaAnimation animation = new AlphaAnimation(0, 1);// 透明度0变化到透明度为1
animation.setDuration(1000);// 动画执行时间1s
textView.startAnimation(animation);
  • XML实现:
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="1000"
        android:fromAlpha="0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:repeatCount="3"
        android:fillAfter="true"
        android:repeatMode="restart"
        android:toAlpha="1" />
</set>

2. 旋转动画

  • 代码实现:
RotateAnimation animation = new RotateAnimation(0, 360, 100, 100);
animation.setDuration(1000);
animation.setFillAfter(true); // 设置保持动画最后的状态
animation.setInterpolator(new AccelerateInterpolator()); // 设置插入器
textView.startAnimation(animation);

        还可以通过系统提供参数来控制动画

new RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
  • xml实现:
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromDegrees="0.0"
    android:interpolator="@android:anim/linear_interpolator"
    android:pivotX="50.0%"
    android:pivotY="50.0%"
    android:repeatCount="infinite"
    android:toDegrees="360.0">
</rotate>
参数 说明
fromDegrees 为动画起始时的旋转角度,此角度是当前为0及360,设置其他值则先跳至该角度的位置再由from - to的值: 负则正向转,正则反向转
toDegrees 为动画旋转到的角度
pivotXType 为动画在X轴相对于物件位置类型
pivotXValue 为动画相对于物件的X坐标的开始位置,此值是以本身原始位置为原点,即如设为20%p,则向右移动父控件的20%位移,为负数则向左移
pivotYType 为动画在Y轴相对于物件位置类型
pivotYValue 为动画相对于物件的Y坐标的开始位置,此值是以本身原始位置为原点,即如设为20%p,则向下移动父控件的20%位移,为负数则向上移

3. 位移动画

  • 代码实现:
TranslateAnimation translateAnimation = new TranslateAnimation(0, 200, 0, 200);
translateAnimation.setDuration(1000);
textView.startAnimation(translateAnimation);
  • xml实现:
<translate
    android:duration="1000"
    android:fillAfter="true"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:repeatCount="3"
    android:toXDelta="200"
    android:toYDelta="000">
</translate>
参数 说明
fromXDelta 为动画起始时 X坐标上的移动位置
toXDelta 为动画结束时 X坐标上的移动位置
fromYDelta 为动画起始时Y坐标上的移动位置
toYDelta 为动画结束时Y坐标上的移动位置

4. 缩放动画

  • 代码实现:
ScaleAnimation animation = new ScaleAnimation(0,1,0,1);
animation.setDuration(1000);
textView.startAnimation(animation);

        缩放动画也可以设置缩放的中心点

ScaleAnimation animation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
  • xml实现:
<scale
    android:duration="1000"
    android:fillAfter="true"
    android:fillBefore="true"
    android:fromXScale="0.0"
    android:fromYScale="0.0"
    android:interpolator="@android:anim/linear_interpolator"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="-1"
    android:repeatMode="reverse"
    android:startOffset="2000"
    android:toXScale="1.0"
    android:toYScale="1.0">
</scale>
参数 说明
fromX 为动画起始时 X坐标上的伸缩尺寸 0.0表示收缩到没有
toX 为动画结束时 X坐标上的伸缩尺寸 1.0表示正常无伸缩
fromY 为动画起始时Y坐标上的伸缩尺寸 值小于1.0表示收缩
toY 为动画结束时Y坐标上的伸缩尺寸 值大于1.0表示放大
pivotXType 为动画在X轴相对于物件位置类型
pivotXValue 为动画相对于物件的X坐标的开始位置
pivotXType 为动画在Y轴相对于物件位置类型
pivotYValue 为动画相对于物件的Y坐标的开始位置

5. anim文件使用

Animation animation = AnimationUtils.loadAnimation(this, R.anim.alpha_anim);
textView.startAnimation(animation);

6. 动画集合

        AnimationSet提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放,顺序播放等。

AnimationSet animationSet = new AnimationSet(true);
animationSet.setDuration(1000);

AlphaAnimation alpha=new AlphaAnimation(0,1);
alpha.setDuration(1000);
animationSet.addAnimation(alpha);

TranslateAnimation translate = new TranslateAnimation(100, 200, 0, 200);
translate.setDuration(1000);
animationSet.addAnimation(translate);
textView.startAnimation(animationSet);

7. 动画监听

        对于动画事件,Android也提供了开始、结束和重复事件的监听方法。

animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        
    }

    @Override
    public void onAnimationEnd(Animation animation) {

    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }
});

8. View动画的特殊使用

(1)LayoutAnimation

        layoutAnimation作用于viewGroup,为ViewGroup制定一个动画,他的每个子元素都会按照这种动画出场。这种效果常被用在ListView上。使用步骤如下:

    1)定义LayoutAnimation

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"  
    android:delay="0.5" 
    android:animationOrder="reverse"  
    android:animation="@anim/animation_scale">
</layoutAnimation>

android:delay             表示子元素开始动画的时间延迟
android:animationOrder         表示子元素动画的顺序,有三个选项:

  •  normal:顺序显示
  •  reverse:表示逆向显示
  •  random:随机播放

android:animation        为子元素指定具体的入场动画

 

    2)为子元素制定具体的入场动画

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="0.1"
    android:toXScale="1.5"
    android:fromYScale="0.1"
    android:toYScale="1.5"
    android:duration="2000">
</scale>

    3)为GroupView指定LayoutAnimation属性

<ListView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/xlv"
    android:layoutAnimation="@anim/animation_layout"
    android:background="#fff4f7f9"
    android:divider="#ddd"
    android:dividerHeight="1.0px"
    android:listSelector="#999">
</ListView>

 

        除了在XML文件中进行设置,我们还可以使用代码来完成: 

Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(controller);

(2)Transition Animation(过渡动画)

        Transition 也是基于属性动画的,与属性动画不同的是,它是可以实现布局改变的过渡动画。由于Transition的内容较多,具体请参照 Android Transition Framework(过渡动画框架) 

(二)帧动画

        帧动画就是加载一系列Drawable资源来创建动画,这种传统动画某种程度上就是创建不同图片序列,顺序播放,就像电影胶片。在代码中定义动画帧,使用AnimationDrawable类;XML文件能更简单的组成动画帧,在res/drawable文件夹,使用<animation-list>采用<item>来定义不同的帧。只能设置的属性是动画间隔时间。

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
   android:oneshot="false">
   <item android:drawable="@drawable/img0" android:duration="100"/>
   <item android:drawable="@drawable/img1" android:duration="100"/>
   <item android:drawable="@drawable/img2" android:duration="100"/>
   <item android:drawable="@drawable/img3" android:duration="100"/>
   <item android:drawable="@drawable/img4" android:duration="100"/>
   <item android:drawable="@drawable/img5" android:duration="100"/>
   ...
</animation-list>

        然后对组件的属性(如ImageView的android:src/android:background)直接设置即可。

ImageView iv = findViewById(R.id.image_view);
//iv.setBackgroundResource(R.drawable.drawable_animation); 
//获取背景,并将其强转成AnimationDrawable
 AnimationDrawable animationDrawable = (AnimationDrawable) iv.getBackground();
 //判断是否在运行
 if(!animationDrawable.isRunning()){
     //开启帧动画
     animationDrawable.start();
 }

        注意:帧动画的使用非常简单,但是比较容易引起OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。 

(三)属性动画

        属性动画的对象除了传统的View对象,还可以是Object对象,动画之后,属性值被实实在在的改变了。因此,属性动画能够通过改变View对象的实际属性来实现View动画。任何时候View属性的改变,View能自动调用invalidate()来刷新。

        属性动画是在 Android 3.0 开始引入的新的动画形式,不过说它新只是相对的,它已经有好几年的历史了,而且现在的项目中的动画 99% 都是用的它,极少再用到 View Animation 了。属性动画不仅可以使用自带的 API 来实现最常用的动画,而且通过自定义 View 的方式来做出定制化的动画。

1. ViewPropertyAnimator

        使用方式:

        View.animate() 后跟 translationX() 等方法,动画会自动执行。

view.animate().translationX(500);

深入理解Android之动画

2. ObjectAnimator

        使用方式:

  • 如果是自定义控件,需要添加 setter / getter 方法;
  • 用 ObjectAnimator.ofXXX() 创建 ObjectAnimator 对象;
  • 用 start() 方法执行动画。
public class SportsView extends View {

    float progress = 0;
    
    ......
    
    // 创建 getter 方法
    public float getProgress() {
        return progress;
    }

    // 创建 setter 方法
    public void setProgress(float progress) {
        this.progress = progress;
        invalidate();
    }
    
    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        ......
        
        canvas.drawArc(arcRectF, 135, progress * 2.7f, false, paint);
        
        ......
    }
}

......

// 创建 ObjectAnimator 对象
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 65);
// 执行动画
animator.start();

3. 通用功能

(1)setDuration(int duration) 设置动画时长

(2)setInterpolator(Interpolator interpolator) 设置 Interpolator(插值器)

  • AccelerateDecelerateInterpolator  先加速再减速  (默认的 Interpolator)
  • LinearInterpolator  匀速
  • AccelerateInterpolator  持续加速
  • DecelerateInterpolator  持续减速直到 0
  • AnticipateInterpolator  先回拉一下再进行正常动画轨迹
  • OvershootInterpolator  动画会超过目标值一些,然后再弹回来
  • AnticipateOvershootInterpolator  上面这两个的结合版:开始前回拉,最后超过一些然后回弹
  • BounceInterpolator  在目标值处弹跳
  • CycleInterpolator  这个也是一个正弦 / 余弦曲线
  • FastOutLinearInInterpolator  用贝塞尔曲线持续加速
  • LinearOutSlowInInterpolator  持续减速,初始速度更高
  • PathInterpolator  自定义动画完成度 / 时间完成度曲线
Path interpolatorPath = new Path();

...

// 匀速
interpolatorPath.lineTo(1, 1);

(3)setListener() / ObjectAnimator.addListener()  设置监听器

        参数类型是 AnimatorListener,所以本质上其实都是一样的。 AnimatorListener共有 4 个回调方法

  • onAnimationStart(Animator animation)
  • onAnimationEnd(Animator animation)
  • onAnimationCancel(Animator animation)
  • onAnimationRepeat(Animator animation)

补充:setUpdateListener() /addUpdateListener()

        和上面两个方法类似,但是这两个方法虽然名称和可设置的监听器数量不一样,但本质其实都一样的。它们的参数都是 AnimatorUpdateListener。它只有一个回调方法:

  • onAnimationUpdate(ValueAnimator animation)

4. TypeEvaluator

        关于 ObjectAnimator,可以用 ofInt() 来做整数的属性动画和用 ofFloat() 来做小数的属性动画。这两种属性类型是属性动画最常用的两种,不过在实际的开发中,可以做属性动画的类型还是有其他的一些类型。当需要对其他类型来做属性动画的时候,就需要用到 TypeEvaluator 了。

(1)ArgbEvaluator

   TypeEvaluator 最经典的用法是使用 ArgbEvaluator 来做颜色渐变的动画。

ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xffff0000, 0xff00ff00);
animator.setEvaluator(new ArgbEvaluator());
animator.start();

        在 Android 5.0 (API 21) 加入了新的方法 ofArgb()。

ObjectAnimator animator = ObjectAnimator.ofArgb(view, "color", 0xffff0000, 0xff00ff00);
animator.start();

(2)自定义 Evaluator

// 自定义 HslEvaluator
private class HsvEvaluator implements TypeEvaluator<Integer> {
   float[] startHsv = new float[3];
   float[] endHsv = new float[3];
   float[] outHsv = new float[3];

   @Override
   public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
       // 把 ARGB 转换成 HSV
       Color.colorToHSV(startValue, startHsv);
       Color.colorToHSV(endValue, endHsv);

       // 计算当前动画完成度(fraction)所对应的颜色值
       if (endHsv[0] - startHsv[0] > 180) {
           endHsv[0] -= 360;
       } else if (endHsv[0] - startHsv[0] < -180) {
           endHsv[0] += 360;
       }
       outHsv[0] = startHsv[0] + (endHsv[0] - startHsv[0]) * fraction;
       if (outHsv[0] > 360) {
           outHsv[0] -= 360;
       } else if (outHsv[0] < 0) {
           outHsv[0] += 360;
       }
       outHsv[1] = startHsv[1] + (endHsv[1] - startHsv[1]) * fraction;
       outHsv[2] = startHsv[2] + (endHsv[2] - startHsv[2]) * fraction;

       // 计算当前动画完成度(fraction)所对应的透明度
       int alpha = startValue >> 24 + (int) ((endValue >> 24 - startValue >> 24) * fraction);

       // 把 HSV 转换回 ARGB 返回
       return Color.HSVToColor(alpha, outHsv);
   }
}

ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xff00ff00);
// 使用自定义的 HslEvaluator
animator.setEvaluator(new HsvEvaluator());
animator.start();

(3)使用不限定类型的属性做动画

        借助于 TypeEvaluator,属性动画就可以通过 ofObject() 来对不限定类型的属性做动画了。

    1)为目标属性写一个自定义的 TypeEvaluator

    2)使用 ofObject() 来创建 Animator,并把自定义的 TypeEvaluator 作为参数填入

private class PointFEvaluator implements TypeEvaluator<PointF> {
   PointF newPoint = new PointF();

   @Override
   public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
       float x = startValue.x + (fraction * (endValue.x - startValue.x));
       float y = startValue.y + (fraction * (endValue.y - startValue.y));

       newPoint.set(x, y);

       return newPoint;
   }
}

ObjectAnimator animator = ObjectAnimator.ofObject(view, "position",
        new PointFEvaluator(), new PointF(0, 0), new PointF(1, 1));
animator.start();

5. PropertyValuesHolder

        很多时候,我们需要在同一个动画中改变多个属性,例如在改变透明度的同时改变尺寸。如果使用 ViewPropertyAnimator,你可以直接用连写的方式来在一个动画中同时改变多个属性。

view.animate()
        .scaleX(1)
        .scaleY(1)
        .alpha(1);

        而对于 ObjectAnimator,是不能这么用的。不过你可以使用 PropertyValuesHolder 来同时在一个动画中改变多个属性。

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);
 
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)
animator.start();

6. AnimatorSet 

        有的时候,我们不止需要在一个动画中改变多个属性,还会需要多个动画配合工作,比如,在内容的大小从 0 放大到 100% 大小后开始移动。这种情况使用 PropertyValuesHolder 是不行的,因为这些属性如果放在同一个动画中,需要共享动画的开始时间、结束时间、Interpolator 等一系列的设定,这样就不能有先后次序地执行动画了。这就需要用到 AnimatorSet 了。

ObjectAnimator animator1 = ObjectAnimator.ofFloat(...);
animator1.setInterpolator(new LinearInterpolator());
ObjectAnimator animator2 = ObjectAnimator.ofInt(...);
animator2.setInterpolator(new DecelerateInterpolator());
 
AnimatorSet animatorSet = new AnimatorSet();
// 两个动画依次执行
animatorSet.playSequentially(animator1, animator2);
animatorSet.start();

  AnimatorSet 还可以这么用:

// 两个动画同时执行
animatorSet.playTogether(animator1, animator2);
animatorSet.start();

        以及这么用:

// 使用 AnimatorSet.play(animatorA).with/before/after(animatorB)
// 的方式来精确配置各个 Animator 之间的关系
animatorSet.play(animator1).with(animator2);
animatorSet.play(animator1).before(animator2);
animatorSet.play(animator1).after(animator2);
animatorSet.start();

7. PropertyValuesHolders.ofKeyframe() 

        除了合并多个属性和调配多个动画,你还可以在 PropertyValuesHolder 的基础上更进一步,通过设置 Keyframe (关键帧),把同一个动画属性拆分成多个阶段。例如,你可以让一个进度增加到 100% 后再「反弹」回来。

// 在 0% 处开始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 时间经过 50% 的时候,动画完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 时间见过 100% 的时候,动画完成度倒退到 80%,即反弹 20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder);
animator.start();

 

总结:「关于复杂的属性关系来做动画」,就这么三种:

  1. 使用 PropertyValuesHolder 来对多个属性同时做动画;
  2. 使用 AnimatorSet 来同时管理调配多个动画;
  3. PropertyValuesHolder 的进阶使用:使用 PropertyValuesHolder.ofKeyframe() 来把一个属性拆分成多段,执行更加精细的属性动画。
深入理解Android之动画深入理解Android之动画 Fighting_初心 发布了37 篇原创文章 · 获赞 38 · 访问量 1万+ 私信 关注
上一篇:Android:属性动画


下一篇:08_属性动画和硬件加速(一)属性动画 Property Animation(上手篇)