Android 动画基础知识学习

时间:2024-03-14 09:39:45

 

1.Android中的三种动画

  1. View动画

    通过场景里的对象不断做图像变换(平移,缩放,旋转,透明度)从而产生动画效果,是一种渐近式动画,并支持自定义。

  2. 帧动画

    帧动画其实也属于View动画。通过顺序播放一系列图像从而产生动画效果,可以简单理解为图片切换动画效果,但图片过多过大会导致OOM

  3. 属性动画

    属相动画通过动态地改变对象的属性从而达到动画效果。

重点在于属性动画的学习


2.View动画

View动画的作用对象是View。支持四种典型动画效果:

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

 

对于View动画,建议采用xml来定义动画,这样可读性更好

View动画的四种变换

名称 标签 子类 效果
平移动画 <translate> TranslateAnimation 移动View
缩放动画 <scale> ScaleAnimation 放大或缩小View
旋转动画 <rotate> RotateAnimation 旋转View
透明度动画 <alpha> AlphaAnimation 改变View的透明度

Animation属性:

xml属性 jav代码 作用
android:detachWallpaper setDetachWallpaper(boolean) 是否在壁纸上运行
android:duration setDuration(long) 动画的持续时间
android:fillAfter setFillAfter(boolean) 动画结束后是否停留在结束位置
android:fillBefore setFillBefore(boolean) 动画结束时是否还原开始位置
android:fillEnabled setFillEnabled(boolean) 同上,与fillBefore相同
android:interpolator setInterpolator(Interpolator) 设置插值器
android:repeatCount setRepeatCount(int) 重复次数
android:repeatMode setRepeatMode(int) 有两种重复类型,reverse倒序回放,restart从头播放
android:startOffset setStartOffset(long) 开启动画startAnimation(animation)之后等待执行运行动画的时间
android:zAdjustment setZAdjustment(int) 表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal

View动画既可以是单个动画,也可以是一些列动画组成。


<set> 标签标示动画集合,对应于AnimationSet类,可以包含若干动画,也可以嵌套其他的动画集合。

  • android:interpolator
    动画集合所采用的的插值器,插值器影响动画的速度,比如非匀速动画就需要通过插值器来控制动画的播放过程。默认为@android:anim/accelerate_decelerate_interpolator,即加速加速插值器。
  • android:shareInterpolator
    集合中的动画是否和集合共享同一个插值器。如果集合不指定插值器,子动画就需要单独指定所需的插值器或者使用默认值。

2.1TranslateAnimation平移动画

Android 动画基础知识学习
平移动画


可以简单实现抖动效果,转成gif掉帧有点严重,没有抖起来

res下创建anim文件夹,文件名translate_animation.xml

xml文件代码:

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="false"
    android:duration="100"
    android:repeatMode="restart"
    >
    <translate
        android:repeatCount="2"
        android:fromXDelta="-10"
        android:fromYDelta="-5"
        android:toXDelta="10"
        android:toYDelta="5" />
</set>

java代码:

Button bt = (Button) view.findViewById(R.id.bt_translate_fragment_translate);
ImageView iv = (ImageView)view.findViewById(R.id.iv_translate_fragment_translate);
//初始化动画
Animation animation = AnimationUtils.loadAnimation(context, R.anim.translate_animation);
//点击按钮开始动画
bt.setOnClickListener((v) -> iv.startAnimation(animation));

  • android:fromXDelta
    x的起始坐标值,可以为数值、百分数、百分数p。以View的左上角为坐标系原点。负为左,正为右。
Android 动画基础知识学习
1.0 坐标图
  1. 数值: 10表示以当前View左上角坐标加10px为初始点
  2. 百分数: 50%表示以当前View的左上角加上当前View宽高的50%做为初始点
  3. 百分数p: 50%p表示以当前View的左上角加上父控件宽高的50%做为初始点
  • android:toXDelta
    x的结束坐标值
  • android:fromYDelta
    y的起始坐标值。负为上,正为下
  • android:toYDelta
    y的结束坐标值

需要注意的是,TranslateAnimation动画并不会改变View的位置布局属性。

例如,利用TranslateAnimation把一个Button改变了,点击移动后的Button是无效的,而点击Button移动前的原始空白位置会响应Button的点击事件。


2.2ScaleAnimation缩放动画

Android 动画基础知识学习
缩放动画

xml代码:

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:fillAfter="false"
    android:repeatMode="reverse">
    <scale
        android:fromXScale="0.5"
        android:fromYScale="0.5"
        android:pivotX="-100"
        android:pivotY="-100"
        android:repeatCount="2"
        android:toXScale="1"
        android:toYScale="1" />
</set>
  • android:fromXScale 水平方向的缩放值,数字代表比例。1.0是不缩放

  • android:fromYScale 垂直方向的缩放值

  • android:toXScale 水平方向的结束值

  • android:toYScale 垂直方向的结束值

  • android:pivotX 缩放的轴点的x轴的坐标。轴点为View的左上角

  • android:pivotY 缩放的轴点的y轴的坐标

默认情况下轴点为View的中心点

感觉书上这里和我实际测试有些出入,我感觉默认是View的左上角。不晓得是我哪里搞错了,希望可以指出。感觉坐标系就是自己上面画的那个1.0坐标图


2.2RotateAnimation旋转动画

Android 动画基础知识学习
旋转动画

xml代码:

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:repeatMode="reverse">
    <rotate
        android:fromDegrees="0"
        android:pivotX="235"
        android:pivotY="150"
        android:repeatCount="2"
        android:toDegrees="360" />
</set>
  • android:fromDegrees 旋转开始的角度
  • android:toDegrees 旋转结束的角度
  • android:pivotX 旋转的轴点的x坐标
  • android:pivotY 旋转的轴点的y坐标

2.4AlphaAnimation透明度动画

Android 动画基础知识学习
透明度动画

xml代码:

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:repeatMode="reverse">
    <alpha
        android:fromAlpha="1.0"
        android:repeatCount="2"
        android:toAlpha="0.1" />
</set>
  • android:fromAlpha 透明度的起始值,1.0代表最不透明,值越小越透明
  • android:toAlpha 透明度的结束值

3. 帧动画

帧动画是顺序播放一组预先定义好的图片,类似播放电影。需要用到AnimationDrawable这个类。

随便百度的吾王,一点没有表现出吾王美如画

Android 动画基础知识学习
帧动画

帧动画使用步骤:

  • 先在drawable文件下,定义一个animation-list文件,文件名字frames_animation.xml
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/a"
        android:duration="500" />
    <item
        android:drawable="@drawable/b"
        android:duration="500" />
    <item
        android:drawable="@drawable/c"
        android:duration="500" />
</animation-list>
  • Drawable作为View的背景播放
private void initView() {
    ImageView iv = (ImageView) findViewById(R.id.iv_frames_animation_activity);
    Button bt_start= (Button) findViewById(R.id.bt_start_frames_animation_activity);
    Button bt_stop= (Button) findViewById(R.id.bt_stop_frames_animation_activity);

    iv.setBackgroundResource(R.drawable.frames_animation);
    AnimationDrawable animation = (AnimationDrawable) iv.getBackground();

    bt_start.setOnClickListener((v)-> animation.start());
    bt_stop.setOnClickListener((v)->animation.stop());
}

帧动画使用很简单,但很容易出现OOM。尽量避免使用较大较多的图片。


4.View动画的特殊使用场景

View动画除了四种基本使用场景外,还可以在ViewGroup中控制子元素的出场效果,在Activity中可以实现不同Activity之间的切换效果。

4.1 LayoutAnimation简单介绍

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

挖坑:RecyclerViewitem的动画效果如何实现?

Android 动画基础知识学习
LayoutAnimation动画

使用步骤:

1.指定子View的动画

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:repeatMode="reverse">
    <rotate
        android:fromDegrees="0"
        android:pivotX="235"
        android:pivotY="150"
        android:repeatCount="2"
        android:toDegrees="360" />
</set>

用的是2.2旋转的动画


2. 定义LayoutAnimation

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/rotate_animation"
    android:animationOrder="reverse"
    android:delay="1" />
  • android:animation

    指定子元素入场显示的动画

  • android:animationOrder

    子元素动画的顺序。有: nomal,reverse,random
    nomal 顺序显示,排在前面的子元素先显示动画;
    reverse 逆序显示,排在后面的子元素先显示动画;
    random 随机显示子元素的动画

  • android:delay

    子元素开始动画的时间延迟。比如子元素的入场动画周期为300ms,0.5就表示每个子元素都需要延迟150ms。第一个进来延迟150ms,播放入场动画,第二个子元素延迟300ms播放入场动画。依次类推。


3.ViewGroup使用LayoutAniimation

采用布局文件的形式,指定android:layoutAnimation

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:layoutAnimation="@anim/layout_anim"
    android:orientation="vertical"
    >

    <ImageView style="@style/img" />

    <ImageView style="@style/img" />

    <ImageView style="@style/img" />

</LinearLayout>

也可以通过代码来实现:

Animation animation= AnimationUtils.loadAnimation(context,R.anim.resId);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(1);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
目标ViewGroup.setLayoutAnimation(controller);

4.2Activity的切换效果

使用overidePendingTransition(int enterAnim, int exitAnim)可以改变Activity的的默认切换效果。这个方法 必须在startActivity()或者finish()之后才有效果。

  • enterAnim Activity被打开时所需的动画资源id

  • exitAnim Activity暂停时所需的动画资源id

启动一个Activity时:

Intent intent = new Intent(MainActivity.this, activity);
startActivity(intent);
overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);

退出一个Activity时:

  @Override
  public void finish() {
     super.finish();
     overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);
}

1.属性动画

属性动画可以对任意对象的属性进行动画不仅仅是View,动画默认时间间隔是300ms,默认帧率是100ms/帧

作用:在一个时间间隔内完成对一个对象从属性值到另一个属性值的改变。

三个常用类:ValueAnimator,ObjectAnimator,AnimatorSet


Android 动画基础知识学习
属性动画

Java代码

private void initView() {
        Button bt = (Button) findViewById(R.id.bt_object_animation_activity);
        ImageView iv = (ImageView) findViewById(R.id.iv_object_animation_activity);

        //改变背景属性
        ValueAnimator colorAnim = ObjectAnimator.ofInt(iv, "backgroundColor", Color.parseColor("#FF4081"), Color.CYAN);
        colorAnim.setRepeatCount(2);
        colorAnim.setRepeatMode(ObjectAnimator.REVERSE);
        colorAnim.setDuration(1000);
        colorAnim.setEvaluator(new ArgbEvaluator());//估值器

        //动画集合
        AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(iv, "rotationX", 0, 360),//绕x轴旋转360度
                ObjectAnimator.ofFloat(iv, "rotation", 0, -90),//逆时针旋转90度
                ObjectAnimator.ofFloat(iv, "translationX", 0, 90),//右移
                ObjectAnimator.ofFloat(iv, "scaleY", 1, 0.5f),//y轴缩放到一半
                ObjectAnimator.ofFloat(iv, "alpha", 1, 0.25f, 1)//透明度变换
        );
        //延迟一秒开始
        set.setStartDelay(1000);

        bt.setOnClickListener((v) -> {
            //改变属性 位置  向下移动iv高的二分之一
            ObjectAnimator.ofFloat(iv, "translationY", iv.getHeight() / 2).start();
            //背景属性改变开始
            colorAnim.start();
            //集合动画
            set.setDuration(3000).start();
        });
}

轴点默认为View的中心点。

常用的propertyName

  • rotationX 围绕x轴旋转
  • rotationY 围绕y轴旋转
  • rotation 围绕轴点旋转
  • translationX 在x轴方向上平移
  • translationY 在y轴方向上平移
  • scaleX 在x轴方向缩放
  • scaleY 在y轴方向缩放
  • alpha 透明度
  • width 宽度
  • height 高度

1.2 常用方法介绍

Animation的api

1.2.1 ObjectAnimator

  • ObjectAnimator.ofFloat(Object target, String propertyName, float... values)

    Constructs and returns an ObjectAnimator that animates between float values.

    返回一个根据方法内的具体的values创建的ObjectAnimator对象

    1. Object target ,目标控件View的对象
    2. String propertyName,属性的名字
    3. float... values ,根据具体需求需要的属性值

  • ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)

    Constructs and returns an ObjectAnimator that animates between the sets of values specified in PropertyValueHolder objects

    返回一个由PropertyValueHolder对象创建的ObjectAnimator对象

    public void byPropertyValuesHolder(ImageView iv) {
          PropertyValuesHolder pvh_1 = PropertyValuesHolder.ofFloat("rotationX", 0, 360);
          PropertyValuesHolder pvh_2 = PropertyValuesHolder.ofFloat("rotation", 0, -90);
          PropertyValuesHolder pvh_3 = PropertyValuesHolder.ofFloat("alpha", 1, 0.25f, 1);
          ObjectAnimator.ofPropertyValuesHolder(iv, pvh_1, pvh_2, pvh_3).setDuration(1000).start();
    }

  • ofInt(Object target, String propertyName, int... values)

    Constructs and returns an ObjectAnimator that animates between int values.

    返回一个由int值属性创建的ObjectAnimator对象


  • setTarget(Object target)
    设置动画目标View

1.2.2 ValueAnimator

  • ValueAnimatorsetEvaluator(new ArgbEvaluator())
    设置估值器

  • addUpdateListener(ValueAnimator.AnimatorUpdateListener listener)

    Adds a listener to the set of listeners that are sent update events through the life of an animation.

    添加一个监听,可以用来在动画过程中改变属性


  • setRepeatCount(int num)
    设置动画的循环次数,默认为0,-1为无限循环

  • setStartDelay(long startDelay)
    设置动画开始的延迟时间


  • cancel()
    取消一个正在进行的动画。取消前,动画进行到哪个状态,取消后,就保持在那个状态。

  • end()
    结束动画。调用结束方法后,View会跳转到结束状态。如果动画设置了循环次数setRepeatCount()和重复模式setRepeatMode(ObjectAnimator.REVERSE),结束状态就要根据具体设置分析。


1.2.3 AnimatorSet

  • playTogether(Animator... items)

    Sets up this AnimatorSet to play all of the supplied animations at the same time.

    同时播放所有的Animator动画对象。


  • playSequentially(Animator... items)

    Sets up this AnimatorSet to play each of the supplied animations when the previous animation ends.

    顺序播放Animator动画对象

  • setInterpolator(TimeInterpolator interpolator)

    Sets the TimeInterpolator for all current child animations of this AnimatorSet.

    设置插值器


1.2.4 Animator

直接子类:AnimatorSet 和 ValueAnimator
间接子类:ObjectAnimator 和 TimeAnimator

Animator类的方法子类都可以直接使用。

  • addListener(Animator.AnimatorListener listener)

    Adds a listener to the set of listeners that are sent events through the life of an animation, such as start, repeat, and end.

    为动画添加一个监听过程的接口。如果不想实现AnimatorListener接口中的所有方法也可以继承AnimatorListenerAdapter

set.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animator) {
         toast("动画开始");
    }
    @Override
    public void onAnimationEnd(Animator animator) {
         toast("动画结束");
    }
    @Override
    public void onAnimationCancel(Animator animator) {
         toast("动画取消");
    }
    @Override
    public void onAnimationRepeat(Animator animator) {
        toast("动画重建");
    }
});

实现了接口中全部的方法。


继承AnimatorListenerAdapter:

set.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        toast("动画结束");
    }
});

根据实际需求,实现不同的方法。


  • addPauseListener(Animator.AnimatorPauseListener listener)

    Adds a pause listener to this animator

    为动画增加暂停监听


2.插值器和估值器

TimeInterpolator,时间插值器。用来根据时间流逝的百分比来计算出当前属性的值变化的百分比。

常用插值器:

名称 作用
LinearInterpolator 线性插值器。匀速动画
AccelerateDecelerateInterpolator 加速减速插值器
DecelerateInterpolator 匀减速插值器。动作越来越慢
BounceInterpolator 回弹插值器。到达平移后,回弹
CycleInterpolator 循环插值器。在两点间往还运动
PathInterpolator 路径插值器。根据单一方向定义的路径坐标运动
OvershootInterpolator 超越插值器。超出后,再返回来
AnticipateInterpolator 预期插值器。先反向运动再根据指定的方向运动

都是Interpolator的子类


TypeEvaluator,类型估值算法(估值器)。用来根据当前属性变化改变的百分比来计算改变后的属性值。

  • IntEvaluator,针对整型属性
  • FloatEvaluator,针对浮点型属性
  • ArgbEvaluator,针对Color属性

2.1简单Demo

Android 动画基础知识学习
模拟小球下落
private void initView() {
    Button bt = (Button) findViewById(R.id.bt_interpolator_activity);
    ImageView iv = (ImageView) findViewById(R.id.iv_interpolator_activity);
    LinearLayout root = (LinearLayout) findViewById(R.id.root_interpolator_activity);//根布局

    AnimatorSet set = new AnimatorSet();
    set.setInterpolator(new BounceInterpolator());
    set.setDuration(3000);
    //利用View的post方法拿到根布局的高度
    root.post(() -> {
        //计算下降高度
        int height = root.getHeight() - iv.getHeight() - bt.getHeight();
        //设置动画
        set.play(ObjectAnimator.ofFloat(iv, "translationY", height));
    });

    bt.setOnClickListener(v ->set.start());
    }

利用BounceInterpolator可以很方便的做出模拟小球下落的动画。也可以根据需求进行自定义插值器。


2.2 对任意属性做动画

Object的属性abc做动画,必须满足2个条件:

  1. object必须提供setAbc()的方法。如果动画的时候没有传递初始值,还要提供getAbc()方法。因为系统要去abc的初始值。如果不满足,程序直接Crash
  2. objectsetAbc对属性abc所做的改变必须能够通过某种方法反映出来,比如UI改变之类的。这条不满足,动画无效但程序不会Crash

2.2.1 改变Button的宽度

例如,想要利用属性对话来改变一个Button的宽度。

private void initView() {
    Button bt = (Button) findViewById(R.id.bt_button_activity);
    bt.setOnClickListener((v) -> performAnimate(bt));
}

private void performAnimate(Button bt) {
    ObjectAnimator.ofInt(bt, "width", 500).setDuration(1000).start();
}

实际测试,这段代码完全不起作用。


2.2.2不起作用的原因

Button继承的TextView

/**
 * Makes the TextView exactly this many pixels wide.
 * You could do the same thing by specifying this number in the
 * LayoutParams.
 *
 * @see #setMaxWidth(int)
 * @see #setMinWidth(int)
 * @see #getMinWidth()
 * @see #getMaxWidth()
 *
 * @attr ref android.R.styleable#TextView_width
 */
 @android.view.RemotableViewMethod
 public void setWidth(int pixels) {
    mMaxWidth = mMinWidth = pixels;
    mMaxWidthMode = mMinWidthMode = PIXELS;

    requestLayout();
    invalidate();
 }


 /**
  * Return the width of the your view.
  *
  * @return The width of your view, in pixels.
  */
  @ViewDebug.ExportedProperty(category = "layout")
  public final int getWidth() {
     return mRight - mLeft;
  }

getWidth()方法View的方法,是可以获取Button高度的。setWidth()方法TextView和子类的专属方法。是用来设置TextView的最大宽度和最小宽度的,并不是用来设置TextView的宽度的方法。TextView的宽度对应于XMLandroid:layout_widthsetWidth方法对应的是android:width

也就是说:ButtonsetWidth()getWidth()对应的就不是一个属性。只满足的条件1,不满足条件2


2.2.3 解决办法

有三种解决办法:

  • 如果有权限,给对象加上getset方法
  • 用一个类包装原始对象,间接提供getset方法
  • 采用ValueAnimation,监听动画过程,实现属性的改变

getset方法往往拿不到权限。

利用包装类方法:

private void initView() {
    Button bt = (Button) findViewById(R.id.bt_button_activity);
    bt.setOnClickListener((v) -> performAnimate(bt,bt.getWidth()));
}

 private void performAnimate(Button bt,int width) {
    ButtonWrapper wrapper = new ButtonWrapper(bt);
    wrapper.setWidth(width);
    ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(1000).start();
}

private static class ButtonWrapper {
    private View target;

    public ButtonWrapper(View target) {
        this.target = target;
    }

    public int getWidth() {
        return target.getLayoutParams().width;
    }

    public void setWidth(int width) {
        target.getLayoutParams().width = width;
        target.requestLayout();
    }
}

拿到Button的宽度后,设置给ButtonWrapper。这样动画开始后,Button会从原始大小开始变化。


利用ValueAnimation方法:

private void initView() {
    Button bt = (Button) findViewById(R.id.bt_button_activity);

    bt.setOnClickListener((v) -> performAnimate(bt,bt.getWidth(),500));
}

private void performAnimate(Button bt,int start, int end) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);

    //IntEvaluator对象,估值的时候使用
    IntEvaluator intEvaluator = new IntEvaluator();

    valueAnimator.addUpdateListener((animator -> {
        //获取当前动画的进度值 , 整型, 1到100
        int currentValue = (int) animator.getAnimatedValue();

        //获取当前进度的占整个动画过程的比例,浮点型, 0到1
        float fraction = animator.getAnimatedFraction();
        //直接利用整型估值器,通过比例计算宽度,然后Button设置
        bt.getLayoutParams().width = intEvaluator.evaluate(fraction,start,end);
        bt.requestLayout();
    }));
    //开启动画
    valueAnimator.setDuration(1000).start();
}

监控动画过程,利用IntEvaluator估值器


3.最后