欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 动画

程序员文章站 2022-06-28 14:30:16
...

Android 的动画可以分为三种:View动画,帧动画,属性动画。

View 动画

View动画的作用对象时View,它支持四种动画效果。平移,缩放,旋转,透明度动画。View 动画的四种变换效果对应着Animation 的四个子类。这四种动画既可以通过XML 来定义,也可以通过代码来动态创建,对于View 动画来说,建议采用XML 来定义动画。这是因为XML 格式的动画可读性更好。

Android 动画

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    >
    <!--fillAfter 该属性表示动画结束后view停留在结束位置-->
    <translate
        android:duration="3000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="100"
        android:toYDelta="100"
        ></translate>

    <alpha
        android:fromAlpha="0.2"
        android:toAlpha="0.8"></alpha>

    <rotate
        android:duration="1000"
        android:fromDegrees="0"
        android:pivotX="50"
        android:toDegrees="90"></rotate>

    <scale
        android:fromXScale="0"
        android:fromYScale="0"
        android:pivotX="10"
        android:pivotY="10"
        android:toXScale="0.5"
        android:toYScale="0.5"></scale>
</set>

从上面语法可以看出,View动画既可以是单个动画,也可以由一系列动画组成。
<‘Set> 标签标示动画集合,对应AnimationSet 类,可包含若干个动画,并且它的内部也是可以嵌套其他动画集合的。

  • android:interpolator :表示动画集合所采用的插值器。
  • android:shareInterpolator : 表示集合中的动画是否和集合共享一个插值器。如果集合不指定插值器,那么子动画就需要单独指定所需的插值器或者使用默认值。
  • android : duration —— 动画的持续时间
  • android:fillAfter—— 动画结束以后View是否停留在结束位置,true 表示View 停留在结束位置,false 则不停留。

translation 标签表示平移动画,对应TeanslateAnimation 类

  • fromXDelta —— 表示X的起始值,比如0
  • toXDelta —— 表示X 的结束值,比如 100
    -fromYDelta —— 表示Y的起始值
    -toYDelta —— 表示 Y的结束值

scale 标签表示缩放动画,对应ScaleAnimation 类

  • fromXScale ——水平方向缩放的起始值。比如 0.5
  • toXScae —— 水平方向缩放的结束值 。比如1.2;
  • fromYScale —— 竖直方向缩放的起始值
  • toYScae —— 竖直方向缩放的结束值
  • pivotX —— 缩放的轴点的X 坐标,它会影响缩放的效果
  • pivotY —— 缩放的轴点的Y坐标,它会影响缩放的效果

默认情况下轴点是View 的中心点,这个时候在水平方向进行缩放的话会导致View 向左右两个方向同时进行缩放,但是如果把轴点设为View的右边界,那么View 就只会向左边进行缩放,反之则向右边进行缩放。

rotate 标签表示旋转动画 ,对应RotateAnimation 类

  • fromDegrees —— 旋转开始的角度,比如0
  • toDegrees —— 旋转结束的角度,比如180
  • pivotX —— 旋转的轴点 x 轴坐标
  • pivotY —— 旋转的轴点 y 轴坐标

在缩放动画(scale标签)和旋转动画(rotate标签)中均有 android:pivotX 和 android:pivotY 这两个属性,其值可以有多种形式。可以是整数值、百分数(或者小数)、百分数p 三种样式

当属性值为

  • 数值时,表示在当前 View 的左上角,即原点处加上 50px ,作为轴坐标
  • 百分数,比如 50%,表示在当前控件的左上角加上自己宽度的 50% (即自身宽度中心)作为轴坐标
  • 50%p :就是表示在当前的左上角加上父控件宽度的 50% 作为轴坐标

alpha 标签表示透明度动画,对应AlphaAnimation

  • fromAlpha —— 表示透明度的起始值 比如 0.1;
  • toAlpha —— 表示透明度的结束值 比如 1;

如何应用上面动画呢? 如下

bt_animation2=findViewById(R.id.bt_animation2);
Animation animation=AnimationUtils.loadAnimation(Main4Activity.this,R.anim.animation_1);
bt_animation2.startAnimation(animation);

除了在XML 中定义动画,还可以通过代码来应用动画,如下

AlphaAnimation alphaAnimation=new AlphaAnimation(0,1);
alphaAnimation.setDuration(2000);
bt_animation1.setAnimation(alphaAnimation);

动画监听

可以通过Animation 的setAnimationListener 的方法给View 动画添加监听过程

public static interface AnimationListener {

 void onAnimationStart(Animation animation);
 void onAnimationEnd(Animation animation);
 void onAnimationRepeat(Animation animation);

}

View动画的特殊使用场景

LayoutAnimation 作用于ViewGroup,为ViewGroup 指定一个动画,这样当它的子元素出场时都会具有这种动画效果。这种效果常被用在ListView 或 RecyclerView 上。

(1)定义LayoutAnimatoin ,如下

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.5"
    android:animationOrder="normal"
    android:animation="@anim/animation_2"
    >
    <!--delay 表示子元素开始动画的时间延迟,
    比如子元素入场动画的时间周期为300ms ,
    第一个子元素延迟150ms,第二个延迟300Ms 以此类推
    -->

    <!--animationOrder 表示子元素动画的顺序
       normal表示顺序显示,即排在前面的子元素先开始播放入场动画
       reverse 表示逆向显示
       random 表示随机播放 -->

    <!--animation 为子元素指定具体的入场动画-->
</layoutAnimation>

(2) 为子元素指定具体的入场动画,如下

anim/animation_2

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    >

    <translate android:toXDelta="0"
        android:fromXDelta="500"
        ></translate>

    <alpha android:toAlpha="1"
        android:fromAlpha="0.3"
        ></alpha>

    <rotate android:toDegrees="0"
        android:fromDegrees="200"
        android:pivotY="0"
        android:pivotX="0"
        ></rotate>
</set>

(3)为ViewGroup 指定android:layoutAnimation 属性,如下

<android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView_layoutAnimation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layoutAnimation="@anim/layout_animation_layout"
        >
</android.support.v7.widget.RecyclerView>

除了通过XML 中指定LayoutAnimation 外,还可以,按如下方式来实现

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

属性动画

属性动画可以对任意对象的属性进行动画而不仅仅是View。其可以达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变。因属性动画在API 11 才有。可以使用动画开源库Nineoldandroids ,Nineoldandroids 对属性动画做了兼容,在Api 11 之前的版本其内部是通过代理View动画来实现。因此在Android 低版本上,它的本质还是View动画。

1.改变一个对象的 translationX 属性,以它的宽度在X轴上平移一段距离。

ObjectAnimator
.ofFloat(bt_animation_4,"translationX",bt_animation_4.getWidth())
.start();

2.改变一个对象的背景色属性

 //背景颜色在3秒内从红到蓝渐变,动画会无限循环而且会有反转的效果
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(bt_animation_1, "backgroundColor", Color.RED, Color.BLUE);
objectAnimator.setDuration(3000);
objectAnimator.setEvaluator(new ArgbEvaluator());
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
objectAnimator.start();

3.动画集合,5秒内对View 的旋转,平移,缩放和透明度都进行了改变

AnimatorSet animationSet = new AnimatorSet();
animationSet.playTogether(
  ObjectAnimator.ofFloat(bt_animation_2, "translationX", bt_animation_2.getWidth()),
  ObjectAnimator.ofInt(bt_animation_2, "backgroundColor", Color.RED, Color.BLUE),
  ObjectAnimator.ofFloat(bt_animation_2, "alpha", 0, 0.3f, 1f, 0.5f, 1),
  ObjectAnimator.ofFloat(bt_animation_2, "rotationX", 0, 360),
  ObjectAnimator.ofFloat(bt_animation_2, "scaleX", 1, 1.5f)
                );
  animationSet.setDuration(5000);
  animationSet.start();

属性动画除了通过代码实现以外,还可以通过XML 来定义。属性动画需要定义在 res/animator/ 目录下,语法如下

Android 动画

  • ordering —— together:表示动画集合中的子动画同时播放;sequentially:表示动画集合中的子动画按照前后顺序依次播放。默认是together
  • propertyName —— 表示属性动画的作用对象的属性的名称
  • duration —— 表示动画的时长
  • valueFrom —— 表示属性的起始值
  • valueTo —— 表示属性的结束值
  • startOffset —— 表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正播放此动画
  • repeatCount —— 表示动画的重复次数 默认0 ; -1表示无限循环
  • repeatMode —— 表示动画的重复模式 restart:连续重复; reverse:逆向重复
  • valueType —— 表示propertyName 所指定的属性的 类型,有intType 和 floatType两个选项,代表整型和浮点型。另外,如果propertyName 所指定的属性表示的是颜色,那么不需要指定valueType,系统会自动对颜色类型的属性做处理。

理解插值器和估值器

TimeInterpolator (插值器)
作用:根据时间流逝的百分比来计算当前属性值改变的百分比。
系统预置了三种插值器,分别是:

  • LinearInterpolator :线性插值器:匀速动画
  • AccelerateDecelerateInterpolator:加速减速插值器,动画两头块中间慢
  • DecelerateInterpolator:减速插值器,动画越来越慢

TypeEvaluator (类型估值算法)
作用:根据当前属性改变的百分比类计算改变后的属性值
系统预置了三种估值器,分别是:

  • IntEvaluator —— 针对整型属性
  • FloatEvluator —— 针对浮点属性
  • ArgbEvluator —— 针对颜色属性

    下图表示一个匀速动画,采用了线性插值器整型估值算法 ,在40ms内,View的X属性实现从0 - 40 的变化。
    Android 动画
    由于动画默认刷新率为 10ms / 帧,所以该动画将分5帧进行,我们来考虑第三帧(x = 20,t =20ms),t=20ms这个时候 ,时间流逝百分比是0.5 (20/40=0.5),意味着现在的时间过了一半,那x应该改变多少呢?这个由插值器和估值算法来确定。
    拿线性插值器来说,当时间流逝一半的时候,x 的变换也应该是一半,即x 的改变是0.5,是实现匀速动画的,下面看源码

public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

很显然,线性插值器的返回和输入一样,因此插值器返回的值是0.5,这意味着x的改变也是0.5。具体x变成了什么值,这个需要估值算法来确定,我们来看看整型估值器算法的源码


public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     *fraction 估值小数
     *startValue  开始值
     *endValue    结束值
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

上述三个参数分别对应于我们的例子就分别是 0.5040

属性动画的监听器

属性动画提供了监听器用于监听动画的播放过程,主要有如下两个接口:

  • AnimatorListener :它可以监听动画的开始、结束、取消、重复播放。同时为了方便开放,系统换提供了AnimatorListenerApapter 类,是AnimatorListener 的适配器类。使我们可以有选择地实现开始、结束、取消、重复播放这4个方法。
public static interface AnimatorListener {

    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
}
  • AnimatorUpdateListener:它会监听整个动画过程,动画是由许多帧组成的,每播放一帧,onAnimatorUpdate 就会被调用一次,利用这个特性我们可以做一些特殊的操作
 public static interface AnimatorUpdateListener {
        void onAnimationUpdate(ValueAnimator animation);
}

对任意属性做动画

当我们需要让一个Button宽度从当前宽度增加到500px, View动画肯定是不行的。所幸我们还有属性动画,我们用属性动画试试。

bt_animation_6=findViewById(R.id.bt_animation_6);
bt_animation_6.setOnClickListener(new View.OnClickListener() {
     @Override
public void onClick(View v) {
    ObjectAnimator.ofFloat(bt_animation_6,"width",500).start();
  }
});

运行后你会发现没效果,其实没效果是对的。下面我们分析一下属性动画的原理.
属性动画原理:属性动画要求动画作用的对象提供该属性的 set 方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次调用 set 方法。每次传递给 set 方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还需要提供 get 方法,因为系统要去获取属性的初始值。如果想要动画生效,要同时满足两个条件:

  • object 必须要提供 set 方法,如果动画的时候没有传递初始值,那么还需要提供 get
    方法,因为系统要去取属性的初始值(如果这条件不满足,程序直接Crash)
  • object 的 set 方法对属性所做的改变必须能够通过某种方法反射出来,比如会带来UI上的改变(如果这不满足,动画无效过但不会Crash)

    以上条件缺一不可。那么为什么我们的Button 的 width 属性做得动画会没有效果呢?这是因为Button 内部虽然提供了getWidth 和 setWidth 方法,但是这个setWidth 方法并不是改变视图的大小,它是TextVIew 新添加的方法,View 是没有这个 setWidth 方法的,由于Button 继承了TextView ,所以 Button 也就有了setWidth 方法。
    而这个setWidth 设置的事TextView 的最大宽度和最小宽度,并不是TextView的宽度。总之,TextView和Button 的setWidth 、getWidth 干的不是同一件事情,通过setWidth 无法改变控件的宽度。针对上诉问题,官方文档告诉我们有3种解决方法:

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

这个的意思很好理解,如果你有权限的话,加上 get 和 set 就搞定了。但是很多时候我们没有权限去这么做。比如文本开头所提到的问题,你无法给Button 加上一个合乎要求的 setWidth 方法,因为这是Android SDK 内部实现的。这个方法就简单,但是往往是不可行的,这里就不对其进行更多的分析了。

2.用一个类来包装原始对象,间接为其提供get 和 set 方法;

3. 采用 ValueAnimator , 监听动画过程,自己实现属性的改变。

相关标签: Android 动画