Android动画

本文内容:Android 动画

版权声明:本文为原创文章,未经允许不得转载

博客地址:http://blog.csdn.net/kevindgk

  • Android动画
    • 分类
    • 区别
  • 帧动画Frame
  • 补间动画Tween
    • XML格式
      • 定义
      • 使用
      • 特点
    • 代码格式
      • 特点
    • 自定义补间动画
    • 使用场景
      • Activity的转场效果
  • 属性动画Animator
    • 继承树
    • Xml格式
    • 插值器Interpolator
      • 继承树
      • 实现类
      • 自定义插值器
    • 估值器TypeEvaluator
      • 继承树
      • 实现类
      • 自定义估值器
    • 动画监听器
    • 值动画ValueAnimator
      • 原理
      • 控件背景色渐变效果
      • 图片从左向右移动效果
      • 缩放效果-多属性动画
    • 对象动画ObjectAnimator
      • 平移动画
      • 旋转动画
    • 动画集合AnimatorSet
    • 对任意属性做动画
    • 注意事项

Android动画

分类

  • View 动画(View Animation)
    • 帧动画(Frame Animation):顺序执行一组准备好的图片
    • 补间动画(Tween Animation):通过对View对象不断地做图像变换(平移、缩放、旋转、渐变)产生动画效果,这种动画只适用于View对象;
  • 属性动画(Property Animation):通过动态的改变对象的属性从而达到动画效果,该动画适用于任何对象,>=3.0(API 11),推荐使用该方式。

区别

​ 就实现原理来分析,Property Aniamtion和Tween Animation之间最大的区别就是前者在为对象添加动画效果时,其更改的是对象的实际属性,而后者改变的只是View的绘制效果,View的实际属性值是不发生改变的。

​ 比如对一个Button实现左右移动的动画,若使用Property Animation实现的话,Button在移动的过程中,由于Property Animation改变的是Button的实际属性值,因此Button的事件响应焦点也随着移动,若点击移动中的Button,其可响应事件点击;而使用View Animation实现的话,点击移动中的Button,则无法响应事件点击,原因是Button控件本身的实际位置依然在初始位置,其事件响应焦点也依然在初始位置。

帧动画(Frame)

顺序播放一组预先定义好的图片,类似于电影播放,关键类是AnimationDrawable

名称 标签 对应的Java类
帧动画 AnimationDrawable

1. 在XML中使用标签定义图片列表,作为一个AnimationDrawable

路径:res/drawable/anim_frame_example01.xml


<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@mipmap/signal01" android:duration="200"/>
    <item android:drawable="@mipmap/signal02" android:duration="200"/>
    <item android:drawable="@mipmap/signal03" android:duration="200"/>
    <item android:drawable="@mipmap/signal04" android:duration="200"/>
    <item android:drawable="@mipmap/signal05" android:duration="200"/>
    <item android:drawable="@mipmap/signal06" android:duration="200"/>
animation-list>
  1. 将该AnimationDrawable在XML中设置为View的背景
<ImageView
    android:id="@+id/iv_signal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingTop="30px"
    android:background="@drawable/anim_frame_example01"/>
  1. 在代码中获取该AnimationDrawable,并开启和停止动画
mAnim = (AnimationDrawable) mIvSignal.getBackground();
// 启动动画
mAnim.start();
// 关闭动画
mAnim.stop();

补间动画(Tween)

开发者只需指定动画开始、动画结束等关键帧,系统自动补全中间帧的动画,类似与Flash的补间动画。

名称 标签 对应Animation的子类
平移动画 TranslateAnimation
缩放动画 ScaleAnimation
旋转动画 RotateAnimation
透明度动画 AlphaAnimation

- AnimationSet:动画集合

1.XML格式

定义

路径:res/anim/xxx.xml


<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fillAfter="true"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true">

    
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />

    
    <scale
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:pivotX="0.5"
        android:pivotY="0.5"
        android:toXScale="2.0"
        android:toYScale="2.0" />

    
    <translate
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="100"
        android:toYDelta="100" />

    
    <rotate
        android:fromDegrees="0"
        android:pivotX="0.5"
        android:pivotY="0.5"
        android:toDegrees="360" />
set>
  • android:interpolator:插值器,控制动画的速度,详细见下文
  • android:shareInterpolator:表示集合动画是否和集合共享一个插值器,
  • android:duration=”500”:动画的持续时间
  • android:fillAfter=”true”:动画结束后是否停留在结束位置

使用

Animation animation = AnimationUtils.loadAnimation(this, R.anim.xxx);
btn.startAnimation(animation);

特点

  • 优点:写法简单,可重复使用
  • 缺点:不够灵活

2.代码格式

// 透明度动画
AlphaAnimation alpha = new AlphaAnimation(0, 1);
alpha.setDuration(500);
alpha.setFillAfter(true);
alpha.setInterpolator(new AccelerateDecelerateInterpolator());
// 设置动画监听器
alpha.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {}

    @Override
    public void onAnimationEnd(Animation animation) {}

    @Override
    public void onAnimationRepeat(Animation animation) {}
});
// 设置动画执行次数
alpha.setRepeatMode(Animation.INFINITE);    // 无限次循环/反转
// 开始执行动画
btn.startAnimation(alpha);

// 取消执行动画
alpha.cancel();
// 如果取消动画,下次执行之前必须重置动画
alpha.reset();

特点

  • 优点:可以动态修改参数,比较灵活

自定义补间动画

继承Animation这个抽象类,并重写方法:

  • initialize():初始化
  • applyTransformation():进行相应的矩阵变换

使用场景

Activity的转场效果

Activity在切换或者是退出的时候可以使用渐入,滑动,缩放等动态效果。

/*
    该行代码必须在finish()或者startActivity()之后执行
    - args1:进入的Activity的动画
    - args2:退出的Activity的动画
    如果不想要进入或者退出的Activity有动画,那么args为0即可
*/
overridePendingTransition(args1, args2);

详细的使用方式请见Library中动画章节。

  • ==SDK内置动画方式:android.R.anim.xxx,在路径C:\Android\sdk\platforms\android-24\data\res\anim下有很多demo,可以学习和使用!==

属性动画(Animator)

名称 标签
值动画 ValueAnimator < animator >
对象动画 ObjectAnimator < objectAnimator >
动画集合 AnimatorSet < set >

继承树

  • Animator:动画的父类,提供动画的基础支持,比如:开始、结束、监听
    • ValueAnimator:值动画
    • ObjectAnimator:对象动画
    • TimeAnimator
    • AnimatorSet:动画集合

Xml格式

定义目录:res/animator/,XML定义动画格式如下:


<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together|sequentially">

    
    <animator
        android:duration="int"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:repeatCount="int"
        android:repeatMode="restart|reverse"
        android:startOffset="int"
        android:valueFrom="int|float|color"
        android:valueTo="int|float|color"
        android:valueType="intType|floatType|colorType|pathType" />

    
    <objectAnimator
        android:duration="int"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:propertyName="string"
        android:repeatCount="int"
        android:repeatMode="restart|reverse"
        android:startOffset="int"
        android:valueFrom="int|float|color"
        android:valueTo="int|float|color"
        android:valueType="intType|floatType|colorType|pathType" />

set>
  • 如果只有一种动画,那么直接作为根标签即可;

使用XML定义的属性动画:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.test);
set.setTarget(view);
set.start();

插值器(Interpolator)

工作流程:

  • 创建ValueAnimator,并设置参数

    • TimeInterpolator:时间插值器
    • TypeEvaluator:类型估值器
    • 动画执行的开始、结束、执行时间

    设置完毕后,开启动画;

  • 时间流逝的百分比=动画已经执行的时间/动画执行的总时间,Elapsed fraction,0=

继承树

  • TimeInterpolator

    时间插值器,该类定义了动画的变化的速率。该接口中定义了一个方法

    float getInterpolation(float input);

    该方法输入时间流逝的百分比,返回插值因子,即当前属性值改变的百分比。

实现类

名称 名称 特点
AccelerateDecelerateInterpolator 加速减速插值器 开始和结束的时候减速,中间加速
AnticipateInterpolator 在开始的时候先反向执行,然后再正向执行
PathInterpolator
BounceInterpolator 回弹插值器 在结束的时候回弹一些
OvershootInterpolator 在结束的时候超过结束值一些,然后回弹
AnticipateOvershootInterpolator 在开始的时候先反向执行,然后再正向执行;在结束的时候超过结束值一些,然后回弹
LinearInterpolator 线性插值器 线性变化,比率为常数
AccelerateInterpolator 加速插值器 在开始的时候减速,然后加速执行
DecelerateInterpolator 减速插值器 在开始的时候加速,然后减速执行
CycleInterpolator 周期插值器 重复执行指定周期的动画,动画变化比率执行正弦函数(sin)

线性插值器源码:

public class LinearInterpolator extends BaseInterpolator {
    ...
    public float getInterpolation(float input) {
        return input;
    }
}

例如,在40ms内View的x属性从0到40的变换,如下图所示:

​ 动画的默认刷新时间为10ms/帧,所以该动画有5帧,当第三帧的时候,t=20ms,此时时间流逝的百分比为P1=20ms/40ms=0.5,根据线性插值器,输入input=0.5,直接返回插值因子p2=0.5,即属性的变化比率应该为50%。然后再根据下面的估值器来计算属性。

自定义插值器

继承BaseInterpolator即可,实现float getInterpolation(float input);方法,input即表示动画执行的时间的进度,即时间流逝的百分比,0~1之间。

估值器(TypeEvaluator)

​ 根据插值因子(属性变化的比率)来计算真实的属性值。

继承树

  • TypeEvaluator 类型估值器

    该方法中包含一个方法evaluate,该方法输入一个初始值、结束值、属性变化的比率,输出属性值。

    public T evaluate(float fraction, T startValue, T endValue);

实现类

IntEvaluator 整型估值器

public class IntEvaluator implements TypeEvaluator<Integer> {
    ...
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

该估值器输入上文线性插值器举例的数据,startInt=0,endValue=40,fraction=0.5,那么返回的数值为

value = 0 + ( 40 - 0 ) * 0.5 = 20

自定义估值器

实现TypeEvaluator接口,实现evaluate()方法即可。根据插值因子,初始值、结束值,计算出当前帧的value。

动画监听器

// 动画监听器:该listner在动画开始、结束、重复、取消的时候都会收到回调
valueAnimator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
    }

    @Override
    public void onAnimationEnd(Animator animation) {

    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }
});
// 动画刷新监听器,在动画生命周期的每一帧,当value被计算出之后,就会收到回调,所以说如果是使用的ValueAnimator,则必须实现此监听器,来实现动画。
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    // 在收到回调的时候,可以通过getAnimatedValue()获取到当前帧的value值,并将它赋值给view
    Integer animatedValue = (Integer) animation.getAnimatedValue();
    mBtn02.setBackgroundColor(animatedValue);
    }
});

值动画(ValueAnimator)

原理

​ 该类提供了一个简单的时间选择器来执行动画,能够计算出动画执行的数值,并将它们设置给目标对象。

控件背景色渐变效果

// 使用ValueAnimator.ofXxx()方法创建ValueAnimator对象
ValueAnimator valueAnimator = ValueAnimator.ofArgb(0xFFFF0000, 0xFF00FF00);
// 设置动画参数
valueAnimator.setDuration(3000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
// 设置插值器,如果没有则使用默认值
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
// 设置估值器,如果没有则使用默认值
valueAnimator.setEvaluator(new IntEvaluator());
// 设置动画监听
valueAnimator.addListener(new Animator.AnimatorListener() {
      @Override
      public void onAnimationStart(Animator animation) {
      }

      @Override
      public void onAnimationEnd(Animator animation) {

      }

      @Override
      public void onAnimationCancel(Animator animation) {

      }

      @Override
      public void onAnimationRepeat(Animator animation) {

      }
});
// 设置动画刷新监听器
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
      // 在没一帧的回调用,将value值赋值给对象
      Integer animatedValue = (Integer) animation.getAnimatedValue();
      mBtn02.setBackgroundColor(animatedValue);
      }
});
// 开启动画
valueAnimator.start();

图片从左向右移动效果

/**
* 从左向右移动
*/
private void leftToRight() {
      // 获取屏幕属性
      DisplayMetrics metrics = new DisplayMetrics();
      getWindowManager().getDefaultDisplay().getMetrics(metrics);
      // 构建ValueAnimator
      ValueAnimator valueAnimator = ValueAnimator.ofInt(0, metrics.widthPixels - iv.getWidth());
      valueAnimator.setDuration(2000);
      valueAnimator.setRepeatCount(1);
      valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
      valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
            int widthUpdate = (int) animation.getAnimatedValue();
            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) iv.getLayoutParams();
            layoutParams.leftMargin = widthUpdate;
            iv.setLayoutParams(layoutParams);
            }
      });
      valueAnimator.start();
}

缩放效果-多属性动画

    /**
     * 缩放
     * - 在缩放的时候,实际上同时动画的属性有X和Y,
     * 如果只有一个属性或者两个属性变化相同的时候,仍然可以使用ValueAnimator.ofFloat()来实现,
     * 但是,如果想要多个属性同时动画,并且每个属性变化并不相同,那么就需要使用PropertyValuesHolder
     * 来实现动画了。
     */
    private void scaleChange() {

        // 为每个属性值设置初始值、结束值、属性名称(随意写,只要有含义即可)
        PropertyValuesHolder x = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0f);
        PropertyValuesHolder y = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 0f);

        // 使用ofPropertyValuesHolder()创建构造器
        ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(x, y);
        valueAnimator.setDuration(2000);
        valueAnimator.setRepeatCount(1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 通过定义的属性名,取出来各自属性计算的数值
                float scaleX = (float) animation.getAnimatedValue("scaleX");
                float scaleY = (float) animation.getAnimatedValue("scaleY");
                iv.setScaleX(scaleX);
                iv.setScaleY(scaleY);
            }
        });
        valueAnimator.start();
    }

​ 通过对ValueAnimator的简单使用就可以看出,只要经过以下步骤就可以轻松的对任何对象的属性实现动画效果:

  • 第一步,使用ValueAnimator.ofXxx()方法构建ValueAnimator对象;
  • 第二步,设置动画执行的参数;
  • 第三步,设置动画的刷新监听.addUpdateListener(),在每一帧的回调中,通过animation.getAnimatedValue()方法获取计算好的属性值,然后将该值设置给View的属性;
  • 第四步,开启动画。

​ 整体思路还是比较清晰的,而且使用比较简单,但是在Android 3.0中,google给View增加了一些新的属性以及相应的getter、setter方法:

属性 含义
x,y 坐标,绝对值
translationX、translationY 坐标变化量,相对于容器的左上角
rotation、rotationX、rotationY 旋转角度
scaleX、scaleY 缩放比例
pivotX、pivotY 缩放和旋转的中心点
alpha 透明度,0-透明;1-不透明

​ 如果想要给View的一个属性实现动画,并且该属性具有setter/getter方法,那么就可以直接使用下文的ObjectAnimator来实现。

对象动画(ObjectAnimator)

​ 对View的具有getter\setter方法的属性xxx实现动画,可以使用ObjectAnimator。

前提条件:

  • 该属性具有getter/setter方法,并且调用getter/setter方法后,View属性值会修改,界面会刷新。

平移动画

    /**
     * 使用ObjectAnimator实现平移动画
     */
    private void translationByObjectAnimator() {
        // 获取屏幕属性
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);

        // 宽度变化:从左到右
        ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(iv, "translationX", 0, metrics.widthPixels - iv.getWidth());
        objectAnimatorX.setRepeatCount(1);
        objectAnimatorX.setRepeatMode(ValueAnimator.REVERSE);

        // 高度变化:从上到下,屏幕的高度-顶部标题栏的高度-手机状态栏的高度
        ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(iv, "translationY", 0,
                metrics.heightPixels - iv.getHeight() - AutoUtils.getPercentHeightSize(120) - 50);
        objectAnimatorY.setRepeatCount(1);
        objectAnimatorY.setRepeatMode(ValueAnimator.REVERSE);

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(objectAnimatorX, objectAnimatorY);
        animatorSet.setDuration(2000);
        animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
        animatorSet.start();
    }

旋转动画

    /**
     * 使用ObjectAnimator实现旋转动画
     */
    private void rotationByObjectAnimator() {

        ObjectAnimator rotation = ObjectAnimator.ofFloat(iv, "rotation", 0f, 360f);
        rotation.setInterpolator(new LinearInterpolator()); // 线性插值器,匀速旋转
        rotation.setDuration(1000);
        rotation.setRepeatCount(ValueAnimator.INFINITE);
        rotation.setRepeatMode(ValueAnimator.RESTART);
        rotation.start();
    }

动画集合(AnimatorSet)

​ Android动画集合分为两种,一种是AnimationSet表示补间动画集合,还有一种就是AnimatorSet表示属性动画集合。注意,不能给动画集合设置动画重复的次数和方式,需要给每个动画分别设置。

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(objectAnimatorX, objectAnimatorY);
animatorSet.setDuration(2000);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.start();

对任意属性做动画

  • 对于满足条件的,可以直接使用ObjectAnimator做属性动画;

  • 对于不满足条件的,可以采用以下方法:

    • 给对象的属性添加getter/setter方法,通常自定义View可以使用;

    • 直接使用ValueAnimator,监听动画过程,手动的将value值设置给View对象的属性;

    • 用一个类来包装原始对象,间接的为其提供getter和setter方法,方法如下

      /**

      • 通过包装对象来实现缩放效果
        */
        private void scaleByViewWrapper() {
        LogUtil.i(tag, “scaleByViewWrapper”);
        ViewWrapper viewWrapper = new ViewWrapper(iv);
        ObjectAnimator objectAnimator = ObjectAnimator.ofInt(viewWrapper, “width”, 0);
        objectAnimator.setDuration(2000);
        objectAnimator.setRepeatCount(1);
        objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
        objectAnimator.start();
        }

        /**

        • View的包装类
        • 由于View的setWidth()方法并不会给真正的给View设置宽度,
        • 需要修改其LayoutParams.with值才可以。
          */
          private class ViewWrapper {
          private View view;

        public ViewWrapper(View view) {
        this.view = view;
        }

        public int getWidth() {
        return view.getWidth();
        }

        public void setWidth(int width) {
        // 获取布局参数
        view.getLayoutParams().width = width;
        // 当布局参数变化的时候,调用该方法进行刷新界面
        view.requestLayout();
        }
        }
        使用ViewWrapper,可以很方便的实现复用,棒棒哒~

注意事项

  • View的setWidth()方法并不起作用,所以如果想要动态修改View的宽度和高度,可以使用:

    // 获取布局参数
    view.getLayoutParams().width = width;
    // 当布局参数变化的时候,调用该方法进行刷新界面
    view.requestLayout();

    并且要求View在XML中的布局参数的宽度和高度为wrap_content,如果还想要显示宽度和高度,可以配合maxWidth和maxHeight使用。

你可能感兴趣的:(android-动画)