自定义控件三部曲之动画篇(四)——ValueAnimator基本使用
转载请标明出处:http://blog.****.net/harvic880925/article/details/50525521
一、概述
long long ago,我写过几篇有关Animation的文章,讲解了传统的alpha、scale、translate、rotate的用法及代码生成方法。其实这三篇文章讲的所有动画效果叫做Tween Animation(补间动画)
在Android动画中,总共有两种类型的动画View Animation(视图动画)和Property Animator(属性动画);
其中
- View Animation包括Tween Animation(补间动画)和Frame Animation(逐帧动画);
- Property Animator包括ValueAnimator和ObjectAnimation;
首先,直观上,他们有如下三点不同:
1、引入时间不同:View Animation是API Level 1就引入的。Property Animation是API Level 11引入的,即Android 3.0才开始有Property Animation相关的API。
2、所在包名不同:View Animation在包android.view.animation中。而Property Animation API在包 android.animation中。
3、动画类的命名不同:View Animation中动画类取名都叫XXXXAnimation,而在Property Animator中动画类的取名则叫XXXXAnimator
大家都知道逐帧动画主要是用来实现动画的,而补间动画才能实现控件的渐入渐出、移动、旋转和缩放的;而Property Animator是在Android 3.0版本才引入的,之前是没有的。大家可能会觉得补间动画和逐帧动画已经很全了,为什么还要引入Property Animator呢?
1、为什么引入Property Animator(属性动画)
我提出一个假设:请问大家,如何利用补间动画来将一个控件的背景色在一分钟内从绿色变为红色?这个效果想必没办法仅仅通过改变控件的渐入渐出、移动、旋转和缩放来实现吧,而这个效果是可以通过Property Animator完美实现的这就是第一个原因:Property Animator能实现补间动画无法实现的功能
大家都知道,补间动画和逐帧动画统称为View Animation,也就是说这两个动画只能对派生自View的控件实例起作用;而Property Animator则不同,从名字中可以看出属性动画,应该是作用于控件属性的!正因为属性动画能够只针对控件的某一个属性来做动画,所以也就造就了他能单独改变控件的某一个属性的值!比如颜色!这就是Property Animator能实现补间动画无法实现的功能的最重要原因。
我们得到了第二点不同:View Animation仅能对指定的控件做动画,而Property Animator是通过改变控件某一属性值来做动画的。
假设我们将一个按钮从左上角利用补间动画将其移动到右下角,在移动过程中和移动后,这个按钮都是不会响应点击事件的。这是为什么呢?因为补间动画仅仅转变的是控件的显示位置而已,并没有改变控件本身的值。View Animation的动画实现是通过其Parent View实现的,在View被drawn时Parents View改变它的绘制参数,这样虽然View的大小或旋转角度等改变了,但View的实际属性没变,所以有效区域还是应用动画之前的区域;我们看到的效果仅仅是系统作用在按钮上的显示效果,利用动画把按钮从原来的位置移到了右下角,但按钮内部的任何值是没有变化的,所以按钮所捕捉的点击区域仍是原来的点击区域。(下面会举例来说明这个问题)
这就得到了第三点不同:补间动画虽能对控件做动画,但并没有改变控件内部的属性值。而Property Animator则是恰恰相反,Property Animator是通过改变控件内部的属性值来达到动画效果的
2、举例说明补间动画的点击区域问题
下面我们就利用TranslateAnimation来做一个移动动画的例子,看它的点击区域是否会变。我们先来看看效果:
在效果图中,首先,我给textview添加了点击响应,当点击textview时,会弹出Toast。
然后,当我点击按钮的时候,textview开始向右下角移动。
从结果中可以看出,在移动前,点击textview是可以弹出toast的的,在移动后,点击textview时则没有响应,相反,点击textview的原来所在区域则会弹出toast.
这就论证了不同第三点:补间动画虽能对控件做动画,但并没有改变控件内部的属性值
下面简单看看这个动画的实现代码吧:
(1)、看布局(main.xml)
从效果图中也可以看出,布局很简单,一个button,一个textview,垂直排列,布局代码如下:- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <Button
- android:id="@+id/btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dp"
- android:text="start anim"
- />
- <TextView
- android:id="@+id/tv"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dp"
- android:background="#ffff00"
- android:text="Hello qijian"/>
- </LinearLayout>
(2)JAVA代码(MyActivity.java)
接下来是操作代码,就是分别给button和textview添加上点击响应,当点击textview时弹出toast,点击button时,textview使用移动。代码如下:
- public class MyActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- final TextView tv = (TextView) findViewById(R.id.tv);
- Button btn = (Button)findViewById(R.id.btn);
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- final TranslateAnimation animation = new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400,
- Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400);
- animation.setFillAfter(true);
- animation.setDuration(1000);
- tv.startAnimation(animation);
- }
- });
- tv.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Toast.makeText(MyActivity.this,"clicked me",Toast.LENGTH_SHORT).show();
- }
- });
- }
- }
二、ValueAnimator简单使用
我们前面讲了Property Animator包括ValueAnimator和ObjectAnimator;这篇文章就主要来看看ValueAnimator的使用方法吧。我觉得谷歌那帮老头是最会起名字的人,单从命名上,就能看出来这个东东的含义。ValueAnimator从名字可以看出,这个Animation是针对值的!ValueAnimator不会对控件做任何操作,我们可以给它设定从哪个值运动到哪个值,通过监听这些值的渐变过程来自己操作控件。以前我们曾讲过Scroller类,Scroller类也是不会对控件操作的,也是通过给他设定滚动值和时长,它会自己计算滚动过程,然后我们需要监听它的动画过程来自己操作控件,ValueAnimator的原理与Scroller类相似。有关Scroller的知识,大家可以参考:《 ListView滑动删除实现之四——Scroller类与listview缓慢滑动》
1、初步使用ValueAnimator
要使用ValueAnimaiton,总共有两步:第一步:创建ValueAnimator实例
- ValueAnimator animator = ValueAnimator.ofInt(0,400);
- animator.setDuration(1000);
- animator.start();
第二步:添加监听
上面的三行代码,我们已经实现了动画,下面我们就添加监听:- ValueAnimator animator = ValueAnimator.ofInt(0,400);
- animator.setDuration(1000);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int curValue = (int)animation.getAnimatedValue();
- Log.d("qijian","curValue:"+curValue);
- }
- });
- animator.start();
这就是ValueAnimator的功能:ValueAnimator对指定值区间做动画运算,我们通过对运算过程做监听来自己操作控件。
总而言之就是两点:
- ValueAnimator只负责对指定的数字区间进行动画运算
- 我们需要对运算过程进行监听,然后自己对控件做动画操作
2、实例使用ValueAnimator
这段,我们就使用上面我们讲到的ValueAnimator做一个动画:我们先看看效果图:
首先这个动画的布局与上一个实例是一样的。但实现的效果确不大相同:
- 首先,点击按钮后,textview从屏幕(0,0)点运动到(400,400)点
- 运动前后,textview都是可以响应点击事件的
1、布局(main.xml)
布局代码与上个例子相同:垂直布局按钮控件和textview- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <Button
- android:id="@+id/btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dp"
- android:text="start anim"
- />
- <TextView
- android:id="@+id/tv"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dp"
- android:background="#ffff00"
- android:text="Hello qijian"/>
- </LinearLayout>
2、JAVA操作
首先,是对textview和btn添加点击响应,当点击textview时,弹出toast;点击btn时,textview开始做动画- public class MyActivity extends Activity {
- private TextView tv;
- private Button btn;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- tv = (TextView) findViewById(R.id.tv);
- btn = (Button) findViewById(R.id.btn);
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- doAnimation();
- }
- });
- tv.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Toast.makeText(MyActivity.this, "clicked me", Toast.LENGTH_SHORT).show();
- }
- });
- }
- …………
- }
下面来看看 doAnimation()的具体实现:
- private void doAnimation(){
- ValueAnimator animator = ValueAnimator.ofInt(0,400);
- animator.setDuration(1000);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int curValue = (int)animation.getAnimatedValue();
- tv.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
- }
- });
- animator.start();
- }
然后添加对计算过程进行监听:
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int curValue = (int)animation.getAnimatedValue();
- tv.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
- }
- });
大家可能注意到了,layout()函数中上下左右点的坐标是以屏幕坐标来标准的。所以在效果图中可以看到,textview的运动轨迹是从屏幕的左上角(0,0)点运行到(400,400)点。
三、常用方法
经过上面的例子,我们就大概知道ValueAnimator要怎么使用了,下面我们就来具体来看看它还有哪些常用的方法吧。1、ofInt与ofFloat
在上面的例子中,我们使用了ofInt函数,与它同样功能的还有一个函数叫ofFloat,下面我们先看看他们的具体声明:- public static ValueAnimator ofInt(int... values)
- public static ValueAnimator ofFloat(float... values)
下面我们还在上面例子的基础上,使用ofFloat函数来举个例子:
- ValueAnimator animator = ValueAnimator.ofFloat(0f,400f,50f,300f);
- animator.setDuration(3000);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- Float curValueFloat = (Float)animation.getAnimatedValue();
- int curValue = curValueFloat.intValue();
- tv.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
- }
- });
- animator.start();
在效果图中,我们可以看到,在点击按钮之后,textview先向右下运动然后再回来,然后再向右下运动过去
在这个例子中,我们使用ValueAnimator.ofFloat(0f,400f,50f,300f)构造了一个比较复杂的动画渐变,值是0变到400再回到50最后变成300;
所以我们在监听时,首先得到当前动画的值:
- Float curValueFloat = (Float)animation.getAnimatedValue();
- Object getAnimatedValue();
它返回的类型是一个Object原始类型,那我们怎么知道我们要将它强转成什么类型呢。注意,我们在设定动画初始值时用的是ofFloat()函数,所以每个值的类型必定是Float类型,所以我们获取出来的类型也必然是Float类型的。同样,如果我们使用ofInt设定的初始值,那么通过getAnimatedValue()获取到的值就应该强转为Int类型。
在得到当前运动的值以后,通过layout函数将textview移动到指定位置即可。
2、常用函数
先做个汇总,这部分将讲述的方法有:- /**
- * 设置动画时长,单位是毫秒
- */
- ValueAnimator setDuration(long duration)
- /**
- * 获取ValueAnimator在运动时,当前运动点的值
- */
- Object getAnimatedValue();
- /**
- * 开始动画
- */
- void start()
- /**
- * 设置循环次数,设置为INFINITE表示无限循环
- */
- void setRepeatCount(int value)
- /**
- * 设置循环模式
- * value取值有RESTART,REVERSE,
- */
- void setRepeatMode(int value)
- /**
- * 取消动画
- */
- void cancel()
1、setDuration()、getAnimatedValue()、start()
这三个函数在上面的实例中已经使用过,setDuration(long duration)是设置一次动画的时长,单位是毫秒,start()是开始动画,唯一有点难度的是Object getAnimatedValue(),它的声明为:- Object getAnimatedValue();
总而言之,通过getAnimatedValue()值类型与初始设置动画时的值类型相同
上面我们已经用过这些函数了,这里就不再举例了。
2、setRepeatCount()、setRepeatMode()、cancel()
setRepeatCount(int value)用于设置动画循环次数,设置为0表示不循环,设置为ValueAnimation.INFINITE表示无限循环。cancel()用于取消动画
我们着重说一下setRepeatMode:
- /**
- * 设置循环模式
- * value取值有RESTART,REVERSE
- */
- void setRepeatMode(int value)
下面我们使用这三个函数来举个例子,先看下动画效果:
在这里,有两个按钮,当点击start anim时,textview垂直向下运动,我定义的运动初始值为ofInt(0,400);所以从效果图中也可以看出我们定义它为无限循环,而且每次循环时都是使用ValueAnimation.REVERSE让其倒序重新开始循环。当我们点击cancel anim时,取消动画。
下面我们来看看代码
首先是布局代码,布局代码时,采用RelativeLayout布局,将两个按钮放两边,textview放中间,代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <Button
- android:id="@+id/btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:padding="10dp"
- android:text="start anim"
- />
- <Button
- android:id="@+id/btn_cancel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:padding="10dp"
- android:text="cancel anim"
- />
- <TextView
- android:id="@+id/tv"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:padding="10dp"
- android:background="#ffff00"
- android:text="Hello qijian"/>
- </RelativeLayout>
下面来看看两个按钮的操作代码:
- private Button btnStart,btnCancel;
- private ValueAnimator repeatAnimator;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- tv = (TextView) findViewById(R.id.tv);
- btnStart = (Button) findViewById(R.id.btn);
- btnCancel = (Button)findViewById(R.id.btn_cancel);
- btnStart.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- repeatAnimator = doRepeatAnim();
- }
- });
- btnCancel.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- repeatAnimator.cancel();
- }
- });
- }
下面我们来看看doRepeatAnim()函数都做了哪些工作:
- private ValueAnimator doRepeatAnim(){
- ValueAnimator animator = ValueAnimator.ofInt(0,400);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int curValue = (int)animation.getAnimatedValue();
- tv.layout(tv.getLeft(),curValue,tv.getRight(),curValue+tv.getHeight());
- }
- });
- animator.setRepeatMode(ValueAnimator.REVERSE);
- animator.setRepeatCount(ValueAnimator.INFINITE);
- animator.setDuration(1000);
- animator.start();
- return animator;
- }
3、两个监听器
(1)、添加监听器
前面,我们讲过一个添加监听器animator.addUpdateListener,以监听动画过程中值的实时变化,其实在ValueAnimator中共有两个监听器:- /**
- * 监听器一:监听动画变化时的实时值
- */
- public static interface AnimatorUpdateListener {
- void onAnimationUpdate(ValueAnimator animation);
- }
- //添加方法为:public void addUpdateListener(AnimatorUpdateListener listener)
- /**
- * 监听器二:监听动画变化时四个状态
- */
- public static interface AnimatorListener {
- void onAnimationStart(Animator animation);
- void onAnimationEnd(Animator animation);
- void onAnimationCancel(Animator animation);
- void onAnimationRepeat(Animator animation);
- }
- //添加方法为:public void addListener(AnimatorListener listener)
在AnimatorListener中,主要是监听Animation的四个状态,start、end、cancel、repeat;当动画开始时,会调用onAnimationStart(Animator animation)方法,当动画结束时调用onAnimationEnd(Animator animation),当动画取消时,调用onAnimationCancel(Animator animation)函数,当动画重复时,会调用onAnimationRepeat(Animator animation)函数。
添加AnimatorListener的方法是addListener(AnimatorListener listener) ;
下面我们就举个例子来看一下AnimatorListener的使用方法。
我们在上面doRepeatAnim()函数的基础上,添加上AnimatorListener,代码如下:
代码如下:
- private ValueAnimator doAnimatorListener(){
- ValueAnimator animator = ValueAnimator.ofInt(0,400);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int curValue = (int)animation.getAnimatedValue();
- tv.layout(tv.getLeft(),curValue,tv.getRight(),curValue+tv.getHeight());
- }
- });
- animator.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- Log.d("qijian","animation start");
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- Log.d("qijian","animation end");
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- Log.d("qijian","animation cancel");
- }
- @Override
- public void onAnimationRepeat(Animator animation) {
- Log.d("qijian","animation repeat");
- }
- });
- animator.setRepeatMode(ValueAnimator.REVERSE);
- animator.setRepeatCount(ValueAnimator.INFINITE);
- animator.setDuration(1000);
- animator.start();
- return animator;
- }
我们来看看动画效果:
打印出来结果如下:
(2)、取消监听
上面我们讲了如何添加监听函数,下面我们来看看如何移除监听器:- /**
- * 移除AnimatorUpdateListener
- */
- void removeUpdateListener(AnimatorUpdateListener listener);
- void removeAllUpdateListeners();
- /**
- * 移除AnimatorListener
- */
- void removeListener(AnimatorListener listener);
- void removeAllListeners();
下面上在添加监听器的例子基础上,不改变doAnimatorListener()的代码,仍然是textview做动画时添加AnimatorListener的状态监听。然后点击cancelAnim时,移除AnimatorListener,代码如下:
AnimatorListener的代码:
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- …………
- btnStart.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- repeatAnimator = doAnimatorListener();
- }
- });
- btnCancel.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- repeatAnimator.removeAllListeners();
- }
- });
- }
效果如下:
在效果图中,在动画循环了三次之后,我们点击btnCancel移除所有的AnimatorListener;打印tag如下:
可见只打印了循环三次以前的log,在移除我们添加的AnimatorListener之后,我们打印log的代码就不会再执行了,所以也就不会再有log了。
好了,有关监听器的部分,我们就到这里了
4、其它函数
上面我们讲了ValueAnimator中常用的一些函数,但是还有一些函数虽然不常用,但我们还是简单讲一下,他们分别是:- /**
- * 延时多久时间开始,单位是毫秒
- */
- public void setStartDelay(long startDelay)
- /**
- * 完全克隆一个ValueAnimator实例,包括它所有的设置以及所有对监听器代码的处理
- */
- public ValueAnimator clone()
但clone()这个函数就有点难度了;首先是什么叫克隆。就是完全一样!注意是完全一样!就是复制出来一个完全一样的新的ValueAnimator实例出来。对原来的那个ValueAnimator是怎么处理的,在这个新的实例中也是全部一样的。
我们来看一个例子来看一下,什么叫全部一样:
首先,我们定义一个函数doRepeatAnim():
- private ValueAnimator doRepeatAnim(){
- ValueAnimator animator = ValueAnimator.ofInt(0,400);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int curValue = (int)animation.getAnimatedValue();
- tv.layout(tv.getLeft(),curValue,tv.getRight(),curValue+tv.getHeight());
- }
- });
- animator.setDuration(1000);
- animator.setRepeatMode(ValueAnimator.REVERSE);
- animator.setRepeatCount(ValueAnimator.INFINITE);
- return animator;
- }
然后我们再看看点击btnStart和btnCancel时的代码处理:
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- …………
- btnStart.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- repeatAnimator = doRepeatAnim();
- //克隆一个新的ValueAnimator,然后开始动画
- ValueAnimator newAnimator = repeatAnimator.clone();
- newAnimator.setStartDelay(1000);
- newAnimator.start();
- }
- });
- btnCancel.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- repeatAnimator.removeAllUpdateListeners();
- repeatAnimator.cancel();
- }
- });
- }
- repeatAnimator = doRepeatAnim();
- //克隆一个新的ValueAnimator,然后开始动画
- ValueAnimator newAnimator = repeatAnimator.clone();
- newAnimator.setStartDelay(1000);
- newAnimator.start();
这里有一点非常注意是:我们除了对newAnimator设置了动画开始延时1000毫秒以后,没有对它进行任何设置,更没有在在它的监听器中对textview的处理!!!!那textview会动吗?答案是会动的,我们讲了,克隆就是完全一样,在原来的ValueAnimator中是如何处理的,克隆过来的ValueAnimator也是完全一样的处理方式!
在点击btnCancel时:
- repeatAnimator.removeAllUpdateListeners();
- repeatAnimator.cancel();
效果图如下:
从效果图中也可以看出,点击btnCancel按钮是没有做用的,并没能取消动画。
源码内容:
1、《try_tween_anim_click》:对应概述中:举例说明补间动画的点击区域问题
2、《BlogValueAnimator1》:对应:《实例使用ValueAnimator》和《ofInt与ofFloat》两部分源码
3、《BlogValueAnimator2》:对应以后所有的源码
如果本文有帮到你,记得加关注哦
源码地址:
****:http://download.****.net/detail/harvic880925/9405988
github:https://github.com/harvic/BlogResForGitHub