关于Android的几种基本动画

一直以来各种动画效果也只是零碎地在需要用到的时候复制粘贴,或者直接自己Draw一遍,并且记下一些零碎的代码段。然而最近比赛项目又碰到了需要各种动画过场的情况,想想这问题也是积攒已久了,于是准备认真的学习一下。

​ 首先,Android里的动画分为三种:PropertyAnimation(属性动画)ViewAnimation(视图动画)DrawableAnimation(帧动画,By the way,官网中翻是可绘制动画,感觉就很不对劲 -_-)。其中属性动画在API 11之后加入,它可以对所有对象(包括未渲染到屏幕的对象)添加动画效果,拓展性强且特点多样,是官方推荐的动画实现手段;视图动画则是比较老的动画实现手段,其仅对View起效,并且只有视觉上的变化,而不实际改变View的位置或其他真实属性;可绘制动画则适用于借助Drawable资源实现的情景,通过切换帧实现动画效果,比如常见的进度条旋转与游戏中人物走动效果等。下面依次说一下各个动画的用法。

视图动画

​ 首先讲讲ViewAnimation,为什么不是PropertyAnimation?大概是因为它的文档比较短.(PropertyAnimation:明明是我先,出现在正文也好,官方推荐也好...)。记得当年年轻的我某一日终于忍受不了各种View硬生生直挺挺地在面前排布时,怒而百度,第一个搜出来的教程就是关于它的,然后我就实现了Coding以来第一个动画:一个很蠢的点击按钮旋转滚动拉出一个EditText的动画。ViewAnimation实际上是为了在View上实现TweenedAnimation,即补间动画(这个名词在各种教程技术文里出现的频率明显也更高)。它的实现效果就是简单地设置View进行自定义起始点和结束点的平移、旋转、大小和透明度变换等等,能够用XML与Java代码两种方式实现,官方推荐是用XML文件(位于res/anim下)实现,这样会使代码结构看起来更清晰。下面是XML代码的各种属性和格式:



    
    
    
    
    
        ...
    

另外它们都有继承自Animation的通用属性:

属性名 说明 参数类型
android:detachWallpaper 窗口动画的特殊选项,当窗口处于壁纸之上时,选择是否让壁纸与其一起运动 boolean
android:duration 动画的执行时间,以毫秒为单位,默认为300ms integer
android:fillAfter 为true时,动画变换效果将保持。(仅在set中设置有效) boolean
android:fillBefore 为true时,动画运行结束后停留在第一帧。默认为true。 boolean
android:fillEnabled 设为true时 fiilBefore才有效,为false且动画未设置给View时,让fillAfter为true boolean
android:interpolator 设置相应插值器。 refrence
android:repeatCount 设置动画重复次数 integer
android:repeatMode 动画播完一遍再重复时的行为模式,有restart和reverse两种模式。 string
android:startOffset 动画开始前的延迟时间,以毫秒为单位。 integer
android:zAdjustment 允许调整在动画持续时间内动画的内容的Z排序。有bottom(ffffff)、normal(0)与top(1)三种模式,默认为normal(保持现有顺序)。 integer

关于上面的interpolator,有如下系统默认调用:

名称 资源ID 说明
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 开始和结束较慢中间加快。
AccelerateInterpolator @android:anim/accelerate_interpolator 开始较慢,之后加速。
AnticipateInterpolator @android:anim/anticipate_interpolator 先减速再加速。
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 先减速再加速直致越界,最后回到原速。
BounceInterpolator @android:anim/bounce_interpolator 上下起伏到后面趋于平坦。
CycleInterpolator @android:anim/cycle_interpolator 重复动画指定的循环次数。 变化率遵循正弦曲线。(也就是有正有负,可以制造回弹效果)
DecelerateInterpolator @android:anim/decelerate_interpolator 减速变化
LinearInterpolator @android:anim/linear_interpolator 线性变化
OvershootInterpolator @android:anim/overshoot_interpolator 减速增长然后越界,回归原点。

上面的插值变化实际表示各种速度变化曲线,可以参考blog中的图参考图例

在写好动画相关函数后可以用Java代码调用:

Animation anim = AnimationUtils.loadAnimate(context,R.anim.demo);
//方式一
view.startAnimation(anim);//立即开始动画,考虑上面的startOffset属性
//方式二
anim.setStartTime(time)//设置动画开始时间,参数是格林威治时间至今的毫秒数
view.setAnimation(anim);//设置动画,当其动画时间到达时则自然开始动画。

另外,需要注意的地方有:一、set内的所有动画默认都是同时运行的,所以当需要动画有先后顺序时需要用startOffset来进行延迟播放。二、位移或者放大类动画并不会拓展View本身的空间,当其越界时,便不会显示在界面上。三、ViewAnimation并不会改变View的真实属性,当其由于动画效果不处于本身的位置时,其事实上的点击区域还是在原地。(所以在知道属性动画之前,我一直都是用两个不同时出现的按钮实现的一开始提到的那个滚动出EditText的效果,真的很蠢。。)

关于属性动画

​ 由于视图动画的纯“花架子”式动画效果,在更多需要和动画界面交互的场景中,属性动画后来的上位也就不难理解了。它的构成部分主要有这么几项:ValueAnimator、 TimeInterpolator和TypeEvaluator,下面先进行一下概述。

ValueAnimator是整个属性动画系统的核心基类,其中包含了动画的起止时间,属性变化规律和监听等功能,负责维护整个动画的运行流程,其下子类ObjectAnimator是最常用到的属性动画实现类,可以为任意对象加载动画效果,另外还有AnimatorSet,其类似XML文件内的标签,表示一组动画。

TimeInterpolatorTypeEvaluator事实上也不是什么新东西了,在ViewAnimation中也有相同的机制,它们的运行机制简单来说是这样的:当Animator设定了动画的起止时间并执行start()之后,之后动画的每一帧都会根据开始时间与当前时间的差与总时间的比值通过TimeInterpolator得到相应的0-1.0间的值,然后TypeEvaluator得到这个值并由此估算出此时对象应具有的属性,并更新其属性值,如此循环直至动画结束。所以这一对其实就是控制动画播放速度(视觉上的,总时间是一定的)的工具。

接下来就是各种例子喽,首先是ValueAnimator:

它的初始化方法有几种,如下:

ValueAnimator animator = ValueAnimator.ofInt(startValue,endValue);
ValueAnimator animator = ValueAnimator.ofFloat(startValue,endValue);
ValueAnimator animator = ValueAnimator.ofArgb(startValue,endValue);//必须为8位ARGB值,且必须为两个值
ValueAnimator animator = ValueAnimator.ofObject(evaluator,startValue,endValue);//这里的两个value都是Object类型的。
Keyframe kf0 = Keyframe.ofFloat(time1, value1);//关键帧,记录了在指定的time时其应有的value,可以定义自己的Interceptor
Keyframe kf1 = Keyframe.ofFloat(time2, value2);
Keyframe kf2 = Keyframe.ofFloat(time3, value3);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
//"rotation"在ValueAnimator中仅仅是一个属性的名字,只在在getAnimationValue(name)时可能会使用到,但在ObjectAnimator中就代表目标对象的属性
ValueAnimator rotationAnim = ValueAnimator.ofPropertyValuesHolder(pvhRotation)

//以上所有startValue 和 endValue都是可变参数,只填一个的话会默认从0开始,然后将填入的值为终点值,当然填多个的话也没有任何问题,甚至可以填入一串值实现来回变化属性的效果
//ofInt,ofFloat 和ofArgb只是决定了TimeInterpolator计算得到的参数类型,并不影响最终结果,影响结果的永远是Evaluator

然而ValueAnimator并没有预设的属性动画效果,官网上给的例子是通过addUpdateListener监听来自己更改View属性的(当然所有对象都一样,反正都是自定义),下面是一个比较完整的一个例子:

ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator() {
            @Override
            public Object evaluate(float fraction, Object startValue, Object endValue) {
                return fraction*(int)endValue; //这里的算法可以随便定义,甚至可以完全不管       TimeInterpolator传来的fraction数值,返回值就是getAnimatedValue能拿到的值。
            }
        },0, 100);
        animator.setDuration(1000);
        animator.setInterpolator(new CycleInterpolator(5));//随便设置了一个插值器,上面有说明
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //注意,动画的每一帧耗费的时间是不确定的,所以这里得到的值在线性插值器条件下也只是近似线性
                view.setTranslationX((float)animation.getAnimatedValue());
            }
        });
        animator.start();//即时开始动画,也可以用setStartDelay进行一下延时

为了简化ValueAnimator的使用(毕竟老是得写匿名类监听还是挺占地方的),ObjectAnimator便站了出来,它的使用如下:

ObjectAnimator animator = ObjectAnimator.ofFloat(view,"scaleX",0.1f);
animator.setDuration(1000);
animator.start();
//大部分方法继承自ValueAnimator,就不写了

需要注意的是,ObjectAnimator的工厂方法第一个参数与ValueAnimator 不同,它需要传入一个Object(可以认为是一个Java Bean)以及其相关属性,在之后的属性更新操作中,会通过反射调用Object的setter和getter方法来更改属性,要求相关Object类中必须有setter和getter方法(如属性字段size,需要在Object类中定义setSize和getSize方法,方法参数与返回值必须与传入数据类型相同),若无法控制Object的方法,则要做一个符合要求的包装类。对于View,除了上面的scaleX,还有几个常用的预定义属性:translationX和translationY,rotation(绕中心在水平面内顺时针旋转) rotationX(绕X轴在立体面内旋转)和rotationY(绕Y轴在立体面内旋转),pivotX和pivotY(设置缩放与平移的中心,默认为View中心),alpha以及backgroundColor等View属性,Android实际上对其也做了一层封装,即ViewPropertyAnimator类:

view.animate().scaleX(0.5f).scaleY(0.5f).duration(1000).start();//默认以中心缩小一倍
view.animate().translationY(200).duration(1000).start();//向下移动200px
view.animate().alpha(0f).duration(1000).start();//从当前透明度变至完全透明
//注意,这里的所有动画都是有绝对的默认中心的。而当运行过前面的动画后,默认中心仍不会变。如translationY(200)后回到原位置的方式并不是translationY(-200),而是translationY(0)

关于AnimatorSet主要是几个动画调度方法:

AnimatorSet set = new AnimatorSet();
set.playSequentially(anim1,anim2,anim3);//在start后顺序播放参数列表内的动画
set.playTogether(anim1,anim2,anim3);//在start后同时播放参数列表内的动画
//AnimatorSet.Builder调用
set.play(anim1).with(anim2);//start后同时播放动画1和动画2
set.play(anim2).before(anim3);//在播放动画2之后播放动画3
set.play(anim4).after(anim3);//在播放动画3之后播放动画4
set.play(anim4).after(1000);//在start后1秒播放动画4
set.start();
//注意,上面的链式调用相邻链之间是并列关系,如set.play(anim1).before(anim2).before(anim3)播放顺序为动画1先播放,动画2和动画3同时播放

其实如果仅是同时运行的话,用上面出现过的PropertyValuesHolder也能实现与AnimatorSet相似的效果:

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();//这里的工厂方法仍然是可变参数的方法,所以可以添加多个动画同时运行

与ViewAnimation相同,PropertyAnimation也能通过XML文件定义,文件存放在res/animator目录下以跟原来的animation区分,其对应上面三个类的标签分别为 ,其相关定义如下:

 //共同运行(默认)和顺序执行

     //值类型,关系到后面估值器的返回值

    

    
        ...
    

在定义了XML后,在Java代码中调用:

//调用ValueAnimator
ValueAnimator animator = (ValueAnimator)AnimatorInflater(context,R.animator.anim);
animator.addUpdateListener(mListener);//在监听里更新属性
animator.start();
//调用ObjectAnimator
ObjectAnimator animator = (ObjectAnimator)AnimatorInflater(context,R.animaror.anim);
animator.setTarget(view);//设置作用对象,此时该对象必须含有propertyName属性以及其setter getter方法
animator.start();
//调用AnimatorSet
AnimatorSet set = (AnimatorSet)AnimatorInflater(context,R.animtor.anim);
set.setTarget(view);//这里主要是set里的动画为ObjectAnimator的情况,事实上将ValueAnimator放在set里是没有意义的,因为set中没有相关的更新回调方法,没有办法更新ValueAnimator指定的属性
set.start();

至此属性动画的基本使用都说完了。不过上面写到的动画效果都是需要人为调用的,事实上Android还提供了一些在特定情景下自动调用的动画效果,这就是 以LayoutTransition表现的布局动画()。

添加布局动画的方式很简单,只要在XML文件中的某个ViewGroup标签下加上一行:

android:animateLayoutChanges="true"

则当布局发生变化(一般是add、remove、setVisibility等情况下)时,就会有默认的控件fade或者上滑等效果。在源码中可以看到,当这个值为true时,ViewGroup便设置了一个LayoutTransition:

case R.styleable.ViewGroup_animateLayoutChanges:
                    boolean animateLayoutChanges = a.getBoolean(attr, false);
                    if (animateLayoutChanges) {
                        setLayoutTransition(new LayoutTransition());
                    }
                    break;

同样地,我们也可以在代码中通过设置LayoutTransition的方式来设置布局动画,并且LayoutTransition的动画效果可以自定义。从官方文档中可以看到,LayoutTransition的动画分为四种情景:

  • APPEARING -View出现在布局中的动画标志。

  • CHANGE_APPEARING -由于新View出现在布局中而造成其他View改变的动画。

  • DISAPPEARING - View消失在布局中时的动画标志。

  • CHANGE_DISAPPEARING - 由于View消失在布局中而造成其他View改变的动画。

其设置方法如下:

transition.setAnimator(LayoutTransition.APPEARING,appearingAnim);
transition.setAnimator(LayoutTransition.DISAPPEARING,disappearingAnim);
transition.setAnimator(LayoutTransition.CHANGE_APPEARING,changingAppearingAnim);
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,changingDisappearingAnim);
//上面的参数都是普通的ObjectAnimator或者ValueAnimator。

关于帧动画

相对上面两种动画来说,帧动画真的不能再简单了。其动画实现原理不同于上面的View局部更新重绘,而是简单粗暴地一帧帧切换已有的图片,它的XML文件直接放在res/drawable文件夹下,结构如:


 //只播放一次或者循环播放
     //该帧播放时长
    ...

在Java中使用时也就几行代码:

ImageView iv = (ImageView) findViewById(R.id.iv);
iv.setBackgroundResource(R.drawable.anim);

animation = (AnimationDrawable) iv.getBackground();
rocketAnimation.start();

就这么简单,但是需要注意的是,不能在onCreate中调用start()方法,官方解释是此时AnimationDrawable对象还未加载到窗口上,故推荐有这个需求是通过在onWindowFocusChanged()中开始动画,此时表示Android已经将焦点聚集在窗口上,即动画已经加载完毕。

最后

各种翻文档后终于是搞定了这篇其实并没有多高深的笔记,里面有些东西被省略了,比如动画状态的监听,不过日常大概也不太会被这种看字面就能理解的东西难住。还有挺重要的一点是layoutAnimation和Activity Fragment等整体组件切换的动画,某些是5.0以后的效果,以后大概会补上。总之以后写些小动画但想不起来属性时终于能告别百度和忍受那些糟糕的界面了,开心。

如果上面的内容有任何错误或不足,欢迎各种交流指导,鞠躬。
个人Github主页:Lazxy

你可能感兴趣的:(关于Android的几种基本动画)