Android 中的属性动画 --- 1(基本用法)

动画在提高用户体验里面起了巨大的作用,可以说是提高用用户体验的“主力军”。在 Android 3.0 之前,视图动画几乎承担了所有的动画效果,但是视图动画有一个很大的局限性:它改变的只是某个 View 的外观。但是响应事件位置并没有随着 View 的改变而改变。举个 case 来说,现在有一个按钮通过视图动画在 x 轴方向上向右移动了 200 px(像素) 的距离,按钮显示的位置虽然改变了,但是点击移动后的按钮并不能相应点击事件,只有点击这个按钮没有移动之前的位置才能响应这个按钮的点击事件。由于这个巨大的局限性,Google 在 Android 3.0 以上添加了一个新的动画框架:属性动画。下面来一起看一下属性动画的用法:

如果你熟悉视图动画的使用方法,那么属性动画的用法一定没问题。我们先来看一下属性动画最简单的用法。 新建一个 Android 工程:

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    tools:context="com.company.zhidian.androidobjectanimator.MainActivity">

    <Button
        android:id="@+id/startAnimatorButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="启动动画"/>

</LinearLayout>

so easy 的布局,下面是 MainActivity.java:

import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private Button startAnimatorButton = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startAnimatorButton = (Button) findViewById(R.id.startAnimatorButton);
        startAnimatorButton.setOnClickListener(clickListener);
    }

    // 开始播放动画的方法
    private void startAnimator() {
        /**
         * 新建一个 ObjectAnimator 对象,第一个参数为动画执行的对象,第二个参数是对象要改变的的属性,
         * 这里相当于对象在 x 轴上的平移效果,第三个参数是一个可变数组,这里代表的意思是从距离对象 x 轴
         * 方向上为 0 px 的位置(即为初始位置)开始在 x 轴上平移到距离对象 x 轴初始位置为 +200 px 的位置,
         * 然后再从这个位置移动到距离对象初始位置 x 轴方向上 -100 px 的位置,
         * 这里的数字都是以对象的初始位置作为参考,
         * 我们可以设置多个数字,那么动画就会按给定数字一直移动,直到所有数字都被使用完
         */
        ObjectAnimator animator = ObjectAnimator.ofFloat(startAnimatorButton,
                "translationX", 0, 200, -100);
        // 设置动画的持续时间,即多长时间内将这段动画执行完成
        animator.setDuration(4000);
        animator.start();
    }

    View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(final View view) {
            if(view == startAnimatorButton) {
                startAnimator();
            }
        }
    };
}

其实逻辑也非常简单:首先为按钮设置点击事件,然后我们自定义了一个方法: startAnimator() ,在里面定义了一个属性动画对象并且设置相关属性,当按钮被点击的时候就会启动这个动画。下面来看看运行效果:

这是一个简单的平移动画,当按钮移动完成之后,我们再次点击这个按钮(明显它已经不在原来的位置)时,它仍然响应了点击事件,重新开始了动画,这证明属性动画确实是“原原本本的对 View 本身进行操作”。

ok,接下来,难道只能支持平移这一种动画效果吗?答案显然是 NO。我们还可以对其他的属性进行操作:

translationX 和 translationY:这两个属性上面已经演示过了一个,另一个我想小伙伴们已经知道了。

rotation、rotationX 和 rotationY :这三个属性控制 View 对象围绕着支点、 x 轴和 y 轴进行旋转。

scaleX 和 scaleY : 这两个属性控制 View 对象围绕支点进行缩放。

x 和 y : 这两个属性控制 View 对象相对于父容器的最终位置

alpha :控制 View 对象的透明度(0~1),0 表示完全透明,1表示完全透明

下面更改我们 startAnimator() 方法的代码:

ObjectAnimator animator = ObjectAnimator.ofFloat(startAnimatorButton,
                "rotation", 0, 300);

把操作的属性更改为 scaleX 试试:

ObjectAnimator animator = ObjectAnimator.ofFloat(startAnimatorButton,
                "scaleX", 0, 2);

这里就演示到这里了,关于剩下的属性有兴趣的小伙伴可以自己试试。

为什么这里我们直接就可以使用这些属性呢?归根结底还是因为 View 对象本身就对这些属性存在这个 get… 和 set… 方法,因为存在这些方法,属性动画对象可以调用相应的 get… 或者 set.. 方法对 View 的属性进行设置完成整个动画。对于上面演示的动画效果,View 类中本身提供了 setTranslationX、setRotation、setAlpha 和 getTranslationX、getRotation、getAlpha 方法,因此objectAnimation 对象可以直接调用这些方法来控制给定的 View 对象。那么对于那些 View 对象本身没有直接提供 get… 和 set… 方法的属性呢?我们可以有两个方法来解决这个问题:1、自定义一个属性类,将我们要操作的 View 对象属性包装起来,并提供对应属性的 get… 和 set… 方法。2 是通过对 ValueAnimator 的监听来实现。我们先看一下第一个方法:

除了 View 中的这些直接有 get… 和 set… 方法的属性之外,我们最常用的属性应该是 width 和 height 了。下面自定义一个类 MyView.java:

import android.view.View;

/**
 * Created by 指点 on 2017/5/3.
 */

public class MyView {

    private View targetView = null;

    public MyView(View view) {
        targetView = view;
    }

    public int getHeight() {
        return targetView.getHeight();
    }

    public void setHeight(int height) {
        // 获取 targetView 的布局参数并设置其 height 属性
        targetView.getLayoutParams().height = height;
        // 重新申请布局
        targetView.requestLayout();
    }
}

这样,我们对 targetView 对象的 height 属性添加了 getHeight() 和 setHeight 方法,接下来在 MainActivity 中使用: 我们只需要改一下 startAnimator 方法中的部分代码:

 MyView myView = new MyView(startAnimatorButton);
 ObjectAnimator animator = ObjectAnimator.ofInt(myView,
        "height", 0, 200);

需要注意的我们动画定义中: ObjectAnimator animator = ObjectAnimator.ofInt(myView,"height", 0, 200); of… 方法后面的数据类型必须和自定义的 get.. 和 set… 方法的返回值和参数数据类型相同,不然属性动画就无效了。 比如说我们这里自定义的的 get… 和 set… 方法返回和参数的数据类型是 int, 那么我们就要使用 ObjectAnimator.ofInt(…); 方法来定义属性动画,其实对于 view 里面提供的属性也是这个道理,小伙伴们有兴趣可以去看一下 View 对象里面的那些直接能使用的属性的 get… 和 set… 方法的返回值和参数的数据类型。这个我想很好理解。 OK了,let’s run it :

上面是使用自定义类的方式来包装 View 对象并提供操作对应属性的方法,下面看看如何使用 ValueAnimator:

ValueAnimator 是属性动画的核心部分,为什么这么说呢? ValueAnimator 本身不提供任何动画效果,它的任务只是依据动画的完成度和某个公式(插值器,下一篇文章会介绍)来提供具有一定规律的数字,之后动画的执行者获取到这个数字之后设置 View 对象的要操作的属性(即通过提供的 set… 方法)来改变 View 对象的属性进而产生动画效果。我们也可以通过监听 ValueAnimator 并且获取其产生的数字来自己完成动画效果。修改 MainActivity.java 中 startAnimator 方法中的代码:

private void startAnimator() {
    // 新建 ValueAnimator 对象并且设置其产生数字的范围
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 200);
    valueAnimator.setDuration(3000).start();
    // 为 ValueAnimator 设置数字更新监听器,用于读取其产生变化后的数字
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 获取改变后的数字
            int value = (int) animation.getAnimatedValue();
            // 获取 startAnimatorButton 的布局参数并设置其 height 属性
            startAnimatorButton.getLayoutParams().height = value;
            startAnimatorButton.requestLayout();
        }
    });
}

ok,试试效果:

nice,同样的完成了相应功能

当然,我们还可以设置属性动画的其他属性:重复次数: ObjectAnimator.setRepeatCount(int value); 重复模式:ObjectAnimator.setRepeatMode(int value); 这个属性设置动画的重复的时候是重新开始还是反向开始(动画的逆动画)等效果。

那么接下来,如果我们要对动画的状态进行监听在呢么办呢?比如说我要在某个动画结束的时候执行另一个操作。属性动画中有一个 addListener 方法来对动画的状态进行监听,修改 startAnimator 方法的代码:

private void startAnimator() {
        // 新建 ValueAnimator 对象并且设置其产生数字的范围
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 200);
        // 设置动画重复一次
        valueAnimator.setRepeatCount(1);
        // 设置动画重复模式为逆动画(反向开始)
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.setDuration(3000);
        // 为 ValueAnimator 设置数字更新监听器,用于读取其产生变化后的数字
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 获取改变后的数字
                int value = (int) animation.getAnimatedValue();
                // 获取 startAnimatorButton 的布局参数并设置其 height 属性
                startAnimatorButton.getLayoutParams().height = value;
                startAnimatorButton.requestLayout();
            }
        });
        /**
         * 设置动画状态的监听事件,其中提供了多个方法用于监听动画的不同状态,我们可以按需求实现里面的方法
         */
        valueAnimator.addListener(new AnimatorListenerAdapter() {

            // 动画结束的时候调用这个方法
            @Override
            public void onAnimationEnd(Animator animation) {
                Toast.makeText(MainActivity.this, "动画结束", Toast.LENGTH_SHORT).show();
            }

            // 动画重复的时候调用
            @Override
            public void onAnimationRepeat(Animator animation) {
                Toast.makeText(MainActivity.this, "动画重复", Toast.LENGTH_SHORT).show();
            }

            // 动画开始的时候调用
            @Override
            public void onAnimationStart(Animator animation) {
                Toast.makeText(MainActivity.this, "动画开始", Toast.LENGTH_SHORT).show();
            }
        });
        // 启动动画
        valueAnimator.start();
    }

run it :

属性动画当然也能在在 xml 文件中声明。如何在 xml 文件中声明属性动画呢?其实和视图动画的步骤差不多: 在 res 文件夹中新建一个anim 文件夹,然后在 anim 文件夹中新建一个 xml 文件 rotation_animator.xml :

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:propertyName="rotation"
    android:valueFrom="0.0"
    android:valueTo="720.0"
    android:valueType="floatType"
    android:duration="4000">

</objectAnimator>

其属性对应的含义和通过代码创建的属性动画的参数差不多,我想小伙伴们应该能看懂。如何在代码中使用这个属性动画呢? 修改 MainActivity.java 中 startAnimator 方法的代码:

private void startAnimator() {
    // 通过 AnimatorInflater 的静态方法 loadAnimator 方法来从 xml 文件中加载属性动画:
    Animator animator = AnimatorInflater.loadAnimator(this, R.animator.rotation_animator);
    animator.setTarget(startAnimatorButton);
    /**
     * 设置动画状态的监听事件,其中提供了多个方法用于监听动画的不同状态,我们可以按需求实现里面的方法
     */
    animator.addListener(new AnimatorListenerAdapter() {

        // 动画结束的时候调用这个方法
        @Override
        public void onAnimationEnd(Animator animation) {
            Toast.makeText(MainActivity.this, "动画结束", Toast.LENGTH_SHORT).show();
        }

        // 动画重复的时候调用
        @Override
        public void onAnimationRepeat(Animator animation) {
            Toast.makeText(MainActivity.this, "动画重复", Toast.LENGTH_SHORT).show();
        }

        // 动画开始的时候调用
        @Override
        public void onAnimationStart(Animator animation) {
            Toast.makeText(MainActivity.this, "动画开始", Toast.LENGTH_SHORT).show();
        }
    });
    // 启动动画
    animator.start();
}

利用 AnimatorInflater 这个类我们可以轻松获取定义在 xml 文件中的属性动画,来看看效果:

因为这里的动画并没有设置重复,因此动画状态监听器中监听动画重复的方法并没有被调用。

最后,如果要同时播放多个动画怎么办呢?Android 属性动画框架给我们提供了一个 AnimatorSet 类来实现,修改 Mainactivity.java 中 startAnimator 方法的代码:

private void startAnimator() {
    private void startAnimator() {
        // 新建三个属性动画对象,分别改变 View 对象的 x 、y 坐标和旋转角度:
        ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(startAnimatorButton, "x", 0, 100, 0);
        objectAnimator1.setDuration(3000);
        ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(startAnimatorButton, "y", 0, 100, 0);
        objectAnimator2.setDuration(4000);
        ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(startAnimatorButton, "rotation", 0, 360);
        objectAnimator3.setDuration(2000);

        AnimatorSet animatorSet = new AnimatorSet();
        // 设置三个属性动画同时开始播放:
        animatorSet.playTogether(objectAnimator1, objectAnimator2, objectAnimator3);
        animatorSet.start();
    }
}

试试效果:

当然,我们可以利用 AnimatorSet 类中的其他方法(AnimatorSet.playTogether、AnimatorSet.play().width(..) 等方法)来更加精确的控制属性动画的播放顺序。小伙伴们可以自己试试。

我们当然可以通过 xml 的方式使用 AnimationSet:在 res/anim 文件夹中新建一个文件 animation_set.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 平移动画,在 2s 内 x y 分别向右和向下移动 100 px,无限次重复,重复模式为来回重复-->
    <translate
        android:duration="2000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:repeatCount="-1"
        android:repeatMode="reverse"
        android:toXDelta="100"
        android:toYDelta="100"/>
    <!-- 平移动画,在 2s 内 view 以 view 的中心点为支点,view 的宽高缩放为原来的两倍,无限次重复,重复模式为来回重复-->
    <scale
        android:duration="2000"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="-1"
        android:repeatMode="reverse"
        android:toXScale="2.0"
        android:toYScale="2.0"/>
</set>

注意使用 set 标签来包裹多个动画,当然 set 标签中可以嵌套 set 标签,意为动画集合的嵌套。

同样的修改 MainActivity.java 中的 startAnimation 方法:

 // 开始播放动画的方法
    private void startAnimator() {
        AnimationSet animationSet = (AnimationSet) AnimationUtils.loadAnimation(
                this, R.anim.anmation_set);
        startAnimatorButton.startAnimation(animationSet);
    }

效果:

对于 View 对象本身已经给出了 get… 和 set… 的属性,我们可以直接使用 view.animate() 方法来直接驱动 View 的属性动画,适用于一些简单的动画操作。改一下 startAnimator 方法的代码:

private void startAnimator() {
    // 使用 animate 方法来直接驱动属性动画:
    startAnimatorButton.animate().setDuration(2000).rotation(360).withStartAction(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(MainActivity.this, "动画开始", Toast.LENGTH_SHORT).show();
        }
    }).withEndAction(new Runnable() {
        @Override
        public void run() {
            Toast.makeText(MainActivity.this, "动画结束", Toast.LENGTH_SHORT).show();
        }
    }).start();
}

原理都是一样的,下面来看看结果:

好了,关于属性动画的第一部分就是这些了。 如果博客中有什么不正确的地方,还请多多指点,如果觉得我写的不错,那么请点个赞支持我吧。

谢谢观看。。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券