Android 手把手进阶自定义View(三)- 属性动画

一、前言


Android 开发中,总是需要一些动画来优化用户的交互体验,提高用户满意度。因此,Google 为我们提供了一些用于处理动画效果的动画框架。

 

二、属性动画


2.1 ObjectAnimator 的简单使用

ObjectAnimator 是属性动画中最简单也最常用的一个对象。ImageVIew 向右平移 200 像素的动画效果,使用属性动画只需要很简单的几句代码即可实现:

ObjectAnimator.ofFloat(mIvPicture,"translationX",0F,200F)
        .setDuration(1000)
        .start();

我们来分析一下这一句代码。我们调用了 ofFloat 代码,并传入三个参数。​ 第一个参数是动画需要操纵的目标,在这里是我们的 ImageView。​ 第二个参数是所需要操纵的目标所具备的属性名称。​ 第三个参数是这个动画变化的取值范围。最后设置一下它的动画的属性便可以 start 了。这次我们再次点击 ImageView 目前的位置,成功地弹出了 Toast,这证实了属性动画是通过改变物体的属性来达到动画效果的理论。

当我们需要改变 y 坐标时,只需要把 "translationX" 变为 "translationY" 即可。其实 ,其实属性动画能操纵的属性,只要具有 set 方法,都可以进行操纵,如果没有初始值,则还必须有 get 方法。如 scaleX、scaleY,或者我们自定义 View 时提供的属性。

public class MyView extends View {
    ...
    private void startDownAnim() {
        ObjectAnimator thumbUpScale = ObjectAnimator.ofFloat(this, "thumbScale", SCALE_MIN, SCALE_MAX);
        thumbUpScale.setDuration(SCALE_DURING);
        thumbUpScale.start();
    }

    //必须提供 thumScale 的 set 方法
    public void setThumbScale(float thumbScale) {
        //根据值来做一些设置操作

        //调用 invalidate() 或者 postInvalidate()
        postInvalidate();
    }

    //如果开启动画时没有设置默认值,则还必须提供 thumScale 的 get 方法
    ...
}

 

2.2 插值器

Android 为我们内置了插值器,使我们的动画更为自然。比如可以让我们的平移动画像物体的重力加速度由快到慢的 Accelerate 等等。要应用插值器,可以调用 ObjectAnimator 的 setInterpolator 方法, new 出对应的插值器作为参数(xxxInterpolator)。比如下面这段代码:

ObjectAnimator animator = ObjectAnimator.ofFloat(
        imageView2, "translationX", 500);
animator.setInterpolator(new AnticipateOvershootInterpolator());
animator.start();

Android中内置了七种插值器,分别是

AccelerateDecelerateInterpolator

先加速再减速。这是默认的 Interpolator,也就是说如果你不设置的话,那么动画将会使用这个 Interpolator。这个是一种最符合现实中物体运动的 Interpolator,它的动画效果看起来就像是物体从速度为 0 开始逐渐加速,然后再逐渐减速直到 0 的运动。它的速度 / 时间曲线以及动画完成度 / 时间曲线都是一条正弦 / 余弦曲线。

用途:就像上面说的,它是一种最符合物理世界的模型,所以如果你要做的是最简单的状态变化(位移、放缩、旋转等等),那么一般不用设置 Interpolator,就用这个默认的最好。

LinearInterpolator

匀速。

AccelerateInterpolator

持续加速。在整个动画过程中,一直在加速,直到动画结束的一瞬间直接停止。

别看见它加速骤停就觉得没什么用,它主要用在离场效果中,比如某个物体从界面中飞离,就可以用这种效果。它给人的感觉就会是从零起步,加速飞走了。到了最后动画骤停的时候,物体已经飞出用户视野看不到了,所以他们是并不会察觉到这个骤停的。

DecelerateInterpolator

持续减速直到 0。动画开始的时候是最高速度,然后在动画过程中逐渐减速,直到动画结束的时候恰好减速到 0。它的效果和上面这个 AccelerateInterpolator 相反,适用场景也和它相反:它主要用于入场效果,比如某个物体从界面的外部飞入界面后停在某处。它给人的感觉会是「咦飞进来个东西,让我仔细看看,哦原来是 XXX」。

AnticipateInterpolator

先回拉一下再进行正常动画轨迹。效果看起来有点像投掷物体或跳跃等动作前的蓄力。如果是平移动画,那么就是位置上的回拉;如果是放大动画,那么就是先缩小一下再放大;其他类型的动画同理。这个 Interpolator 没有通用的适用场景,根据具体需求和设计师的偏好而定。

OvershootInterpolator

动画会超过目标值一些,然后再弹回来。效果看起来有点像你一屁股坐在沙发上后又被弹起来一点的感觉。没有通用的适用场景。

AnticipateOvershootInterpolator

上面这两个的结合版:开始前回拉,最后超过一些然后回弹。

BounceInterpolator

在目标值处弹跳。有点像玻璃球掉在地板上的效果。

CycleInterpolator

这个也是一个正弦 / 余弦曲线,不过它和 AccelerateDecelerateInterpolator 的区别是,它可以自定义曲线的周期,所以动画可以不到终点就结束,也可以到达终点后回弹,回弹的次数由曲线的周期决定,曲线的周期由 CycleInterpolator() 构造方法的参数决定。

PathInterpolator

自定义动画完成度 / 时间完成度曲线。用这个 Interpolator 你可以定制出任何你想要的速度模型。定制的方式是使用一个 Path 对象来绘制出你要的动画完成度 / 时间完成度曲线。例如:

Path interpolatorPath = new Path();
...
// 匀速
interpolatorPath.lineTo(1, 1);

除了上面的这些,Android 5.0 (API 21)引入了三个新的 Interpolator 模型,并把它们加入了 support v4 包中。这三个新的 Interpolator 每个都和之前的某个已有的 Interpolator 规则相似,只有略微的区别。

FastOutLinearInInterpolator

加速运动。这个 Interpolator 的作用你不能看它的名字,一会儿 fast 一会儿 linear 的,完全看不懂。其实它和 AccelerateInterpolator 一样,都是一个持续加速的运动路线。只不过 FastOutLinearInInterpolator 的曲线公式是用的贝塞尔曲线,而 AccelerateInterpolator 用的是指数曲线。具体来说,它俩最主要的区别是 FastOutLinearInInterpolator 的初始阶段加速度比 AccelerateInterpolator 要快一些。

FastOutSlowInInterpolator

先加速再减速。同样也是先加速再减速的还有前面说过的 AccelerateDecelerateInterpolator,不过它们的效果是明显不一样的。FastOutSlowInInterpolator 用的是贝塞尔曲线,AccelerateDecelerateInterpolator 用的是正弦 / 余弦曲线。具体来讲, FastOutSlowInInterpolator 的前期加速度要快得多。

LinearOutSlowInInterpolator

持续减速。它和 DecelerateInterpolator 比起来,同为减速曲线,主要区别在于 LinearOutSlowInInterpolator 的初始速度更高。对于人眼的实际感觉,区别其实也不大,不过还是能看出来一些的。

 

2.3 监听器

通过添加一个 AnimatorListener 就可以实现动画的监听,需要实现 4 个方法:

animator.addListener(new AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
    }
    @Override
    public void onAnimationEnd(Animator animation) {
        Toast.makeText(MainActivity.this,"Animation End",Toast.LENGTH_SHORT).show();
    }
    @Override
    public void onAnimationCancel(Animator animation) {
    }
    @Override
    public void onAnimationRepeat(Animator animation) {
    }
});

如果每次监听都需要实现这么多方法,未免太麻烦了一点。因此 Android 为我们提供了另一种方法来添加动画的监听事件:在添加 AnimatorListener 的时候,传入 AnimatorListenerAdapter 即可。这样我们就只需要实现自己需要的方法即可。

animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        Toast.makeText(MainActivity.this,"Animation End",Toast.LENGTH_SHORT).show();
    }
});

 

2.4 多种属性动画同时作用

2.4.1 PropertyValuesHolder

当我们把几种动画按顺序写下时,运行程序,会发现效果是三种属性动画的叠加。由此可以发现,属性动画在调用 start 方法后,实际上是一个异步的过程。因此我们就可以看到三个属性动画同时作用的效果。我们可以使用 PropertyValuesHolder 来实现。其构造函数仅仅比 ObjectAnimator 少了一个作用对象参数。之后通过ObjectAnimator 的 ofPropertyValuesHolder 方法,传入作用对象以及要同时作用的 PropertyValuesHolder 即可执行。可以看到下面的代码示例:

PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("translationX",0F,200F);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("rotationX",0F,360F);
PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("translationY",0F,200F);
ObjectAnimator.ofPropertyValuesHolder(mIvPicture,p1,p2,p3).setDuration(1000).start();

Google 在 PropertyValuesHolder 内部进行了一些优化,使得我们使用多个属性动画时更加有效率,节省系统资源。

2.4.2 AnimatorSet 属性集合

我们其实还可以通过 AnimatorSet,来实现同样的效果

playTogether 方法

这里我们调用了 set 的 playTogether 方法,使得这些方法同时执行:

AnimatorSet set = new AnimatorSet();
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mIvPicture, "translationX", 0F, 200F);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mIvPicture, "rotationX", 0F, 360F);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mIvPicture, "translationY", 0F, 200F);
set.playTogether(animator1,animator2,animator3);
set.setDuration(1000);
set.start();

playSequentially 方法

除了 playTogether 方法外,AnimatorSet 还提供了 playSequentially 方法,它可以使得动画按顺序执行。具体顺序取决于调用时的参数顺序。

AnimatorSet set = new AnimatorSet();
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mIvPicture, "translationX", 0F, 200F);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mIvPicture, "rotationX", 0F, 360F);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mIvPicture, "translationY", 0F, 200F);
set.playSequentially(animator1,animator2,animator3);
set.setDuration(1000);
set.start();

play 与 with、after 方法

我们除了可以用上述方法来让动画按顺序执行外,也可以通过 AnimatorSet 的 play、with、after、before 等方法相组合来控制动画播放关系。例如如下的代码就可以实现先平移,再旋转的效果:

set.play(animator1).with(animator3);
set.play(animator2).after(animator1);

 

2.5 ValueAnimator

ValueAnimator 本身不作用于任何一个属性,也不提供任何一种动画。它就是一个数值发生器,可以产生想要的各种数值。Android 系统为它提供了很多计算数值的方法,如 int、float 等等。我们也可以自己实现计算数值的方法。其实,在属性动画中,如何产生每一步的动画效果,都是通过 ValueAnimator 计算出来的。比如我们要实现一个从 0-100 的位移动画。随着动画时间的持续,它产生的值也会从 0-100 递增。通过这个 ValueAnimator 产生的值,再进行属性的设置即可。那么 ValueAnimator 究竟是如何产生这些值的呢?

首先 ValueAnimator 会根据会根据动画已进行的时间与它持续的总时间的比值,产生一个 0-1 的时间因子。有了这样的时间因子,经过相应的变换,就可以根据初始值和最终值来生成中间的相应值。同时,通过插值器的使用,我们还可以进一步控制每一个时间因子产生值的变化速率。如果我们使用的是线性插值器,那么它生成值的时候就会呈一个线性变化。如果我们使用一个加速度插值器,那么它生成值时便会呈一个二次曲线,增长率越来越快。

由于 ValueAnimator 不作用于任何一个属性,也不提供任何一种动画。因此并没有 ObjectAnimator 使用得广泛。实际上,ObjectAnimator 就是基于 ValueAnimator 进行的一次封装。我们可以查看 ObjectAnimator 的源码,会发现它继承自 ValueAnimator,是它的一个子类。正是 ValueAnimator 产生的变化值,才使得 ObjectAnimator 可以将它应用于各个属性。

我们可以通过 ValueAnimator 的 ofXXX 产生一个 XXX 类型的值(如ofInt),然后为 ValueAnimator 添加一个更新的回调事件。在回调事件中,通过参数 animation 的 getAnimationValue() 方法,来获取对应的 value。有了这个值,我们就可以实现我们所有想要的动画效果。比如此处就通过 ValueAnimator 实现了一个计时器的动画效果。

ValueAnimator animator = ValueAnimator.ofInt(0,100);
animator.setDuration(5000);
animator.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Integer value = (Integer) animation.getAnimatedValue();
        mButton.setText(""+value);
    }
});
animator.start();

自定义数值生成器。前面提到,ValueAnimator 可以创建自定义的数值生成器,做法就是调用 ValueAnimator 的 ofObject 方法,创建一个 TypeEvaluator 作为参数。之后我们可以通过重写 TypeEvaluator 的 evaluate 方法,来按照自己的规则返回具体的值。

ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator() {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        //计算
        return null;    //返回值
    }
});

我们来看一下 evaluate 方法的几个参数

  • float fraction:前面提到的时间因子
  • Object startValue:起始值
  • Object endValue:结束值

通过这三个值,我们就可以经过计算产生所有我们想要的值。其实,通过 TypeEvaluator,我们不光能产生普通的数据,还能结合泛型,我们还能定义更加复杂的数据:我们可以在创建 TypeEvaluator 时指定具体类型,来达到更丰富的效果。比如这里就用到了一个名为 PointF 的数据类型:

ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator<PointF>() {
    @Override
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
        //计算
        return null;    //返回值
    }
});
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值