动画篇(二)——android属性动画

本文讲介绍android在3.0之后推出的一种新的动画机制,属性动画,对动画不了解的同学,可以先去看看动画篇(一)——android动画基础这篇文章。

本人水平有限,文章中如果出现什么不正确或者模糊的地方,还请各位小伙伴留下评论,多多指教 : )

好了,现在我们进入正题。

基本概念

【android传统动画Animation与属性动画Animator的工作原理】

简单来说,传统动画就是不断的去调用系统的onDraw方法,去重新绘制组件,而属性动画则是通过调用属性的get和set方法重新设置控件的属性值,实现动画的效果

【Animator出现的原因】

既然已经存在了可以实现各种动画的方法了,为什么谷歌还要推出新的动画框架呢?为了解释这个问题,我们写一个小栗子。

首先是布局文件,很简单,就是一个imageView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>
</RelativeLayout>

接下来是java代码,我们找到这个view,为其添加点击事件,这里直接简单的Toast一下,表示view被点击;然后我们为这个view添加一个位移动画。代码如下:

package com.example.dell.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {
    private ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView= (ImageView) findViewById(R.id.image);
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this,"ImageView",Toast.LENGTH_SHORT).show();
            }
        });
        //参数1:X开始位置 参数2:x结束的位置
        //参数3:y开始位置 参数4:y结束的位置
        TranslateAnimation animation =new TranslateAnimation(0f,500f,0f,0f);
        animation.setDuration(2000);
        //动画结束后应用动画
        animation.setFillAfter(true);
        imageView.startAnimation(animation);
    }

}

我们来看看演示效果:
这里写图片描述

可以看到,当我们进入到Activity当中后,动画就已经完成了,大家注意一下,这段代码其实是有问题的,因为我们只看到了动画的结果,而没看到动画的过程,这样一来,动画设置的就毫无意义。为什么会出现这种情况呢?这涉及到了Activity生命周期的内容,答案将在本节最后揭晓。

现在这时我先点的imageview完成动画后的位置(0f,100f)发现并没有弹出Toast,然后我们再去点击imageView的初始位置(0f,0f),有意思的事情发生了,Toast被弹出来了

那么这些说明了什么呢?

说明了我们在使用Animation的时候,虽然可以改变动画在界面上显示的位置,但是却不能改变点击事件所在的位置

到这里,传统Animation一个很大的局限性,它只是重绘了动画,改变了显示的位置,但是真正事件响应的位置,却没有发生任何改变。所以,Animation并不适合制作具有交互的动画效果。它只能用来完成一些显示性的效果

那么下面,我们就来列一下传统Animation的局限性

【Animation的局限性】
  • 第一点就是上面所说的内容,不在啰嗦了。
  • 因为Animation的工作原理,上面介绍了,是不断调用系统的onDraw()方法去绘制图像,那么必然十分耗费GPU,效率不高
  • Animation提供了位移、旋转、透明度、缩放这四种动画,虽然经过组合,可以创造出很多的动画效果,但是有时候依然无法制作出复杂好看的动画,即存在动画效果上的局限。

为解决以上的局限性,Google在android3.0后推出了属性动画。那么接下来我们就正式进入属性动画的内容。

在这之前,我们似乎还有一个小问题没有解决,那就是为什么上一个栗子的动画没有显示出来呢?

答案是,动画的启动写在了onCreate()方法当中,而在这之后,还有onStart(),onResume()方法,而用户真正看到界面的时候,onCreate方法早已经调用完毕了,此时如果动画的持续时间过短,那么用户看到界面时动画自然已经结束了。解决的办法也非常简单,只需要把动画的启动逻辑放在onResume() (活动准备好和用户进行交互的时候 )方法当中即可

补充:经过测试,发现在onResume()方法当中也会出现一些延时,这因为机器性能的问题,启动一个活动的时间长短不一,即便调用了onResume()方法,距离Activity的启动可能还有一段时间。但是这种写法肯定要比在onCreate()方法中启动动画要好一些。

属性动画——ObjectAnimator

【“translationX”】

现在我们在上面栗子的基础上增加一个button,将动画的启动逻辑放在里面,这样就能避免刚才动画显示的问题。

更改后的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>
    <Button
        android:id="@+id/bt_move"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="move"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

接下来是java代码

public class MainActivity extends Activity {
    private ImageView imageView;
    private TranslateAnimation animation;
    private Button bt_move;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView= (ImageView) findViewById(R.id.image);
        bt_move= (Button) findViewById(R.id.bt_move);
        //view的点击事件
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this,"ImageView",Toast.LENGTH_SHORT).show();
            }
        });
        //button的点击事件
        bt_move.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //使用ObjectAnimator
                //第一个参数是被操纵的view对象;第二个参数是被操作的属性
                //第三,第四个参数是可变长的数,表示熟悉所变化的范围
                ObjectAnimator objectAnimator= ObjectAnimator.ofFloat(imageView,"translationX",0f,200f);
                //设置显示时长
                objectAnimator.setDuration(2000);
                //让动画开始
                objectAnimator.start();

            }
        });

    }

}

我们看一下效果
这里写图片描述

这里很明显,view的点击事件也发生了相应的变化。

【“translationY”】

想要改变Y上的属性,也很简单,代码如下:

ObjectAnimator objectAnimator= ObjectAnimator.ofFloat(imageView,"translationY",0f,200f);
    //设置显示时长
   objectAnimator.setDuration(2000);
   //让动画开始
   objectAnimator.start();

看到这里,有的小伙伴可能会有疑问,到底那些属性是我们可以去设置的呢?其实只要是Google定义了的可以通过set和get去操纵的属性,我们都可以在属性动画中对其进行设置。

【“X和Y”】

前面已经讲了translationX和translationY,那么现在出现的X和Y又是什么东西呢?

translationX是指的是物体的偏移量,而X是指物体最后到达的一个绝对值,即具体移动到的坐标

【“rotation”】

旋转属性,旋转360度

ObjectAnimator objectAnimator= ObjectAnimator.ofFloat(imageView,"rotation",0f,360f);
                //设置显示时长
                objectAnimator.setDuration(2000);
                //让动画开始
                objectAnimator.start();

以此类推,可以操作的属性还有很多,凡是可以通过get或者set设置的属性,都可以设置成属性动画。

【组合动画】

现在我们使用ObjectAnimator来进行多个动画效果的组合

  ObjectAnimator.ofFloat(imageView,"translationY",0f,100f).setDuration(1000).start();
                ObjectAnimator.ofFloat(imageView,"translationX",0f,100f).setDuration(1000).start();
                ObjectAnimator.ofFloat(imageView,"rotation",0f,360f).setDuration(1000).start();

效果图:
这里写图片描述
可以发现,这三个动画的效果是同时完成的,因为在调用start()方法之后,实际上是一个异步的过程。

实际上,我们还有更好的方法去实现这种组合效果,我们对上面的写法进行优化:

       PropertyValuesHolder p1=PropertyValuesHolder.ofFloat("translationY",0f,100f);
                PropertyValuesHolder p2=PropertyValuesHolder.ofFloat("translationX",0f,100f);
                PropertyValuesHolder p3=PropertyValuesHolder.ofFloat("rotation",0f,360f);
                //通过ObjectAnimator来调用ofPropertyValuesHolder()方法
                //第一个参数传递view,后续的参数为可变长的数组
                ObjectAnimator.ofPropertyValuesHolder(imageView,p1,p2,p3).setDuration(1000).start();

这里我们使用了一个PropertyValuesHolder的容器来容纳3个动画效果,然后在最后调用ObjectAnimator的ofPropertyValuesHolder()方法来加载之前定义的三个holder。那么这样写的好处是什么呢?Google在PropertyValuesHolder这个类中对动画进行了一些优化,这些优化使得我们在使用多个动画属性的时候能够更加有效率,更加节省系统资源。

在前面的动画基础当中,有一个动画集合的概念,那么在属性动画当中其实也有这么一个集合的概念。下面我们就用AnimatorSet来实现上述的效果。

代码如下:

AnimatorSet animatorSet=new AnimatorSet();
                //接下来将单个动画添加到AnimatorSet当中
                animatorSet.playTogether(animator1,animator2,animator3);
                animatorSet.setDuration(1000);
                animatorSet.start();

当然在AnimatorSet方法当中我们还有更多的选择去控制动画。从animatorSet.playTogether()这个方法的名字中就能看出,该方法是让所有的动画同时起效果,我们才看到了和刚才几种方法实现的同样的效果。这里google还提供了playSequentially()方法,该方法则是按顺序去播放动画。大家可以试一下。

我们还可以使用play()方法,这里我们实现view在X和Y轴上同时平移,结束之后再旋转360度。

代码如下:

                ObjectAnimator animator1= ObjectAnimator.ofFloat(imageView,"translationY",0f,100f);
                ObjectAnimator animator2= ObjectAnimator.ofFloat(imageView,"translationX",0f,100f);
                ObjectAnimator animator3= ObjectAnimator.ofFloat(imageView,"rotation",0f,360f);

                AnimatorSet animatorSet=new AnimatorSet();
                //animator1和Animator2同时播放
                animatorSet.play(animator1).with(animator2);
                //Animator3在Animator1或者Animator2结束后播放
                animatorSet.play(animator3).after(animator2);
                animatorSet.setDuration(1000);
                animatorSet.start();

这样一来就实现了对每个动画更加细致的控制。通过play(),with(),after(),before()方法,我们就能做到对一个属性集合的详细的顺序控制。这种方式,也是属性动画框架中使用最多的一种配和。

接下来总结一下

(1)通过ObjectAnimator进行更精细的控制,只控制一个对象的一个属性。
(2)同时多个ObjectAnimator组合到AnimatorSet当中,可以形成一个完整的动画效果。
(3)而且AnimatorSet可以自动驱动,可以去调用play(),with(),after(),before,playTogether(),playSequentially()实现更为丰富的动画效果。

动画事件监听

顾名思义,和一般的点击事件差不多,我们为ObjectAnimator设置监听事件,以满足实际开发当中的需求。

       bt_move.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

  ………………

                ObjectAnimator bt_animator=ObjectAnimator.ofFloat(view,"alpha",0f,1f);
                bt_animator.setDuration(1000);
                  bt_animator.addListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animator) {
                    //动画开始前调用
                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                    //动画结束后调用,为简单起见,这里我们只简单的提示一行字
                        Toast.makeText(MainActivity.this,"anim is end",Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onAnimationCancel(Animator animator) {
                    //动画被取消后
                    }

                    @Override
                    public void onAnimationRepeat(Animator animator) {
                    //动画重复时调用
                    }
                });
                bt_animator.start();

…………
            }
        });

我们在上一个栗子的基础上为我们的Button添加一个alpha动画,让其从透明变成不透明,然后在这个动画里面加入了一个监听事件,在监听事件当中,我们看到了4个需要重写的方法,通过这4个方法,我们就可以监听动画在不同事件段所需要完成的操作。

演示效果:
这里写图片描述

现在我们在考虑一种情况,那就是如果我们并不需要重写那么多的方法该怎么办呢?这时可以使用android系统提供的一个更方便的接口AnimatorListenerAdapter(),大家可以发现,系统帮我们实现了很多方法,这里我们只需要添加需要重写的方法即可

代码如下:

 bt_animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        Toast.makeText(MainActivity.this,"anim is end",Toast.LENGTH_SHORT).show();
                    }
                });

这样我们就实现了对某一个事件的监听,而不需要写出所有的事件。

总结一下

看到出来动画监听事件还是比较简单的,我们只需要调用ObjectAnimator的addListener()方法,就能为我们的Animator增加一个监听事件,接着我们可以通过 AnimatorListenerAdapter这个类,去有选择的去选取我们需要监听的事件。

小栗子

这里的栗子来自于慕课网的课程,有兴趣的小伙伴可以去看看。

在开始写这个栗子之前,请各位小伙伴去下载一下素材,其中a.png的尺寸有点问题,改为58*58即可

演示效果:
这里写图片描述
接下来让让我们看看布局文件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/view_b"
        android:src="@drawable/b"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/view_c"
        android:src="@drawable/c"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/view_d"
        android:src="@drawable/d"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/view_e"
        android:src="@drawable/e"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/view_f"
        android:src="@drawable/f"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/view_g"
        android:src="@drawable/g"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/view_h"
        android:src="@drawable/h"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <ImageView
        android:id="@+id/view_a"
        android:src="@drawable/a"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</FrameLayout>

里面定义了我们需要用到的imageView
接下来是比较关键的java代码:

public class MainActivity extends Activity implements View.OnClickListener {
    //定义每一个图片资源
    private int[] res = {R.id.view_a, R.id.view_b, R.id.view_c, R.id.view_d, R.id.view_e, R.id.view_f, R.id.view_g,
            R.id.view_h};
    //存储viewd的list集合
    private List<ImageView> imageViewList = new ArrayList<>();
    //设置一个flag来标示列表是否展开
    private boolean flag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        for (int i = 0; i < res.length; i++) {
            //实例化imageView
            ImageView imageView = (ImageView) findViewById(res[i]);
            //添加点击事件
            imageView.setOnClickListener(this);
            //将每一个ImageView添加到list当中
            imageViewList.add(imageView);
        }

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.view_a:
                //点击最上面的图片,显示展开动画
                //动画方法
                if (flag) {
                    //列表未展开
                    startAnimation();
                    flag = false;
                } else {
                    //列表已展开
                    stopAnimation();
                    flag = true;
                }
                break;
            default:
                //其他按钮
                Toast.makeText(this, "clicked", Toast.LENGTH_SHORT).show();
                break;
        }
    }

    private void stopAnimation() {
        for (int i = 1; i < res.length - 1; i++) {
            ObjectAnimator animator = ObjectAnimator.ofFloat(imageViewList.get(i), "translationY", 200 * i, 0);
            animator.setDuration(500);
            //设置插值器
            animator.setInterpolator(new AnticipateInterpolator());
            animator.start();
        }
    }

    private void startAnimation() {
        for (int i = 1; i < res.length - 1; i++) {
            ObjectAnimator animator = ObjectAnimator.ofFloat(imageViewList.get(i), "translationY", 0f, 200 * i);
            animator.setDuration(500);
            //设置延迟,让动画更加丰富
            animator.setStartDelay(i * 100);
            //设置插值器
            animator.setInterpolator(new OvershootInterpolator());
            animator.start();
        }
    }
}

上述代码当中有非常详细的注释,这里就不多解释了。为了让动画效果更棒,有时我们还可以为动画添加差插器(interpolator)。android内置的插值器有如下
- Accelerate
- Decelerate
- Accelerate/Decelerate、
- Overshoot
- Bounce
通过插值器,我们让某一属性在数值上的变化时,可以拥有不同的加速曲线,进而让我们的动画更加丰富。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值