属性动画ValueAnimator在自定义View中的使用


转载地址:http://mafei.site/2016/07/17/android-valueanimator/?sukey=3997c0719f1515205be8ad9aea79b9ad51de542a2b92fc95a19db31c5683e178477941e608c949305e1948a0d90a8f7b


最近在学习有关自定义 View 的内容,在 Github 上看到好多开源的 View 控件,如果涉及动画,基本上都使用的是功能更加强大的属性动画,真心觉得属性动画比之前的补间动画强大太多饿了,也学到了使用属性动画自定义 View 的方便和强大。所以想记录一下在自定义 View 时,使用属性动画的几个方面。

属性动画的强大之处在于可以对任意对象的任意属性增加动画效果,并且可以自定义值的类型和变化过程(TypeEvaluator)和过渡速度(Interpolator)。

这篇文章先来看看 ValueAnimator 的使用方法。

ValueAnimator

ValueAnimator 是属性动画的核心类,最常用的 ObjectAnimator (下篇会讲到)就是它的子类。此类只是以特定的方式(可以自定义)对值进行不断的修改,已达到某种想要的过渡效果。此类提供设置播放次数、动画间隔、重复模式、开始动画以及设置动画监听器的方法。

看一个最简单的例子吧。

      
      
ValueAnimator animator = ValueAnimator.ofFloat( 0f, 1.0f);
animator.setDuration( 3000);
animator.setInterpolator( new LinearInterpolator());
animator.start();

以上代码先使用 ofFloat() 方法接收 0 和 1 参数初始化了一个 ValueAnimator 对象,接着设置动画播放的时间,设置变化速率为系统提供的线性变化,最后启动动画。

达到的效果是,在3000毫秒内从0线性增加到1。我们试着打印出这些值。

      
      
animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float)animation.getAnimatedValue();
Log.d(TAG, value);
}
});
animator.start();

结果打印从0到1 线性 变化的值,并且耗时3000毫秒。如果我们不设置 Interpolator,会调用默认的 Interpolator,先加速增加后减速增加。

这些值对于动画有什么用呢?我们可以添加监听器获取每次改变的值,并且可以把此值用在改变 view 的某些属性上,从而达到动画效果。

怎样使用ValueAnimator自定义View动画?

当然,上面的代码只是对数字的变化的操作,并没有涉及到动画效果。接下来我们通过在动画开始前(start()方法)设置监听器来让自定义 View 做出相应的动画。

如果想要做出如下图所示的效果,使用 ValueAnimator 就特别简单。


横向移动gif

由于效果是一个小球从左边移动一段距离后,重复执行。变化的值只有小球圆心的X轴坐标。所以可以利用 ValueAnimator 产生从开始位置到结束位置的一系列中间值,设置小球移动的动画。

直接上代码:
onDraw() 方法:根据 XPoint (x轴坐标)绘制圆形。

      
      
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(XPoint, heightSpecSize / 2, 30, mPaint);
}

对外提供开始动画的 start() 方法:创建 ValueAnimator 对象,以及各种属性,添加监听器把每次改变的值赋值给 xPoint,并且通知 view 重绘,最后开始动画。

      
      
public void start() {
final ValueAnimator animator = ValueAnimator.ofFloat( 60, 600);
animator.setDuration( 2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setInterpolator( new LinearInterpolator());
animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
XPoint = (Float)animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}

以上代码首先创建一个从 60 变化到 600 的 ValueAnimator 对象,接着设置动画时间、重播方式、重播次数和速度变化情况,最后增加监听器,每次获取变化的值再赋值给 xPoint ,接着很重要的一点,调用 invalidate() 方法通知 View 重绘,即值每次改变都需要 View 重绘。

这样就很方便的根据属性动画控制 float 值的改变,给 view 增加了动画的效果。

TypeEvaluator

前面的例子,创建 ValueAnimator 的时候,都是使用的 ValueAnimator.ofFloat(float, float) 方法,这个方法传递的参数为可变参数。其实创建 ValueAnimator 也可以使用 ofInt() 等方法,得到 Int 值得改变动画。
在使用 ofInt() 或 ofFloat() 方法时,其实是使用了 FloatEvaluator、FloatArrayEvaluator、IntEvaluator、IntArrayEvaluator 这些系统已经实现好了的 TypeEvaluator。我们使用这些方法创建 ValueAnimator 时就不必自定义类来继承 TypeEvaluator。

还有一个很重要的方法: ValueAnimator.ofObject(TypeEvaluator, Object…),此方法和其他方法不同之处在于参数 TypeEvaluator 和 Object,需要我们自己去实现,此处需要使用系统已经实现好的或自定义子类,用于设定自定义类型。

假如现在需要这个圆形像下图这样斜着移动,使用 ValueAnimator 该怎样实现?当然很很多简单的实现方法,比如 Path 路径的使用,这里为了演示自定义 TypeEvaluator,使用自定义 TypeEvaluator 的方式来实现。


斜着移动gif

自定义类 PointEvaluator 实现 TypeEvaluator 接口。

      
      
class PointEvaluator implements TypeEvaluator{
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
int x = ( int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
int y = ( int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
return new Point(x, y);
}
}

这里需要实现 evaluate() 方法,根据动画完成的百分比返回对应的值。其中 fraction 参数和动画时间有关,一般代表动画执行的完成程度,比如动画总时间为2000毫秒,现在执行了1000毫秒,那么此刻传递进来的 fraction 参数值为二分之一。

在 onDraw() 方法中依然还是简单的绘制一个圆形,此圆的圆心坐标是成员变量 mPoint 的 x , y值。

      
      
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mPoint.x, mPoint.y, 30, mPaint);
}

最后提供 start() 方法开始动画。

      
      
public void start() {
final ValueAnimator animator = ValueAnimator.ofObject( new PointEvaluator(),
new Point( 30, 30), new Point( 600, 600));
animator.setDuration( 2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setInterpolator( new LinearInterpolator());
animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPoint = (Point)animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}

和上边的代码类似,只是 ValueAnimotor 操作的值从 float 改变成了 Point (可以自定义类型),不再做过多的解释了。

这个例子,我们可以知道 ValueAnimotor 操作的值的类型是任意的,可以由我们来自定义,只要自定义类实现 TypeEvaluatorb,并且实现此接口的唯一一个方法 evaluate() 即可。

TimeInterpolator

TimeInterpolator 表示动画的速率,上边代码中我们就设置了动画速率,只不过使用的是API中已经实现好了的 LinearInterpolator。

查询API知道 TimeInterpolator 接口有很多已知的实现类,比如
AccelerateDecelerateInterpolator 表示先加速后减速,
AccelerateInterpolator 表示一直加速,
DecelerateInterpolator 表示一直加速等。
BounceInterpolator 可以模拟物理规律,实现反弹的效果

如果不设置 setInterpolator(),那么默认使用 AccelerateDecelerateInterpolator。

在自定义 TimeInterpolator 之前,我们先看看API中提供的实现的例子:LinearInterpolator,AccelerateInterpolator。

TimeInterpolator 接口,只有一个方法:getInterpolation(float input),此方法接收一个 float 类型的 input 值,此值的变化范围为0~1,并且根据动画运行的时间,均匀增加,和 TypeEvaluator 接口方法中的参数 fraction 很像, fraction 也是根据动画运行的时间,均匀增加。

注意:getInterpolation() 方法的返回值传递给了 TypeEvaluator 的 fraction 参数

LinearInterpolator 源码:

      
      
public float getInterpolation(float input) {
return input;
}

从源码中,可以看到 getInterpolation 的逻辑简单到不能再简单,直接返回 input,因为 input 本身表示的就是均匀增加的。

AccelerateInterpolator 源码:

      
      
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return ( float)Math.pow(input, mDoubleFactor);
}
}

构造函数接收一个 mFactor 表示加速的倍数,接收1.0f以上的数,mDoubleFactor = 2 * mFactor
在 getInterpolation() 方法中,判断 mFactor 如果等于1.0f,直接返回input * input(默认,二次函数增长),否则返回 input 的 mDoubleFactor 次方(mDoubleFactor次函数增长)。

看来要想实现一个自定义的 TimeInterpolator,得要有一些必要的数学修养了。没办法,数学没有那么好,只能实现一个简单的TimeInterpolator,演示自定义 TimeInterpolator 的步骤。

注意:最好让 getInterpolation() 方法返回的值在0~1之间,并且递增。以便在使用fraction参数时意思明确

下面我们实现一个以10给底数的负指数函数减速的例子:

      
      
class LgDecelerateInterpolator implements TimeInterpolator {
private float background;
public LgDecelerateInterpolator() {
background = 10;
}
@Override
public float getInterpolation(float input) {
return ( 1 - ( float) Math.pow(background, -input));
}
}

然后在设置animator.setInterpolator(new LgDecelerateInterpolator());,就可以使用了。

成员变量background表示底数,在构造方法中初始化为10,因为是减速,所以用到了负指数,得到的值从1变化到0,所以再用1减去这个结果值,就得到了最终的结果。

AnimatorSet

AnimatorSet 表示动画的集合,可以把几个动画一起播放,或按次序播放。提供 paly、with、after 等方法。

接下来,把以上用到的全部结合起来,播放一个动画的效果:圆形从 View 的左上角移动到右下角,伴随着颜色的变化,移动速度和颜色变化的速率都由上面自定义的 LgDecelerateInterpolator 实现。

这是效果图:

斜着指数减速移动伴随颜色指数渐变gif

这是完整的代码:

      
      
public class MyView extends View {
private Paint mPaint;
private Point mPoint;
private int mColor;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public MyView(Context context) {
super(context);
initPaint();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setColor( 0xFFF00000);
mPaint.setAntiAlias( true); // 抗锯齿
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mPoint.x, mPoint.y, 60, mPaint);
}
public void start() {
final ValueAnimator animator = ValueAnimator.ofObject( new PointEvaluator(),
new Point( 60, 60), new Point( 990, 1050));
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
final ValueAnimator animator1 = ValueAnimator.ofArgb( 0xFFF00000, 0xFFFFFF00);
animator1.setRepeatCount(ValueAnimator.INFINITE);
animator1.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mColor = ( int) animation.getAnimatedValue();
mPaint.setColor(mColor);
}
});
AnimatorSet animationSet = new AnimatorSet();
animationSet.setDuration( 3000);
animationSet.setInterpolator( new LgDecelerateInterpolator());
animationSet.play(animator).with(animator1);
animationSet.start();
}
class PointEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
int x = ( int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
int y = ( int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
return new Point(x, y);
}
}
class LgDecelerateInterpolator implements TimeInterpolator {
private float background;
public LgDecelerateInterpolator() {
background = 10;
}
@Override
public float getInterpolation(float input) {
return ( 1 - ( float) Math.pow(background, -input));
}
}
}

start() 方法中创建了两个 ValueAnimator,第一个使用 ofObject() 方法,使用自定义的 PointEvaluator,第二个使用API已经实现的ofArgb 使颜色值变化的动画属性(API21以上支持),都添加监听器以实现对成员变量的修改,重绘 View,最后创建 AnimatorSet 对两个动画进行叠加,在播放移动动画的同时播放颜色渐变的动画。



  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在指定时间内匀速画一条直线,您可以使用ValueAnimator来实现。首先,您需要自定义一个View,重写onDraw方法,在该方法内绘制一条直线。然后,您需要在View的构造函数创建一个ValueAnimator对象,并设置它的属性动画为“fraction”,时长为您想要的时间。在动画更新过程,您需要计算直线的起点和终点坐标,并调用invalidate方法不断绘制直线。下面是一个示例代码: ``` public class LineView extends View { private Paint paint; private float startX; private float startY; private float endX; private float endY; private ValueAnimator animator; public LineView(Context context) { this(context, null); } public LineView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LineView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); paint.setColor(Color.BLACK); paint.setStrokeWidth(5); // 创建ValueAnimator对象,时长为3000ms animator = ValueAnimator.ofFloat(0f, 1f).setDuration(3000); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { // 计算直线的起点和终点坐标 float fraction = (float) animation.getAnimatedValue(); startX = 0; startY = getHeight() / 2; endX = getWidth() * fraction; endY = getHeight() / 2; // 调用invalidate方法,不断绘制直线 invalidate(); } }); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawLine(startX, startY, endX, endY, paint); } public void startAnimation() { animator.start(); } } ``` 在上面的代码,我们首先在init方法创建了一个ValueAnimator对象,并设置它的属性动画为“fraction”,时长为3000ms。在动画更新过程,我们计算了直线的起点和终点坐标,并调用invalidate方法,不断绘制直线。在startAnimation方法,我们启动了ValueAnimator动画。您可以在您的Activity或Fragment调用该方法,启动动画
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值