Android学习笔记之动画(二)——属性动画

属性动画

属性动画是从Android 3.0 开始引入的新特性,和View动画不同,它可以对任何对象做动画,甚至可以没有对象(ValueAnimator)除此之外,属性动画通过ValueAnimator、ObjectAnimator和AnimatorSet 等概念,可以实现比View动画要丰富得多的动画效果。

在Android 3.0之前的版本可以采用开源动画库nineoldandroids来使用属性动画来兼容以前的版本,虽然本质上还是View动画。

PS:属性动画的默认时间间隔为300ms,默认的帧率为10ms/帧

ValueAnimator

ValueAnimator是整个属性动画机制当中最核心的一个类,因为属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。

但是它的用法并不复杂,可是这里我先不给出它的具体用法示例,因为单一的 ValueAnimator 动画仅仅是数值的动画,并没有视觉效果,通常在使用它的时候会和监听器一同使用,因此我会在之后的部分给出它的用法示例。

ObjectAnimator

相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,因为ValueAnimator只不过是对值进行了一个平滑的动画过渡,但我们实际使用到这种功能的场景好像并不多。而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。

不过虽说ObjectAnimator会更加常用一些,但是它其实是继承自ValueAnimator的,底层的动画实现机制也是基于ValueAnimator来完成的,因此ValueAnimator仍然是整个属性动画当中最核心的一个类。那么既然是继承关系,说明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它们的用法也非常类似,比如这里我设置了一个改变背景颜色的动画,如下:

/**
 * 背景色变换动画
 * */
private void backgroundColorAnim(Object target){
    colorAnim = ObjectAnimator.ofInt((View)target,"backgroundColor",0xFFFF8080,0xFF8080FF);
    colorAnim.setDuration(3000);
    colorAnim.setEvaluator(new ArgbEvaluator());
    //无限循环
    colorAnim.setRepeatCount(ValueAnimator.INFINITE);
    //反转变换
    colorAnim.setRepeatMode(ValueAnimator.REVERSE);
    colorAnim.start();
}

可以看到,我们调用了ofInt()方法来去创建一个ObjectAnimator的实例。其中第一个参数要求传入一个object对象,我们想要对哪个对象进行动画操作就传入什么。第二个参数是想要对该对象的哪个属性进行动画操作,由于我们这里想要改变背景颜色,因此这里传入"backgroundColor"。后面的参数就是不固定长度了,想要完成什么样的动画就传入什么值,这里传入的值就表示将指定控件从红色变为蓝色。之后调用setDuration()方法来设置动画的时长,之后调用setEvaluator()方法来设置估值器,然后设置成无限反转动画,最后调用start()方法启动动画。

然后我们再看一个简单点的平移动画(旋转,透明度,缩放都可以由此举一反三),如下:

/**
 * 向左平移然后归位
 * */
private void translateAnim(Object target){
    View view = (View) target;
    float curTranslationX = view.getTranslationX();
    transAnim = ObjectAnimator.ofFloat(view,"translationX",curTranslationX,-20,curTranslationX);
    transAnim.setDuration(3000);
    transAnim.start();
}

AnimatorSet 

也叫动画集合、组合动画、复合动画。具体的使用方法跟单一动画也没有太大区别,只是创建的是AnimatorSet对象,然后调用了play()方法,如下:

/**
 * 复合动画
 * */
private void compoundAnim(Object target){
    View view = (View) target;
    set = new AnimatorSet();
    set.playTogether(
            ObjectAnimator.ofFloat(view,"rotationX",0,360),
            ObjectAnimator.ofFloat(view,"rotationY",0,180),
            ObjectAnimator.ofFloat(view,"rotation",0,-90),
            ObjectAnimator.ofFloat(view,"alpha",1,0.25f,1),
            ObjectAnimator.ofFloat(view,"scaleX",1,0.5f),
            ObjectAnimator.ofFloat(view,"scaleY",1,0.5f)
    );
    set.setDuration(5000).start();
}

可以看到,上述代码调用的是playTogether()方法,运行起来就是上面的六个动画效果复合起来同时播放。但如果我们不想让他们全部同时播放,比如旋转类型的动画全部组合在一起同时播放,之后剩下的动画再组合在一起同时播放呢?简单,AnimatorSet还有一系列方法用来解决这类问题:

  • after(Animator anim)   将现有动画插入到传入的动画之后执行
  • after(long delay)   将现有动画延迟指定毫秒后执行
  • before(Animator anim)   将现有动画插入到传入的动画之前执行
  • with(Animator anim)   将现有动画和传入的动画同时执行

所以,想要实现上述效果只用做下更改即可,如下:

set.play(ObjectAnimator.ofFloat(view,"rotationX",0,360))
        .with(ObjectAnimator.ofFloat(view,"rotationY",0,180))
        .with(ObjectAnimator.ofFloat(view,"rotation",0,-90))
        .before(ObjectAnimator.ofFloat(view,"alpha",1,0.25f,1))
        .before(ObjectAnimator.ofFloat(view,"scaleX",1,0.5f))
        .before(ObjectAnimator.ofFloat(view,"scaleY",1,0.5f));

值得注意的是,虽然后三个动画是同时播放的,但是我们不能想当然的用with。因为这是链式结构,即使是后面的动画,调用方法的主体还是最开始的那个动画集合,因此要使用before。

属性动画除了通过代码来实现以外,还可以通过XML文件来定义。属性动画需要定义在res/animator/目录下,比如想要实现上述六个动画效果同时播放的复合动画,我可以这么定义XML文件:

<?xml version="1.0" encoding="utf-8"?>
<set android:ordering="together"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="5000"
        android:propertyName="rotationX"
        android:valueFrom="0"
        android:valueTo="360"
        android:valueType="floatType"
        />
    <objectAnimator
        android:duration="5000"
        android:propertyName="rotationY"
        android:valueFrom="0"
        android:valueTo="180"
        android:valueType="floatType"
        />
    <objectAnimator
        android:duration="5000"
        android:propertyName="rotation"
        android:valueFrom="0"
        android:valueTo="-90"
        android:valueType="floatType"
        />
    <objectAnimator
        android:duration="5000"
        android:propertyName="scaleX"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"
        />
    <objectAnimator
        android:duration="5000"
        android:propertyName="scaleY"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"
        />
    <objectAnimator
        android:duration="2500"
        android:propertyName="alpha"
        android:valueFrom="1"
        android:valueTo="0.25"
        android:valueType="floatType"
        />
    <objectAnimator
        android:startOffset="2500"
        android:duration="2500"
        android:propertyName="alpha"
        android:valueFrom="0.25"
        android:valueTo="1"
        android:valueType="floatType"
        />
</set>

然后代码控制加载XML文件中的动画即可,如下:

/**
 * 通过xml文件实现相同的复合动画
 * */
private void compoundAnimByXml(Object target){
    set = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.compound_anim);
    set.setTarget(target);
    set.start();
}

属性动画的监听器

属性动画提供了监听器用于监听动画的播放过程,主要有如下两个接口:AnimatorUpdateListener 和 AnimatorListener

AnimatorListener 的定义如下:

public static interface AnimatorListener {
    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
}

可以看出,它可以监听动画的开始、结束、取消以及重复播放。同时为了方便开发,系统还提供了 AnimatorListenerAdapter 这个类,它是AnimatorListener的适配器类,这样我们就可以有选择地实现上面的四个方法了,毕竟不是所有方法我们都感兴趣。

AnimatorUpdateListener 的定义如下:

public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animation);
}

可以看到,只有一个方法,它会监听整个动画过程,动画每播放一帧,onAnimationUpdate就会被调用一次(每10ms调用一次)

对任意属性做动画

其实到这里,属性动画的用法已经介绍的差不多了,但是有关属性动画还有个非常重要的点还没提及。想要让属性动画生效,需要满足两个条件:

  1. 被属性动画操作的对象必须要有set方法来设置对应属性,如果动画的时候没有传递初始值,那么还要有get方法来读取对应属性的初始值
  2. 被属性动画操作的对象的set方法对属性值做出的改变能通过某种方法反映到UI上面 

可能看上述条件还比较绕,接下来看一个属性动画失效的例子,改变按钮宽度。通过之前的介绍,使用属性动画改变某个控件的宽度属性,应该这么写:  

ObjectAnimator.ofInt(mButton,"width",800).setDuration(5000).start();

然而实际运行起来是没有效果的。因为Button是继承TextView的,而TextView的 setWidth 方法设置的不是我们看到的那个宽度,而是最大宽度和最小宽度。因此没有满足上述条件第二条。所以属性动画没有生效。解决办法有两种:

1.用一个类来包装原始对象,间接为其提供get、set方法

private static class ViewWrapper {
    private View mTarget;
    public ViewWrapper (View target){
        mTarget = target;
    }
    public int getWidth(){
        return mTarget.getLayoutParams().width;
    }
    public void setWidth(int width){
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}

然后就能正常使用了

private void changeWidthAnim(Object target){
    ViewWrapper wrapper = new ViewWrapper((View)target);
    changeWidthAnim = ObjectAnimator.ofInt(wrapper,"width",800);
    changeWidthAnim.setDuration(3000);
    changeWidthAnim.start();
}

2.结合ValueAnimator 和 监听器,自己实现属性的改变

    /**
     * 变更按钮宽度动画(通过监听器来实现)
     * */
    private void changeWidthAnimByListener(final View target, final int start, final int end){
        valueAnimator = ValueAnimator.ofInt(1,100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            //新建一个 IntEvaluator 对象,方便待会估值时使用
            private IntEvaluator mEvaluator = new IntEvaluator();
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
//                //获取当前动画进度 1-100 整型
//                int currentValue = (int) animation.getAnimatedValue();
                //获取当前进度占整个动画过程的比例 0-1 浮点型
                float fraction = animation.getAnimatedFraction();
                //通过整型估值器计算出当前宽度并设给View
                target.getLayoutParams().width = mEvaluator.evaluate(fraction,start,end);
                target.requestLayout();
            }
        });
        valueAnimator.setDuration(3000).start();
    }

可以看到,这里我设置了一个ValueAnimator让它从1平滑过渡到100,然后在监听器中通过估值器算出随时间变化百分比的宽度变化量,再将结果宽度赋值给操控对象,最后刷新界面即可。

Demo:https://github.com/Ein3614/AnimationTest

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值