long long ago,我写过几篇有关Animation的文章,讲解了传统的alpha、scale、translate、rotate的用法及代码生成方法。其实这三篇文章讲的所有动画效果叫做Tween Animation(补间动画)
在Android动画中,总共有两种类型的动画View Animation(视图动画)和Property Animator(属性动画);
其中
在效果图中,首先,我给textview添加了点击响应,当点击textview时,会弹出Toast。
然后,当我点击按钮的时候,textview开始向右下角移动。
从结果中可以看出,在移动前,点击textview是可以弹出toast的的,在移动后,点击textview时则没有响应,相反,点击textview的原来所在区域则会弹出toast.
这就论证了不同第三点:补间动画虽能对控件做动画,但并没有改变控件内部的属性值
下面简单看看这个动画的实现代码吧:
<!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%2D%2D%3E--> <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"> </textview></button></linearlayout>
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 animator = ValueAnimator.ofInt(0,400); animator.setDuration(1000); animator.start();在这里我们利用ValueAnimator.ofInt创建了一个值从0到400的动画,动画时长是1s,然后让动画开始。从这段代码中可以看出,ValueAnimator没有跟任何的控件相关联,那也正好说明ValueAnimator只是对值做动画运算,而不是针对控件的,我们需要监听ValueAnimator的动画过程来自己对控件做操作。
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();在上面的代码中,我们通过addUpdateListener添加了一个监听,在监听传回的结果中,是表示当前状态的ValueAnimator实例,我们通过animation.getAnimatedValue()得到当前值。然后通过Log打印出来,结果如下:
这就是ValueAnimator的功能:ValueAnimator对指定值区间做动画运算,我们通过对运算过程做监听来自己操作控件。
总而言之就是两点:
首先这个动画的布局与上一个实例是一样的。但实现的效果确不大相同:
首先,点击按钮后,textview从屏幕(0,0)点运动到(400,400)点运动前后,textview都是可以响应点击事件的 下面我们就来看看这里如何利用ValueAnimator来实现这个效果的。<!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%2D%2D%3E--> <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"> </textview></button></linearlayout>
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(); } }); } ………… }这段代码很简单,在点击btn的时候执行 doAnimation()来执行动画操作,在点击tv的时候,弹出toast;
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(); }首先,我们构造一个ValueAnimator实例,让其计算的值是从0到400;
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的位置。这里注意了,我们是通过layout函数来改变位置的,我们知道layout函数在改变控件位置时是永久性的,即通过更改控件left,top,right,bottom这四个点的坐标来改更改坐标位置的,而不仅仅是从视觉上画在哪个位置,所以通过layout函数更改位置后,控件在新位置是可以响应点击事件的。
public static ValueAnimator ofInt(int... values) public static ValueAnimator ofFloat(float... values)他们的参数类型都是可变参数长参数,所以我们可以传入任何数量的值;传进去的值列表,就表示动画时的变化范围;比如ofInt(2,90,45)就表示从数值2变化到数字90再变化到数字45;所以我们传进去的数字越多,动画变化就越复杂。从参数类型也可以看出ofInt与ofFloat的唯一区别就是传入的数字类型不一样,ofInt需要传入Int类型的参数,而ofFloat则表示需要传入Float类型的参数。
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();先看看效果:
Float curValueFloat = (Float)animation.getAnimatedValue();通过getAnimatedValue()来获取当前运动点的值,大家可能会疑问为什么要转成Float类型,我们先来看看getAnimatedValue()的声明:
Object getAnimatedValue();
它返回的类型是一个Object原始类型,那我们怎么知道我们要将它强转成什么类型呢。注意,我们在设定动画初始值时用的是ofFloat()函数,所以每个值的类型必定是Float类型,所以我们获取出来的类型也必然是Float类型的。同样,如果我们使用ofInt设定的初始值,那么通过getAnimatedValue()获取到的值就应该强转为Int类型。
在得到当前运动的值以后,通过layout函数将textview移动到指定位置即可。
/** * 设置动画时长,单位是毫秒 */ ValueAnimator setDuration(long duration) /** * 获取ValueAnimator在运动时,当前运动点的值 */ Object getAnimatedValue(); /** * 开始动画 */ void start() /** * 设置循环次数,设置为INFINITE表示无限循环 */ void setRepeatCount(int value) /** * 设置循环模式 * value取值有RESTART,REVERSE, */ void setRepeatMode(int value) /** * 取消动画 */ void cancel()
Object getAnimatedValue();它的意义就是获取动画在当前运动点的值,所以这个对象只能用于在动画运动中。返回的值是Object,上面我们说过,通过getAnimatedValue()得到的值的实际类型与初始设置的值相同,如果我们利用ofInt()设置的动画,那通过getAnimatedValue()得到的值为类型就是Int类型。如果我们利用ofFloat()设置的动画,通过getAnimatedValue()得到的值类型就是Float类型。
/** * 设置循环模式 * value取值有RESTART,REVERSE */ void setRepeatMode(int value)setRepeatMode(int value)用于设置循环模式,取值为ValueAnimation.RESTART时,表示正序重新开始,当取值为ValueAnimation.REVERSE表示倒序重新开始。
在这里,有两个按钮,当点击start anim时,textview垂直向下运动,我定义的运动初始值为ofInt(0,400);所以从效果图中也可以看出我们定义它为无限循环,而且每次循环时都是使用ValueAnimation.REVERSE让其倒序重新开始循环。当我们点击cancel anim时,取消动画。
下面我们来看看代码
首先是布局代码,布局代码时,采用RelativeLayout布局,将两个按钮放两边,textview放中间,代码如下:
<!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%2D%2D%3E--> <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><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"> </textview></button></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(); } }); }这段代码也没什么难度,当我们点击btnStart的时候,执行doRepeatAnim()函数,这个函数返回它构造的ValueAnimator对象,将其赋值给repeatAnimator变量。当点击btnCancel时,调用 repeatAnimator.cancel()取消当前动画。
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; }在这里我们构造了一个ValueAnimator,动画范围是0-400,设置重复次数为无限循环。循环模式为倒序。在animator.setDuration(1000)表示动画一次的时长为1000毫秒。最后,由于我们在取消动画时还需要我们构造的这个ValueAnimator实例,所以将animator返回。
/** * 监听器一:监听动画变化时的实时值 */ 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)关于监听器一:AnimatorUpdateListener就是监听动画的实时变化状态,在onAnimationUpdate(ValueAnimator animation)中的animation表示当前状态动画的实例。这里就不再细讲这个监听器了,这里我们主要讲讲监听器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; }在上面的代码中,我们是在doRepeatAnim()函数的基础上,又添加了AnimatorListener()以监听它的状态,并把这些状态打印出来。
/** * 移除AnimatorUpdateListener */ void removeUpdateListener(AnimatorUpdateListener listener); void removeAllUpdateListeners(); /** * 移除AnimatorListener */ void removeListener(AnimatorListener listener); void removeAllListeners();针对AnimatorUpdateListener和AnimatorListener,每个监听器都有两个方法来移除;我们就以移除AnimatorListener来简单讲一下,removeListener(AnimatorListener listener)用于在animator中移除指定的监听器,而removeAllListeners()用于移除animator中所有的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(); } }); }doAnimatorListener的代码与上面的一样,就不再重复贴了,当点击btnCancel时移除animator中所有的AnimatorListener,但注意的是,我们在移除AnimatorListener后,并没有cancel动画效果,所以动画会一直不停的运动下去。但移除AnimatorListener之后,Log应该就不会再打印了。
在效果图中,在动画循环了三次之后,我们点击btnCancel移除所有的AnimatorListener;打印tag如下:
可见只打印了循环三次以前的log,在移除我们添加的AnimatorListener之后,我们打印log的代码就不会再执行了,所以也就不会再有log了。
好了,有关监听器的部分,我们就到这里了
/** * 延时多久时间开始,单位是毫秒 */ public void setStartDelay(long startDelay) /** * 完全克隆一个ValueAnimator实例,包括它所有的设置以及所有对监听器代码的处理 */ public ValueAnimator clone()setStartDelay(long startDelay)非常容易理解,就是设置多久后动画才开始。
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; }这个函数其实与上面在讲循环函数时的doRepeatAnim()函数是一样的;在这个函数中,我们定义一个ValueAnimator,设置为无限循环,然后添加AnimatorUpdateListener监听;在动画在运动时,向下移动textview.这里要非常注意的一点是我们只是定义了一个ValueAnimator对象,并没有调用start()让动画开始!!!!
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(); } }); }在上面的代码中,我们在点击btnStart时:
repeatAnimator = doRepeatAnim(); //克隆一个新的ValueAnimator,然后开始动画 ValueAnimator newAnimator = repeatAnimator.clone(); newAnimator.setStartDelay(1000); newAnimator.start();我们利用clone()克隆了一个doRepeatAnim()生成的对象。然后调用setStartDelay(1000);将动画开始时间设为1000毫秒后开始动画。最后调用start()函数开始动画。
repeatAnimator.removeAllUpdateListeners(); repeatAnimator.cancel();我们既移除了repeatAnimator的监听器又取消了动画。但有用吗?必须当然是没用的,因为我们start的动画对象是从repeatAnimator克隆来的newAnimator。这好比是克隆羊,原来的羊和克隆羊什么都是一样的,但你把原来的羊杀了,克隆的羊会死吗?用大脚指头想都知道不会!所以如果要取消当前的动画必须通过newAnimator.cancel()来取消
从效果图中也可以看出,点击btnCancel按钮是没有做用的,并没能取消动画。