前面介绍了Animation的两种表现形式——View Animation和Drawable Animation。今天重点来讲一下Property Animation,它是动画中功能最强大的,同时也是用得最多的一种。从上一篇博客我们了解到了View Animation也可以改变View控件的属性,但也仅仅只能改变View控件的4种属性,有局限性,像颜色这样的属性它是改变不了的,但Property Animation就没有这样的限制,而且它可以控制任意对象的属性,并不仅仅局限于View控件的属性。具体是怎么做到的呢?我们主要把目光放在ValueAnimator和ObjectAnimator上面。
一、ValueAnimator
该类的继承关系如下:
它继承自Animator,说明它也有动画的一些属性。我们可以使用下面的代码来启动它:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(200);
anim.start();
但单纯这样是没有什么实际意义的,它只是执行了一次从0到1值的变化,我们也看不到执行的效果。这时候我们需要添加一个监听器:
Button btn = (Button) findViewById(R.id.start_btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.i(TAG, "current value is " + currentValue);
}
});
anim.setDuration(200);
anim.start();
}
});
从log我们可以看到值从0到1的变化:
那么我们就可以利用这一点来实现真正意义上的动画了。我们在监听器中设置一下控件的透明属性就可以实现透明渐变的效果了:
textView.setAlpha(currentValue);
跟其他动画一样,我们也是可以通过配置xml来实现动画
,xml内容如下:
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:repeatCount="1"
android:repeatMode="reverse" />
代码加载:
ValueAnimator anim = (ValueAnimator) AnimatorInflater.
loadAnimator(MainActivity.this, R.anim.animator);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
textView.setAlpha((Float) animation.getAnimatedValue());
}
});
anim.start();
到这里我们要引进一个新的接口TypeEvaluator,音译为类型估值器,它有什么作用呢?我们查看一下API文档,发现ValueAnimator还有一个static方法可以创建对象:
static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
实现TypeEvaluator接口的有这4个类:ArgbEvaluator, FloatEvaluator, IntEvaluator, RectEvaluator。
我们可以用下面的代码替换原来的方式:
ValueAnimator anim = ValueAnimator.ofObject(new FloatEvaluator(), 0f, 1f);
当然,我们也完全可以实现TypeEvaluator接口,自定义一个类。现在我们使用自定义的TypeEvaluator同时改变View的x和y坐标,斜对角线匀速运动,代码参考ApiDemo。
布局文件包含我们自定义的View:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/start_btn"
android:text="Start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.example.customevaluator.AnimationView
android:id="@+id/my_anim"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
自定义View及自定义TypeEvaluator:
public class AnimationView extends View {
ValueAnimator bounceAnim = null;
ShapeHolder ball = null;
public AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
ball = createBall(25, 25);
}
private void createAnimation() {
if (bounceAnim == null) {
XYHolder startXY = new XYHolder(0f, 0f);
XYHolder endXY = new XYHolder(300f, 500f);
// 核心部分
bounceAnim = ValueAnimator.ofObject(new XYEvaluator(), startXY, endXY);
bounceAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
XYHolder xyHolder = (XYHolder) animation.getAnimatedValue();
ball.setX(xyHolder.getX());
ball.setY(xyHolder.getY());
invalidate();
}
});
bounceAnim.setDuration(1500);
}
}
public void startAnimation() {
createAnimation();
bounceAnim.start();
}
private ShapeHolder createBall(float x, float y) {
OvalShape circle = new OvalShape();
circle.resize(50f, 50f);
ShapeDrawable drawable = new ShapeDrawable(circle);
ShapeHolder shapeHolder = new ShapeHolder(drawable);
shapeHolder.setX(x - 25f);
shapeHolder.setY(y - 25f);
int red = (int)(Math.random() * 255);
int green = (int)(Math.random() * 255);
int blue = (int)(Math.random() * 255);
int color = 0xff000000 | red << 16 | green << 8 | blue;
Paint paint = drawable.getPaint();
int darkColor = 0xff000000 | red/4 << 16 | green/4 << 8 | blue/4;
RadialGradient gradient = new RadialGradient(37.5f, 12.5f,
50f, color, darkColor, Shader.TileMode.CLAMP);
paint.setShader(gradient);
shapeHolder.setPaint(paint);
return shapeHolder;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(ball.getX(), ball.getY());
ball.getShape().draw(canvas);
canvas.restore();
}
public class XYHolder {
private float mX;
private float mY;
public XYHolder(float x, float y) {
mX = x;
mY = y;
}
public float getX() {
return mX;
}
public void setX(float x) {
mX = x;
}
public float getY() {
return mY;
}
public void setY(float y) {
mY = y;
}
}
public class XYEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
XYHolder startXY = (XYHolder) startValue;
XYHolder endXY = (XYHolder) endValue;
return new XYHolder(startXY.getX() + fraction * (endXY.getX() - startXY.getX()),
startXY.getY() + fraction * (endXY.getY() - startXY.getY()));
}
}
}
上面代码我们使用ValueAnimator.ofObject静态方法获取一ValueAnimator对象,第一个参数传的就是TypeEvaluator,这里我们使用自定义的XYEvaluator对象,实现evaluate方法,fraction参数可以理解为开始到结束间占的百分比,第二和第三个参数分别代表开始和结束的值,该方法负责计算出某一时刻的值,我们可以在监听方法onAnimationUpdate中获取到该值,设置后重绘一下UI。
还有一辅助类ShapeHolder代码也贴上:
public class ShapeHolder {
private float x = 0, y = 0;
private ShapeDrawable shape;
private int color;
private RadialGradient gradient;
private float alpha = 1f;
private Paint paint;
public void setPaint(Paint value) {
paint = value;
}
public Paint getPaint() {
return paint;
}
public void setX(float value) {
x = value;
}
public float getX() {
return x;
}
public void setY(float value) {
y = value;
}
public float getY() {
return y;
}
public void setShape(ShapeDrawable value) {
shape = value;
}
public ShapeDrawable getShape() {
return shape;
}
public int getColor() {
return color;
}
public void setColor(int value) {
shape.getPaint().setColor(value);
color = value;
}
public void setGradient(RadialGradient value) {
gradient = value;
}
public RadialGradient getGradient() {
return gradient;
}
public void setAlpha(float alpha) {
this.alpha = alpha;
shape.setAlpha((int)((alpha * 255f) + .5f));
}
public float getWidth() {
return shape.getShape().getWidth();
}
public void setWidth(float width) {
Shape s = shape.getShape();
s.resize(width, s.getHeight());
}
public float getHeight() {
return shape.getShape().getHeight();
}
public void setHeight(float height) {
Shape s = shape.getShape();
s.resize(s.getWidth(), height);
}
public ShapeHolder(ShapeDrawable s) {
shape = s;
}
Activity中点击按钮启动动画:
final AnimationView animView = (AnimationView) findViewById(R.id.my_anim);
Button btn = (Button) findViewById(R.id.start_btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animView.startAnimation();
}
});
二、ObjectAnimator
ObjectAnimator继承自ValueAnimator,所以它们间是有共性的,这里只着重介绍以下方法的使用:
static ObjectAnimator ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values)
该方法的重点在于第二个参数propertyName——属性名,指的是谁的属性呢?指的是第一个参数对象的属性。比如说target为View控件对象,propertyName我们可以设置为“alpha”,表示将要修改View的透明属性。这里有一点需要特别注意:属性需要有对应的set和get方法。上面的例子我们用ObjectAnimator实现,修改一下自定义View文件:
public class AnimationView extends View {
ValueAnimator bounceAnim = null;
ShapeHolder ball = null;
BallXYHolder ballHolder = null;
public AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
ball = createBall(25, 25);
ballHolder = new BallXYHolder(ball);
}
private void createAnimation() {
if (bounceAnim == null) {
XYHolder startXY = new XYHolder(0f, 0f);
XYHolder endXY = new XYHolder(300f, 500f);
// 核心部分
bounceAnim = ObjectAnimator.ofObject(ballHolder, "XY",
new XYEvaluator(), startXY, endXY);
bounceAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
bounceAnim.setDuration(1500);
}
}
public void startAnimation() {
createAnimation();
bounceAnim.start();
}
private ShapeHolder createBall(float x, float y) {
OvalShape circle = new OvalShape();
circle.resize(50f, 50f);
ShapeDrawable drawable = new ShapeDrawable(circle);
ShapeHolder shapeHolder = new ShapeHolder(drawable);
shapeHolder.setX(x - 25f);
shapeHolder.setY(y - 25f);
int red = (int)(Math.random() * 255);
int green = (int)(Math.random() * 255);
int blue = (int)(Math.random() * 255);
int color = 0xff000000 | red << 16 | green << 8 | blue;
Paint paint = drawable.getPaint();
int darkColor = 0xff000000 | red/4 << 16 | green/4 << 8 | blue/4;
RadialGradient gradient = new RadialGradient(37.5f, 12.5f,
50f, color, darkColor, Shader.TileMode.CLAMP);
paint.setShader(gradient);
shapeHolder.setPaint(paint);
return shapeHolder;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.translate(ball.getX(), ball.getY());
ball.getShape().draw(canvas);
canvas.restore();
}
public class XYHolder {
private float mX;
private float mY;
public XYHolder(float x, float y) {
mX = x;
mY = y;
}
public float getX() {
return mX;
}
public void setX(float x) {
mX = x;
}
public float getY() {
return mY;
}
public void setY(float y) {
mY = y;
}
}
public class XYEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
XYHolder startXY = (XYHolder) startValue;
XYHolder endXY = (XYHolder) endValue;
return new XYHolder(startXY.getX() + fraction * (endXY.getX() - startXY.getX()),
startXY.getY() + fraction * (endXY.getY() - startXY.getY()));
}
}
public class BallXYHolder {
private ShapeHolder mBall;
public BallXYHolder(ShapeHolder ball) {
mBall = ball;
}
public void setXY(XYHolder xyHolder) {
mBall.setX(xyHolder.getX());
mBall.setY(xyHolder.getY());
}
public XYHolder getXY() {
return new XYHolder(mBall.getX(), mBall.getY());
}
}
}
上面例子我们设置"XY"的属性,BallXYHolder类中实现setXY()和getXY()方法。可以看到,使用ObjectAnimator我们不需要依赖onAnimationUpdate来得到当前值,ObjectAnimator会直接找到属性名的set方法帮我们完成这件事。
动画的内容还有很多,限于篇幅和能力原因,先写到这吧!