当前位置: 首页 > 工具软件 > View Animator > 使用案例 >

Android笔记之 属性动画(Animator)

夏炎彬
2023-12-01
属性动画(3.0 之后新增)

优点:
         在Animator 框架中使用最多的就是 AnimatorSet 和 ObjectAnimator 配合使用,ObjectAnimator 进行精细化控制,只控制一个对象的一个属性值,使用多个 ObjectAnimator 组合到 AnimatorSet 形成一个动画。

        属性动画通过调用属性的 get、set 方法来真实的控制一个 View 的属性值,其可以达到的效果是:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变,因此强大的属性动画框架基本可以实现所有的动画效果

ObjectAnimator 
1、原理:
        属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。总结一下,你对object的属性xxx做动画,如果想让动画生效,要同时满足两个条件:
        1. object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满,程序直接Crash
        2. object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)

以上条件缺一不可

    ObjectAnimator  继承自 ValueAnimator,通过ObjectAnimator  的静态工厂类直接创建ObjectAnimator  对象,参数包括一个对象和对象的属性名字(该属性必须有get、set函数,因为内部是通过反射机制来调用set 函数修改对象的属性值,否则ObjectAnimator就无法起效),可通过setInterpolator 设置相应的差值器
//参数一 要操控的View
//参数二 要操控的属性
//参数三 一个可变的数组参数,需要传进去该属性变化的一个取值过程,这里只设置了一个参数,及变化到300
ObjectAnimator animator = ObjectAnimator.ofFloat(view,
                                                 "translationX", 
                                                 300);
        animator.setDuration(300);
        animator.setInterpolator(new TimeInterpolator() {//设置差值器
            @Override
            public float getInterpolation(float input) {
                return 100;
            }
        });
        animator.start();



2、常见的属性和API
API:
  • Duration动画的持续时间,默认300m 
  • Repeat count and behavior:重复次数、以及重复模式;可以定义重复多少次;重复时从头开始,还是反向。
  • Animator sets: 动画集合,你可以定义一组动画,一起执行或者顺序执行。
  • Frame refresh delay:帧刷新延迟,对于你的动画,多久刷新一次帧;默认为10ms,但最终依赖系统的当前状态;基本不用管。
  • Time interpolation:时间差值
类:
  • AnimatorInflater 用户加载属性动画的xml文件
  • ObjectAnimator 动画的执行类,
  • ValueAnimator 动画的执行类,
  • PropertyValuesHolder 控制一组动画同时执行,无法实现顺序的控制
  • AnimatorSet 用于控制一组动画的执行:线性,一起,每个动画的先后执行等。
  • AnimatorInflater 用户加载属性动画的xml文件
  • TypeEvaluator 类型估值,主要用于设置动画操作属性的值。
  • TimeInterpolator 时间插值

可直接使用的属性动画属性值:
  • translationX 和 translationY:这两个属性作为一种增量来控制着 View 对象从它布局容器的左上角坐标偏移的位置
  • ratation、ratationX 和 ratationY:控制View 对象围绕它的支点进行2D、3D旋转
  • scaleX 和scaleY:控制着 View 对象围绕它的支点进行2D 缩放
  • pivotX 和pivotY:控制着 View 对象的支点位置,围绕这个支点进行旋转和缩放变换处理,默认情况下支点位置为 View 的中心点
  • x 和y:描述了 View 对象在它的容器中的最终位置,它是最初的左上角标和translationX、translationY的
  • alpha:表示 View 的透明度,默认值是1(不透明),0代表完全透明(即不可见)

如果View 的一个属性没有get、set方法,怎么使用属性动画?
1、用一个类来包装原始对象,间接为其提供getset方法
2 采用 ValueAnimator ,监听动画过程,自己实现属性的改变
  • 用一个类来包装原始对象,间接为其提供getset方法
       这是一个很有用的解决方法,因为用起来很方便,也很好理解,下面将通过一个具体的例子来介绍它
private void performAnimate() {  
    ViewWrapper wrapper = new ViewWrapper(mButton);  
    ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();  
}  
  
@Override  
public void onClick(View v) {  
    if (v == mButton) {  
        performAnimate();  
    }  
}  
  
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();//重绘  
    }  
} 


        上述代码5s内让Button的宽度增加到500px,为了达到这个效果,我们提供了ViewWrapper类专门用于包装View,具体到本例是包装Button,然后我们对ViewWrapper的width属性做动画,并且在setWidth方法中修改其内部的target的宽度,而target实际上就是我们包装的Button,这样一个间接属性动画就搞定了。上述代码同样适用于一个对象的其他属性。其主要还是改变View 的LayoutParams 属性并重绘,达到属性动画的效果

  • 采用ValueAnimator,监听动画过程,自己实现属性的改变,不需要有get、set方法
       首先说说啥是ValueAnimatorValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。
private void performAnimate(final View target, final int start, final int end) {  
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);  
  
    valueAnimator.addUpdateListener(new AnimatorUpdateListener() {  
  
        //持有一个IntEvaluator对象,方便下面估值的时候使用  
        private IntEvaluator mEvaluator = new IntEvaluator();  
  
        @Override  
        public void onAnimationUpdate(ValueAnimator animator) {  
            //获得当前动画的进度值,整型,1-100之间  
            int currentValue = (Integer)animator.getAnimatedValue();  
            Log.d(TAG, "current value: " + currentValue);  
  
            //计算当前进度占整个动画过程的比例,浮点型,0-1之间  
            //float fraction = currentValue / 100f;  
            float fraction     = animation.getAnimatedFraction();
            //直接调用整型估值器通过比例计算出宽度,然后再设给Button  
            target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);  
            target.requestLayout();  
        }  
    });  
  
    valueAnimator.setDuration(5000).start();  
}  
  
@Override  
public void onClick(View v) {  
    if (v == mButton) {  
        performAnimate(mButton, mButton.getWidth(), 500);  
    }  
}  



上述代码的动画效果图和采用ViewWrapper是一样的

ValueAnimator
        ValueAnimator 本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程, 其一般的使用方法如下所示:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);//从0到1过渡  
anim.setDuration(300);  
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    @Override  
    public void onAnimationUpdate(ValueAnimator animation) {  
        float currentValue = (float) animation.getAnimatedValue();  
        Log.d("TAG", "cuurent value is " + currentValue);  
    }  
});  
anim.start();  

另外ofFloat()方法当中是可以传入任意多个参数的,因此我们还可以构建出更加复杂的动画逻辑,比如说将一个值在5秒内从0过渡到5,再过渡到3,再过渡到10,就可以这样写:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 5f, 3f, 10f);  
anim.setDuration(5000);  
anim.start();  

PropertyValuesHolder
    主要是针对属性动画中,同一个对象的多个属性同时进行变换,同时执行多种变化,其代码如下所示:
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("alpha", 1f, 0, 1f);  
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);  
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);  
ObjectAnimator.ofPropertyValuesHolder(mBlueBall, pvh1, pvh2, pvh3).setDuration(1000).start();  

AnimatorSet的使用
1、如何使用xml文件来创建属性动画
    首先在res下建立animator文件夹,然后建立res/animator/scalex.xml
    在XML文件中我们一共可以使用如下三种标签:
  • <animator>  对应代码中的ValueAnimator
  • <objectAnimator>  对应代码中的ObjectAnimator
  • <set>  对应代码中的AnimatorSet
那么比如说我们想要实现一个从0到100平滑过渡的动画,在XML当中就可以这样写:
<animator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="0"  
    android:valueTo="100"  
    android:valueType="intType"/>  

而如果我们想将一个视图的alpha属性从1变成0,就可以这样写:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="1"  
    android:valueTo="0"  
    android:valueType="floatType"  
    android:propertyName="alpha"/> 
组合动画
<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:ordering="together" >  
  
    <objectAnimator  
        android:duration="1000"  
        android:propertyName="scaleX"  
        android:valueFrom="1"  
        android:valueTo="0.5" >  
    </objectAnimator>  
    <objectAnimator  
        android:duration="1000"  
        android:propertyName="scaleY"  
        android:valueFrom="1"  
        android:valueTo="0.5" >  
    </objectAnimator>  
  
</set>  

使用set标签,有一个orderring属性设置为together,【还有另一个值:sequentially(表示一个接一个执行)】。
        // 加载动画  
        Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scale);  
        mMv.setPivotX(0);  
        mMv.setPivotY(0);  
        //显示的调用invalidate  
        mMv.invalidate();  
        anim.setTarget(mMv);  
        anim.start();  

调用AnimatorInflater的loadAnimator来将XML动画文件加载进来,然后再调用setTarget()方法将这个动画设置到某一个对象上面,最后再调用
start()方法启动动画就可以了。


AnimatorSet提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一
个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
  • after(Animator anim)   将现有动画插入到传入的动画之后执行
  • after(long delay)   将现有动画延迟指定毫秒后执行
  • before(Animator anim)   将现有动画插入到传入的动画之前执行
  • with(Animator anim)   将现有动画和传入的动画同时执行
好的,有了这四个方法,我们就可以完成组合动画的逻辑了,那么比如说我们想要让TextView先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作,就可以这样写:
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);  
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);  
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
AnimatorSet animSet = new AnimatorSet();  
animSet.play(rotate).with(fadeInOut).after(moveIn);  
animSet.setDuration(5000);  
animSet.start();  

2、布局动画(Layout Animations
        主要使用LayoutTransition为布局的容器设置动画,当容器中的视图层次发生变化时存在过渡的动画效果。
最简单的布局动画是在 ViewGroup 的XML 中,使用以下代码打开布局动画:
android:animaterLayoutChanges="true"

     通过以上代码设置,当 ViewGroup 添加 View 时,子 View 会呈现逐渐显示的默认的过渡效果,且该方式无法使用自定义的动画来替换这个效果

LayoutAnimationController和LayoutTransition。
LayoutAnimationController:
    该动画主要用于针对于一个ViewGroup初始化时对于其下子控件的动画操作。可通过它来自定义一个子 View 的过渡效果,代码如下:
        LinearLayout ll = (LinearLayout)findViewById(R.id.ll);
        //通过加载XML动画设置文件来创建一个Animation对象;
        Animation animation= AnimationUtils.loadAnimation(this, R.anim.listview_item_anim);   
        //得到一个LayoutAnimationController对象;
        LayoutAnimationController controller = new LayoutAnimationController(animation,//需要作用的动画
                                                                             0.5F);//每个子View 显示的时间   
        //设置控件显示的顺序;
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);  
        //设置控件显示间隔时间;
        //controller.setDelay(0.3f);   
        //为ViewGroup设置布局动画;
        ll.setLayoutAnimation(controller);

当子 View 显示的时间不为0 时,可以设置子 View 的显示顺序,如下所示:
  • ORDER_NORMAL:正常顺序。
  • ORDER_REVERSE:倒序。
  • ORDER_RANDOM:随机。
间隔时间,可设置的值0.0~1.0(百分比值)。及上一个控件显示到多少是下一个控件开始执行动画。
因为其只是在控件初始化时调用,并且是对控件整体的子控件加载一个按照固定顺序显示的动画。我们可以调用ViewGroup.startLayoutAnimation()让其重新显示一遍。


LayoutTransition:
基本代码为:
LayoutTransition transition = new LayoutTransition();  
    transition.setAnimator(LayoutTransition.CHANGE_APPEARING,//要设置动画的目标  
            transition.getAnimator(LayoutTransition.CHANGE_APPEARING));//要设置的动画。  
    transition.setAnimator(LayoutTransition.APPEARING,  
            null);  
    transition.setAnimator(LayoutTransition.DISAPPEARING,  
            null);  
    transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,  
            null);  
    mGridLayout.setLayoutTransition(transition);  
过渡的类型一共有四种:
    LayoutTransition.APPEARING  当一个 View ViewGroup 中出现时,对此 View 设置的动画
    LayoutTransition.CHANGE_APPEARING  当一个 View ViewGroup 中出现时,对此 View 对其他 View 位置造成影响,对其他 View 设置的动画
    LayoutTransition.DISAPPEARING  当一个 View ViewGroup 中消失时,对此 View 设置的动画
    LayoutTransition.CHANGE_DISAPPEARING  当一个 View ViewGroup 中消失时,对此 View 对其他 View 位置造成影响,对其他 View 设置的动画
    LayoutTransition.CHANGE  不是由于 View 出现或消失造成对其他 View 位置造成影响,然后对其他 View 设置的动画。
注意动画到底设置在谁身上,此View还是其他View


Animator监听器
        Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了(比如动画何时开始,何时结束,然后在开始或者结束的时候去执行一些逻辑处理)。
        大家已经知道,ObjectAnimator是继承自ValueAnimator的,而ValueAnimator又是继承自Animator的,因此不管是ValueAnimator还是ObjectAnimator都是可以使用addListener()这个方法的。另外AnimatorSet也是继承自Animator的,因此addListener()这个方法算是个通用的方法。
添加一个监听器的代码如下所示:

anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {//在动画开始的时候调用  
    }  
  
    @Override  
    public void onAnimationRepeat(Animator animation) {//在动画重复执行的时候调用  
    }  
  
    @Override  
    public void onAnimationEnd(Animator animation) {//在动画结束的时候调用  
    }  
  
    @Override  
    public void onAnimationCancel(Animator animation) {//在动画被取消的时候调用  
    }  
});  

        如果我们只想要监听动画结束这一个事件,那么每次都要将四个接口全部实现一遍就显得非常繁琐。为此Android提供了一个适配器类,叫作AnimatorListenerAdapter,使用这个类就可以解决掉实现接口繁琐的问题了,如下所示:
anim.addListener(new AnimatorListenerAdapter() {  
    @Override  
    public void onAnimationEnd(Animator animation) {  
        
    }  
});  

使用动画的注意事项:
1、OOM 问题
         该问题主要出现在帧动画中,当图片数量较多且图片比较大时就极易出现OOM,这个在实际的开发中尤其要注意,尽量避免使用帧动画

2、内存泄漏问题
         在属性动画中有一类无限循环的动画,这类动画需要在Activity 退出时及时停止,否则将导致Activity 无法释放从而造成内存蟹柳,通过验证后发现View 动画并不存在此问题

3、View 动画问题
         V iew 动画是对View 的影像做动画,并不是真正的改变View 的状态,因此有时候会出现动画完成后 View 无法隐藏的现象,即           setVisibility(View.GONE)失效了,这个时候只要调用 view.clearAnimation() 清除动画即可解决此问题

4、不要使用px
         在进行动画的过程中,要尽量使用dp,使用px会在不同的设备上有不同的效果

5、动画元素的交互
         将View 移动后,在Android3.0之前的系统,不管是View 动画还是属性动画,新位置均无法出发单击事件,同时,老位置仍然可以触发单击事件。3.0之后属性动画的单击事件触发位置为移动后的位置,但是View 动画仍然在原位置

6、硬件加速
         使用动画的过程中,建议开启硬件加速,这样会提高动画的流畅性。

 类似资料: