1.用ValueAnimator来制作动画
ValueAnimator 类通过设定动画过程中的int、float或颜色值,来指定动画播放期间的某些类型的动画值。
通过ValueAnimator类的一个工厂方法来获取一个 ValueAnimator对象:ofInt()、ofFloat()、ofObject()。
代码如下:
public void onClick(View v){
//获取按钮组件
final Button b = (Button)v;
// 生成ValueAnimator对象
final ValueAnimator animator = ValueAnimator.ofInt(0,30);
animator.setDuration(30*1000);
//添加Interpolator
animator.setInterpolator(new LinearInterpolator());
//为动画添加update监听
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer i = (Integer) animator.getAnimatedValue();
b.setEnabled(false);
b.setText(" "+i);
}
});
//为动画添加监听 重写动画结束方法
animator.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
b.setEnabled(true);
}
});
//启动动画
animator.start();
}
|
ValueAnimator animation = ValueAnimator.ofObject(newMyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
|
在这段代码中,在start()方法开始执行的时候,ValueAnimator对象会在1000毫秒的动画期间内,使用由MyTypeEvaluator对象提供的逻辑,在startPropertyValue和endPropertyValue之间,开始计算动画的值。
但是,在前一个代码片段中,不会对一个对象形成实际的影响,因为ValueAnimator对 象没有直接在对象或属性上执行操作。这么做的最大可能是用这些计算值来修改那些想要动画效果的对象。
通过定义ValueAnimator类中响应的事件监听器,来处理动画执行期间的重要事件,如帧更新等。当监听器执行的时候,就能够通过调用getAnimateValue()方法获得指定帧的刷新的计算值。
ObjectAnimator类是ValueAnimator类的一个子类,并且把时序引擎和ValueAnimator对象的计算值组合到一起,让目标对象的命名属性具备动画能力。这样使得让任意对象具有动画效果变的更加容易,如不在需要实现ValueAnimator.AnimatorUpdateListener接口,因为被动画的属性会自动的更新。
实例化一个ObjectAnimator对象与实例化一个ValueAnimator对象类似,但是,它还需要跟动画期间的参数一起,指定动画对象和对象动画属性(用一个字符串)的名字:
效果图:
代码如下:
public void move(View v) {
//方式一、属性动画
// ObjectAnimator.ofFloat(imageView, "translationX", 0f, 200f).setDuration(1000)
// .start();
// ObjectAnimator.ofFloat(imageView, "translationY", 0f, 200f).setDuration(1000)
// .start();
// ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f).setDuration(1000)
// .start();
//
//方式二、属性动画
PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("translationX", 0f, 200f);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("translationY", 0f, 200f);
PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("rotation", 0f, 360f);
ObjectAnimator.ofPropertyValuesHolder(imageView, p1,p2,p3).setDuration(1000).start();
}
|
要正确的更新ObjectAnimator对象的属性,必须做以下事情:
1. 动画效果的属性必须有一个set<propertyName>格式的设置器方法。因为在动画处理期间,ObjectAnimator对象会自动的更新对应的动画效果属性,所以它必须使用这个设置器方法来访问对应的属性。例如,如果属性名是foo,那么就需要有一个setFoo()方法,如果这个设置器方法不存在,你有三种选择:
A. 如果你权利这么做,就要在这个类中添加设置器方法;
B. 使用一个你有权改变的包装器类,并且这个包装器能够用一个有效的设置方法来接收动画值,而且还要能够把这个值转发给初始对象。
C. 使用ValueAnimator类来代替。
2. 如果你只在ObjectAnimator类的一个工厂方法中指定了一个values…参数,那么该值会被假定为动画的结束值。因此,该对象的动画效果属性就必须要有一个获取方法,用于获得动画的开始值。这个获取方法必须使用get<propertyName>()格式。例如,属性是foo,就必须有一个getFoo方法。
3. 动画属性的获取(如果需要)和设置方法必须操作相同类型的指定给ObjectAnimator对象开始和结束值。例如,如果构建一个下面这样的ObjectAnimator对象,就必须要有targetObejct.setPropName(float)和targetObject.getPropName(float)方法:
ObjectAnimator.ofFloat(targetObject,
"propName"
, 1f)
|
4. 根据属性或对象上的动画效果,可能需要调用View对象上的invalidate()方法,在更新动画效果时,强制屏幕重绘自己。在onAnimationUpdate()回调方法中做这件事情。例如,一个绘图对象的颜色属性的动画效果,在队形重绘自己时,才会将变化结果更新到屏幕上。在View对象上的所有的属性的设置器,如setAlpha()和setTranslationX()会正确的让View对象失效,因此在调用这些方法设置新的值时候,你不需做失效这件事。
在很多场景中,一个动画的播放要依赖与另一个动画的开始或结束。Android系统让你把这些相互依赖的动画绑定到一个AnimatorSet对象中,以便能够指定它们是同时的、顺序的、或在指定的延时之后来播放。AnimatorSet对象也能够彼此嵌套。
以下示例代码示例,它按照以下方式播放Animator对象:
1. 同时播放animX 、animY (X,Y轴平移200dip)
2. 然后 播放animR(旋转360度)
//顺序播放动画
ObjectAnimator animX = ObjectAnimator.ofFloat(imageView, "translationX", 0f, 200f);
ObjectAnimator animY = ObjectAnimator.ofFloat(imageView, "translationY", 0f, 200f);
ObjectAnimator animR = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
AnimatorSet set = new AnimatorSet();
set.play(animX).with(animY);
set.play(animR).after(animY);
set.playSequentially(anim1,anim2,anim3);
set.setDuration(1000);
set.start();
|
onAnimationStart()
|
动画开始的时候被调用
|
onAnimationEnd()
|
动画结束的时候被调用,它不管动画是如何结束的。
|
onAnimationRepeate()
|
动画重复播放的时候被调用
|
onAnimationCancel()
|
动画被取消播放的时候被调用。
|
属性动画系统给ViewGroup对象的动画变化提供了与View对象一样容易动画处理方法。
使用LayoutTransition类在ViewGroup内部处理布局变化的动画。
当调用一个View对象的setVisibility()方法,或者设置该View为GONE常量,或者把该View对象添加到ViewGroup中(或者从ViewGroup中删除)时,在ViewGroup内部的View对象就能够实现时隐时现的动画效果。
当在ViewGroup对象中添加或删除View对象时,其中的其他View对象也能够动画移动到新的位置。在LayoutTransition对象内通过调用setAnimator()方法,并且在传递给该方法的Animator对象参数中带有下列LayoutTransition常量之一,就能够定义该常量所代表的动画:
APPEARING |
一个标记,它指示要在容器中正在显示的项目上运行动画;
|
CHANGE APPEARING |
一个标记,它指示在容器中由于新项目的出现而导致其他项目变化所要运行的动画;
|
DISAPPEARING |
一个标记,它指示一个从容器中消失的项目所要运行的动画;
|
CHANGE_DISAPPEARING |
一个标记,它指示由于一个项目要从容器中消失而导致其他项目的变化,所要运行的动画。
|
能够给这四种事件类型定义自定义动画,以便定制自己的布局过渡效果,也可以告诉动画系统只使用默认的动画效果。
举个小例子,自定义ViewGroup的4种相关动画
效果图:
XML文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.layout_animation.MainActivity" > <LinearLayout android:id="@+id/ll_bt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:animateLayoutChanges="true" android:orientation="horizontal" > <Button android:id="@+id/bt_add" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onClick" android:text="Add Button" /> <Button android:id="@+id/bt_remove" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onClick" android:text="Remove Button" /> </LinearLayout> <LinearLayout android:id="@+id/ll" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/ll_bt" android:animateLayoutChanges="true" android:orientation="vertical" > </LinearLayout> </RelativeLayout> |
代码如下:
package com.example.layout_animation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.Keyframe;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;
public class MainActivity extends ActionBarActivity {
LinearLayout linearLayout;
int i = 0;
private LayoutTransition mTransitioner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
linearLayout = (LinearLayout) findViewById(R.id.ll);
mTransitioner = new LayoutTransition();
//设置ViewGroup的相关动画
setupCustomAnimations();
linearLayout.setLayoutTransition(mTransitioner);
}
public void onClick(View view) {
switch (view.getId()) {
//添加按钮
case R.id.bt_add:
Button b = new Button(this);
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
b.setLayoutParams(params);
b.setText(i + "");
linearLayout.addView(b, 0);
i++;
break;
//删除按钮
case R.id.bt_remove:
i--;
linearLayout.removeViewAt(0);
break;
default:
break;
}
}
// 生成自定义动画
private void setupCustomAnimations() {
// 动画:CHANGE_APPEARING
// Changing while Adding
PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0,
1);
PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom",
0, 1);
PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX",
1f, 0f, 1f);
PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofFloat("scaleY",
1f, 0f, 1f);
final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(
this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScaleX,
pvhScaleY).setDuration(
mTransitioner.getDuration(LayoutTransition.CHANGE_APPEARING));
mTransitioner.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);
changeIn.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setScaleX(1f);
view.setScaleY(1f);
}
});
// 动画:CHANGE_DISAPPEARING
// Changing while Removing
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.9999f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe(
"rotation", kf0, kf1, kf2);
final ObjectAnimator changeOut = ObjectAnimator
.ofPropertyValuesHolder(this, pvhLeft, pvhTop, pvhRight,
pvhBottom, pvhRotation)
.setDuration(
mTransitioner
.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
mTransitioner.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
changeOut);
changeOut.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setRotation(0f);
}
});
// 动画:APPEARING
// Adding
ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "rotationY", 90f,
0f).setDuration(
mTransitioner.getDuration(LayoutTransition.APPEARING));
mTransitioner.setAnimator(LayoutTransition.APPEARING, animIn);
animIn.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setRotationY(0f);
}
});
// 动画:DISAPPEARING
// Removing
ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "rotationX", 0f,
90f).setDuration(
mTransitioner.getDuration(LayoutTransition.DISAPPEARING));
mTransitioner.setAnimator(LayoutTransition.DISAPPEARING, animOut);
animOut.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator anim) {
View view = (View) ((ObjectAnimator) anim).getTarget();
view.setRotationX(0f);
}
});
}
}
|
需要做的事情仅仅是把ViewGroup元素的android.animateLayoutchanges属性设置为true。例如:
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/verticalContainer"
android:animateLayoutChanges="true" />
如果把这个属性设置为true,那么在该ViewGroup对象中添加或删除View对象,以及ViewGroup对象中其他的View对象都会自动的具有动画效果。
IntEvaluator
|
int
|
FloatEvaluator
|
float
|
ArgbEvaluator
|
color(颜色、106进制)
|
TypeEvaluator
|
1个用于用户自定义计算器的接口,如果你的对象属性值类型,不是int,float,或color类型,你必须实现这个接口,去定义自己的数据类型。
|
public class FloatEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
returnstartFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
|
Class/Interface |
说明 |
AccelerateDecelerateInterpolator |
变化频率在开始和结尾处慢,而在中间部分加速 |
AccelerateInterpolator |
变化频率在开始慢,然后加速 |
AnticipateInterpolator |
先向后,然后向前抛出(抛物运动) |
AnticipateOvershootInterpolator |
先向后,向前抛出并超过目标值,然后最终返回到目标值。 |
BounceInterpolator |
在结束时反弹 |
CycleInterpolator |
用指定的循环数,重复播放动画 |
DecelerateInterpolator |
变化频率是快出,然后减速 |
LinearInterpolator |
固定的变化频率 |
OvershootInterpolator |
向前抛出,并超过目标值,然后再返回 |
TimeInterpolator |
实现自定义插值的一个接口 |
xml 设置 插入器:android:interpolator="@android:anim/accelerate_decelerate_interpolator"
public float getInterpolation(float input) {
return(float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
|
public float getInterpolation(float input) {
return
input;
}
|
下表列出了一个持续1000毫秒的动画通过插补器所计算的近似值:
播放时间(毫秒) |
播放比例/插值比例(线性) |
插值比例(加速/减速) |
0 |
0 |
0 |
200 |
0.2 |
0.1 |
400 |
0.4 |
0.345 |
600 |
0.6 |
0.8 |
800 |
0.8 |
0.9 |
1000 |
1 |
1 |
如上表所示,LinearInterpolator插补器的计算结果是匀速变化的,每200毫秒增加0.2。AccelerateDecelerateInterpolator插补器的计算结果在200毫秒到600毫秒之间比LinearInterpolator的计算结果要快,而在600毫秒到1000毫秒之间则比LinearInterpolator的计算结果要慢。
有时间和值构成的Keyframe对象会定义动画在特定的时间点上特定的状态。每个关键帧还有它自己的插补器来控制当前关键帧与前一个关键帧之间的动画行为。
要实例化一个Keyframe对象,必须使用以下工厂方法之一:ofInt()、ofFloat()、或ofObject()。使用这些工厂方法来获取对应类型的关键帧,然后调用ofKeyframe工厂方法来获取一个PropertyValuesHolder对象,一旦获得了这个对象,就能够得到一个在PropertyValuesHolder对象中传递的动画制作器对象。以下代码演示了如何做这件事情:
Keyframe kf0 = Keyframe.ofFloat(0f, 0f); Keyframe kf1 = Keyframe.ofFloat(.5f, 360f); Keyframe kf2 = Keyframe.ofFloat(1f, 0f); PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2); ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation) rotationAnim.setDuration(5000ms); |
属性动画系统能够通过改变View对象中的实际属性,让View对象在屏幕上展现动画效果。另外,View对象也会自动的调用invalidate()方法,在属性发生变化时来属性屏幕。在View类中便于动画设置的新属性有:
translationX和translationY
|
这两个属性作为一种增量来控制着View对象从它布局容器的左上角坐标开始的位置。 |
rotation、rotationX和rotationY
|
这三个属性控制View对象围绕支点进行2D和3D旋转。 |
scaleX和scaleY
|
这两个属性控制着View对象围绕它的支点进行2D缩放 |
pivotX和pivotY
|
这两个属性控制着View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是View对象的中心点。 |
x和y
|
这是两个简单实用的属性,它描述了View对象在它的容器中的最终位置,它是最初的左上角坐标和translationX和translationY值的累计和。 |
alpha
|
它表示View对象的alpha透明度。默认值是1(不透明),0代表完全透明(不可见)。 要让一个View对象的属性具有动画效果,如它的颜色或旋转值等,就需要创建一个属性动画制作器,并给对象属性指定想要的动画效果,如: ObjectAnimator.ofFloat(myView,"rotation",0f,360f); |
10.用ViewPropertyAnimator制作动画
ViewPropertyAnimator类使用一个单一的Animator对象,给一个View对象的几个动画属性平行处理提供一种简单的方法。它的行为非常像ObjectAnimator类,因为它修改了View对象属性的实际的值,但是当多个动画属性同时处理时,它会更加高效。另外,使用ViewPropertyAnimator类的代码更加简洁和易于阅读。以下代码片段显示了在同时处理View对象的x和y属性动画效果时,使用多个ObjectAnimator对象、一个单独的ObjectAnimator对象和ViewPropertyAnimator对象之间的差异:
多个ObjectAnimator
ObjectAnimator animX = ObjectAnimator.ofFloat(myView,
"x"
, 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView,
"y"
, 100f);
AnimatorSet animSetXY =
new
AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
|
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(
"x"
, 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(
"y"
, 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
|
myView.animate().x(50f).y(100f);
|
11.在XML中声明动画
属性动画系统会让你用XML来声明属性动画,而不是用编程的方式来做它。通过XML中定义你的动画,能够更加容易的在多个Activity中重用动画,并且更加容易的编辑动画的播放顺序。
从Android3.1开始,要把使用新的属性动画的API的动画文件与那些使用传统的视图动画框架区分开,你应该把属性动画的XML文件保存在res/animator/目录中(而不是res/anim/)。animator目录名是可选的,但是如果想要使用Eclipse ADT插件(ADT11.0.0+)中的布局编辑器,就必须使用animator目录,因为ADT只搜索res/animator目录中属性动画资源。
以下是属性动画类在XML声明中所使用的对应的XML标签:
ValueAnimator- <animator>
ObjectAnimator- <objectAnimator>
AnimatorSet- <set>
以下示例顺序的播放两组对象动画,第一组动画中嵌套了一个同时播放两个对象的动画:
<set android:ordering=
"sequentially"
>
<set>
<objectAnimator
android:propertyName=
"x"
android:duration=
"500"
android:valueTo=
"400"
android:valueType=
"intType"
/>
<objectAnimator
android:propertyName=
"y"
android:duration=
"500"
android:valueTo=
"300"
android:valueType=
"intType"
/>
</set>
<objectAnimator
android:propertyName=
"alpha"
android:duration=
"500"
android:valueTo=
"1f"
/>
</set>
|
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();
|