Android动画

Drawable Animation

帧动画,多张图片循环播放,实现动画效果。

以Xml定义一组帧动画

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item
        android:drawable="@drawable/a1"
        android:duration="200" />
    <item
        android:drawable="@drawable/a2"
        android:duration="200" />
    <item
        android:drawable="@drawable/a3"
        android:duration="200" />
    <item
        android:drawable="@drawable/a4"
        android:duration="200" />
    <item
        android:drawable="@drawable/a5"
        android:duration="200" />
</animation-list>

oneshot表示动画是否只执行一次。

每一帧的动画可以设置图层,让同一帧能有几个图。

    <item android:duration="100">  
        <layer-list>  
            <item android:drawable="@drawable/login_loading_1" />  
            <item android:drawable="@drawable/login_loading_2" />  
        </layer-list>  
    </item>  

帧动画图片多,可能会有内存溢出的问题、

View Animation

视图动画,又叫补间动画,有旋转,透明度,大小,位移四种动画效果,特点是实现简单,但是动画并不改变实际view的属性

用代码实现动画

TranslateAnimation 平移动画

 TranslateAnimation ta = new TranslateAnimation(0,400,0,0);//左平移400个单位
 ta.setDuration(2000);
 view.startAnimation(ta);

AlphaAnimation 透明度动画

  AlphaAnimation aa = new AlphaAnimation(1.0f,0.5f);//从不透明到半透明
  aa.setDuration(2000);
  view.startAnimation(aa)

RotateAnimation 旋转动画

  RotateAnimation ra = new RotateAnimation(0,180);
  ra.setDuration(2000);
  view.startAnimation(ra);

ScaleAnimation 缩放动画

 ScaleAnimation sa = new ScaleAnimation(0,5,0,5);
 sa.setDuration(2000);
 view.startAnimation(ra);

其中除AlphaAnimation之外其余三种动画的参考坐标系有三种。

  • 默认是view的左上角。
  • 以及view自身:Animation.RELATIVE_TO_SELF
  • 还有view的父控件:Animation.RELATIVE_TO_PARENT

这里说的参考系,可以理解成一个关键点的确定

这是一个300*300px的ViewGroup,背景图是个圆,小红点位于ViewGroup的左上角
以TranslateAnimation来看这三种参考坐标系

TranslateAnimation ta1 = new TranslateAnimation(0,150,0,0);
//TranslateAnimation ta2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,10f,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0);
//TranslateAnimation ta3 = new TranslateAnimation(Animation.RELATIVE_TO_PARENT,0,Animation.RELATIVE_TO_PARENT,1f,Animation.RELATIVE_TO_PARENT,0,Animation.RELATIVE_TO_PARENT,0);
ta1.setDuration(2000);
image.startAnimation(ta1);


三种设置分别对应三个gif图,第一个是以左上角为参考点,右平移150个单位(px)

第二个是以自身为参考,右平移自身10倍的距离。

第三个是以父控件为参考,右平移父控件一倍宽的距离。

ScaleAnimation有个比较有意思的地方。如果参考点不在原先的view上,缩放之后的view会带移动的效果。

 ScaleAnimation sa1 = new ScaleAnimation(1,5,1,5,-10,0);
 ScaleAnimation sa = new ScaleAnimation(1,5,1,5,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);

比如说sa1这个ScaleAnimation,参考点在他的左侧-10px,0px的地方
他放大五倍的效果是这样的

由于参考点在圆的左边,所以圆是向右边移动的。实际上这个放大效果是把圆和他的参考点看成了一个整体,参考点固定不动,放大五倍的过程中,参考点和圆的距离10px也放大了五倍。

让几个动画一起作用可以使用AnimationSet

AnimationSet as = new AnimationSet(true);//boolean 是否共享插值器
ScaleAnimation sa1 = new ScaleAnimation(1,5,1,5,-10,0);
ScaleAnimation sa = new ScaleAnimation(1,5,1,5,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
RotateAnimation ra = new RotateAnimation(0, 360f, Animation.RELATIVE_TO_PARENT, 0.5f, Animation.RELATIVE_TO_PARENT, 0.5f);
as.setInterpolator(new AccelerateDecelerateInterpolator());
as.addAnimation(sa1);
as.addAnimation(ra);
as.setDuration(2000);
image.startAnimation(as);

相比在代码里设置动画,一般还是用Xml来配置
在res目录下新建文件夹anim用来存放动画文件

如下代码

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <scale
        android:duration="1000"
        android:fillAfter="true"
        android:fromXScale="1"
        android:fromYScale="1"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="5"
        android:toYScale="5" />

    <set android:startOffset="1000">
        <translate
            android:duration="2000"
            android:fromXDelta="0"
            android:fromYDelta="0"
            android:toXDelta="200"
            android:toYDelta="0"
            />
        <alpha
            android:duration="2000"
            android:fromAlpha="1"
            android:toAlpha="0"
            />
    </set>
</set>

定义了一个一秒内放大五倍,接着一边平移一边消失的动画
值得注意的是pivotX和pivotY参考点的确定,以view的左上角为0,0点,用绝对数值确定,比如pivotX="-10",pivotY="0",表示左上角往左10px的点,pivotX="50%"表示相对于自身的百分之50,pivotX="50%p"表示相对于父控件的百分之50.对应代码设置的三种坐标确定

加载一个xml的Animation

Animation animation =  AnimationUtils.loadAnimation(MainActivity.this,R.anim.animation);
img.startAnimation(animation);

Property Animation

属性动画,通过改变View的属性,来达到动画的效果。
因为改变的是属性值,所以动画会改变View的位置,大小等。
一般要求改变的属性值有 get/set 方法

属性动画有两个动画执行类分别是ObjectAnimator和ValueAnimator

ObjectAnimator是ValueAnimator的子类,具有比ValueAnimator更强的封装性,使用代码更简洁,相反的,封装性不强的ValueAnimator则显得更加灵活,容易实现复杂的效果

ObjectAnimator

修改单个属性的动画效果

 ObjectAnimator.ofFloat(img,"rotation",0.0F,180F).setDuration(500).start();

直接调用ObjectAnimator的ofFloat,ofInt,ofObject方法,第一个参数是作用的view,第二个是操作的属性,第三个是一个不定参,只填一个表示结束的属性值,填两个分别表示开头和结尾的属性值。

多个属性值改变的动画效果,(同时改变)可以用PropertyValuesHolder

    PropertyValuesHolder pvha = PropertyValuesHolder.ofFloat("alpha", 1f, 0, 1f);
    PropertyValuesHolder pvhsx = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
    PropertyValuesHolder pvhsy = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
    ObjectAnimator.ofPropertyValuesHolder(view, pvha, pvhsx, pvhsy).setDuration(2000).start();

ValueAnimator

实际上,ValueAnimator可以看成是提供一个区间的计算过程。
而实际的动画效果,需要我们在回调中自己去设置。
例如:

            ValueAnimator valueAnimator = ValueAnimator.ofFloat(200.0F);
            valueAnimator.setTarget(img);
            valueAnimator.setDuration(2000).start();
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    img.setTranslationY((Float) valueAnimator.getAnimatedValue());
                }
            });

因为ofFloat只传了一个参,所以200是结束值,区间是0-200;
2000ms时间内在该区间内不断的递增,AnimatorUpdateListener里通过valueAnimator.getAnimatedValue()就能拿到某时刻的值,再根据需要进行设置,这里是平移200个px

TypeEvaluator

上面说道ValueAnimator可看成是提供一个区间的计算过程。实际上,这个计算的操作,是由TypeEvaluator来进行的。

TypeEvaluator是一个接口,我们可以实现这个接口来实现自己的需求,也可以用Android提供的几个Evaluator

比如:

            ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointFEvaluator(),new PointF(0,0),new PointF(100,100));
            valueAnimator.setDuration(2000).start();
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    PointF pf = (PointF) valueAnimator.getAnimatedValue();
                    Log.e("walker","x值 : " + pf.x);
                    Log.e("walker","y值 : " + pf.y);
                }
            });

PointFEvaluator 是 自带的一个Evaluator,上面代码是(0,0)点到
(100,100)的计算过程,过程中不断回调AnimatorUpdateListener
因为我们没有为任何view设置动画,所以不会有什么动画效果,但是Log里可以看到2s间点的移动状态。毫无疑问,这是一条直线的运动轨迹

自定义TypeEvaluator模拟抛物线效果

            ValueAnimator valueAnimator = ValueAnimator.ofObject(new TypeEvaluator<PointF>() {

                @Override
                public PointF evaluate(float v, PointF o, PointF t1) {
                    
                    PointF pf = new PointF();
                    pf.x = 200 * v * 2;
                    pf.y = 0.5f * 200 * (v * 2) * (v * 2);
                    return pf;
                }
            },new PointF(0,0));

            valueAnimator.setDuration(2000).start();
            valueAnimator.setInterpolator(new LinearInterpolator());

            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {

                    PointF point = (PointF) valueAnimator.getAnimatedValue();
                    ball.setX(point.x);
                    ball.setY(point.y);
                }
            })

关于动画状态的监听
一个有四个状态,分别为开始,结束,取消,重复

            valueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {
                    
                }

                @Override
                public void onAnimationEnd(Animator animator) {

                }

                @Override
                public void onAnimationCancel(Animator animator) {

                }

                @Override
                public void onAnimationRepeat(Animator animator) {

                }
            })

也可以用AnimatorListenerAdapter选择自己需要的监听
比方说只需要动画结束的监听

            valueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {

                }
            })

AnimatorSet设置多个动画的执行

            ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(img,"rotation",0.0F,360F);

            ValueAnimator valueAnimator = ValueAnimator.ofFloat(400.0F);
            valueAnimator.setTarget(img);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    img.setTranslationY((Float) valueAnimator.getAnimatedValue());
                }
            });
            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.setDuration(2000);
            animatorSet.playTogether(objectAnimator,valueAnimator);//一起执行
//          animatorSet.playSequentially(objectAnimator,valueAnimator);//顺序执行
//          animatorSet.play(objectAnimator).with(valueAnimator);//链式排列
            animatorSet.start();

推荐阅读更多精彩内容