Android动画学习笔记大集合

  其实动画这个东西我已经了解过很长一段时间了,但是一直没系统的整理过。关于android中的各种动画虽然都会用,但总怕自己会慢慢遗忘。这回看了几篇动画分析的文章,自己也学到了一些东西,在此就梳理一下。

参考博文如下,感谢大神们的分享:

http://www.open-open.com/lib/view/open1329994048671.html

http://www.tuicool.com/articles/yeM3my

http://blog.csdn.net/singwhatiwanna/article/details/17841165

http://blog.csdn.net/lmj623565791/article/details/38067475

http://blog.csdn.net/lmj623565791/article/details/38092093

 

注意:所有view的动画就是被限制在它的父控件中的,即使你做了view的移动,它也不可能显示在父控件的外边。也就是说父控件是一个舞台,演员可以在舞台上到处走动,但如果超过了舞台,那么观众就看不到了。

 

一、View Animation(Tween Animation)

View Animation(Tween Animation):也可称为补间动画(Tween Animation),给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。

View animation只能应用于View对象,而且只支持一部分属性,如支持缩放旋转而不支持背景颜色的改变。

对于View animation,它只是改变了View对象绘制的位置,注意这是“绘制”,而不是实际的位置。比如你让一个button变成它的两倍大小,但是它能接受你点击的区域还是原来的button区域,没有做任何改变。

View Animation支持设定多种动画样式,也可以设定这些动画的执行顺序,支持通过xml和代码两种方式来设置动画效果。

动画举例

【 By XML 】

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true"
    android:startOffset="50">  

    <alpha
        android:duration="200"
        android:fromAlpha="1.0"
        android:toAlpha="0.0" />
</set>
     Animation aimation = AnimationUtils.loadAnimation(this, R.anim.anim);
        final ImageView imageView = (ImageView) findViewById(R.id.imageView_id);
        imageView.startAnimation(aimation);

可以对动画添加监听器

    aimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                animation = null;

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

如果你是用AnimationSet设置动画的话,animationSet也是继承自Animation,所以也有setAnimationListener的方法

详细代码可以参考:http://www.cnblogs.com/tianzhijiexian/p/3983616.html

 

【 By JAVA 】

通过java代码来做的,可以参考这篇文章。其实也就是各种类继承Animation,然后有自己独特的方法,也可以通过AnimationSet进行各种设置。

http://www.cnblogs.com/tianzhijiexian/p/3981241.html

 

二、Drawable Animation(Frame Animation)

Drawable Animation(Frame Animation):帧动画,就像GIF图片,通过一系列Drawable依次显示来模拟动画的效果。在XML中的定义方式如下:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />     <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />     <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

必须以<animation-list>为根元素,以<item>表示要轮换显示的图片,duration属性表示各项显示的时间。XML文件要放在/res/drawable/目录下。

protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
setContentView(R.layout.main); imageView
= (ImageView) findViewById(R.id.imageView1); imageView.setBackgroundResource(R.drawable.drawable_anim); AnimationDrawable anim = (AnimationDrawable) imageView.getBackground(); } public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { anim.stop(); anim.start(); return true; } return super.onTouchEvent(event); }

 我在实验中遇到两点问题:

  • 要在代码中调用Imageview的setBackgroundResource方法,如果直接在XML布局文件中设置其src属性当触发动画时会FC。
  • 在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次。
  • 最后一点是SDK中提到的,不要在onCreate中调用start,因为AnimationDrawable还没有完全跟Window相关联,如果想要界面显示时就开始动画的话,可以在onWindowFoucsChanged()中调用start()。

 

三、Property Animation

 属性动画,这个是在Android 3.0中才引进的,它更改的是对象的实际属性,如Button的缩放,Button的位置与大小属性值都改变了。而且Property Animation不止可以应用于View,还可以应用于任何对象(Object)。Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。

在Property Animation中,可以对动画应用以下属性:

  • Duration:动画的持续时间,单位ms
  • TimeInterpolation:属性值的计算方式,如先快后慢,是一个接口。用来设置插值器
  • TypeEvaluator:根据属性的开始、结束值与TimeInterpolation计算出的因子计算出“当前”时间的属性值,这个值可以在AnimationUpdate中得到
  • Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放
  • Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
  • Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响

 

 

四、ValueAnimator

ValueAnimator包含Property Animation动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。它其实就是一个计算器,并不能实际执行动画效果。它可以计算出动画要执行的时间,每隔几毫秒刷新一次等等,但具体如何执行动画,它是不管的。你需要在ValueAnimator的ValueAnimator.onUpdateListener监听器中进行设置。

这里给ValueAnimator对象设置了一个值,因为是一个值,所以会默认为是最终的结果值,动画默认从当前的值到你设定的这个值。如果你设定两个值,那么它就意味着是从第一个值到另一个值进行动画。

     // set one value,it is final value.
        // If you set two values in it.It means animation will start from first value to Second value.
        ValueAnimator animator = ValueAnimator.ofFloat(1f);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                
            }
        });
    animator.start();

这个AnimationUpdateListener将会在动画执行过程中触发。如果你没在这里做任何处理,那么即使是执行了(start())也不会有任何动画的。

实际运用——自由落体 & 抛物线

 

如果我希望小球抛物线运动【实现抛物线的效果,水平方向100px/s,垂直方向加速度200px/s*s 】,分析一下,貌似只和时间有关系,但是根据时间的变化,横向和纵向的移动速率是不同的,我们该咋实现呢?此时就要重写TypeValue的时候了,因为我们在时间变化的同时,需要返回给对象两个值,x当前位置,y当前位置。

/**
     * 抛物线
     * @param view
     */
    public void paowuxian(View view)
    {

        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setDuration(3000);
        valueAnimator.setObjectValues(new PointF(0, 0));
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setEvaluator(new TypeEvaluator<PointF>()
        {
            // fraction = t / duration
            @Override
            public PointF evaluate(float fraction, PointF startValue,
                    PointF endValue)
            {
                Log.e(TAG, fraction * 3 + "");
                // x方向200px/s ,则y方向0.5 * 10 * t
                PointF point = new PointF();
                point.x = 200 * fraction * 3;
                point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3);
                return point;
            }
        });

        valueAnimator.start();
        valueAnimator.addUpdateListener(new AnimatorUpdateListener()
        {
            @Override
            public void onAnimationUpdate(ValueAnimator animation)
            {
                PointF point = (PointF) animation.getAnimatedValue();
                mBlueBall.setX(point.x);
                mBlueBall.setY(point.y);

            }
        });
    }

可以看到,因为ofInt,ofFloat等无法使用,我们自定义了一个TypeValue,每次根据当前时间返回一个PointF对象,(PointF和Point的区别就是x,y的单位一个是float,一个是int;RectF,Rect也是)PointF中包含了x、y的当前位置,然后我们在监听器中获取,动态设置属性。

 

五、ObjectAnimator

实际应用中一般都会用ObjectAnimator来产生某一对象的动画,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,应该满足以下条件:

  • 对象应该有一个setter函数:set<PropertyName>(驼峰命名法)
  • 如上面的例子中,像ofFloat之类的工场方法,第一个参数为对象名,第二个为属性名,后面的参数为可变参数,如果values…参数只设置了一个值的话,那么会假定为目的值,属性值的变化范围为当前值到目的值,为了获得当前值,该对象要有相应属性的getter方法:get<PropertyName>
  • 如果有getter方法,其应返回值类型应与相应的setter方法的参数类型一致。

  如果上述条件不满足,则不能用ObjectAnimator,应用ValueAnimator代替。

下面是将imageview进行透明度渐变的例子。

    ObjectAnimator oa=ObjectAnimator.ofFloat(imageview, "alpha", 0f, 1f);
    oa.setDuration(3000);
    oa.start();

根据应用动画的对象或属性的不同,可能需要在onAnimationUpdate函数中调用invalidate()函数刷新视图。

 

下面分析下属性动画的原理:

属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。总结一下,你对object的属性xxx做动画,如果想让动画生效,要同时满足两个条件:

  • object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满足,程序直接Crash)
  • object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)

以上条件缺一不可

 那么如果我们的object没办法满足这个条件呢?比如button的setWidth方法仅仅是设置它的最小宽度(minWidth),对于实际宽度不会产生任何影响。如果让它实际改变就需要用

btn.getLayoutParams().width = xxx;

来设置,但这种通过方法来获得public值的方法又不适合ObjecAnimator的应用场景,改怎么办呢?

针对上述问题,Google告诉我们有3中解决方法:

1. 给你的对象加上get和set方法,如果你有权限的话

对于View我们没权限给其添加各种属性,所以这种方法仅仅适合于自己定义的Object对象

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

这个方法很好,你可以写一个类继承那个Object对象,在子类中添加各种set、get方法,更好的办法是写一个包装类,传入一个object对象,然后给其添加各种方法。

    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();
        }
    }

上面类中ViewWrapper这个内部类就是一个包装类,它的构造函数传入一个View对象,然后提供了set和get方法,这样我们就可以对它进行动画的操作了

3. 采用ValueAnimator,监听动画过程,自己实现属性的改变

 这个方法就有些别扭了,但扩展性是最强的。ValueAnimator我们在上面已经介绍过了,下面是在动画执行过程中能的得到的东西。

ValueAnimator animator = ValueAnimator.ofFloat(1, 1);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer currentValue = (Integer)animation.getAnimatedValue(); // current Value(0~100)

                float fraction = valueAnimator.getAnimatedFraction(); // progress of animation
            }
        });

getAnimatedFraction() 这个可以得到当前动画的进度,得到的是浮点型数据很有用。api12中加入的,感觉比getAnimationValue有用

getAnimatedValue(String propertyName) 得到某个特定属性当前的值,这个可用性就很高了。如果有特殊要求可以用它

其余的属性都在下面了,看名字就知道是什么意思啦~

 

实际运用

这个例子是将一个view进行移动和拉伸的动画。涉及了多个属性,当你要让view选装或者是移动的时候,请务必设定PivotX,PivotY来指定坐标,如果不设定的话,移动和选择默认的中心点都是Object的中心

public void startViewSimpleAnim(final View fromView,Rect finalBounds,int 
            startOffsetY,int finalOffsetY, float startAlpha, float finalAlpha) {
        Rect startBounds  = new Rect();
        startBounds.set(Position.getGlobalVisibleRect(fromView));
        //设置偏移量
        startBounds.offset(0, -startOffsetY);
        finalBounds.offset(0, -finalOffsetY);
        //设定拉伸或者旋转动画的中心位置,这里是相对于自身左上角
        fromView.setPivotX(0f);
        fromView.setPivotY(0f);
        //计算拉伸比例
        float scaleX = (float)finalBounds.width() / startBounds.width();
        float scaleY = (float)finalBounds.height() / startBounds.height();
        
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(fromView, "alpha", startAlpha, finalAlpha);
        ObjectAnimator xAnim = ObjectAnimator.ofFloat(fromView, "x", startBounds.left, finalBounds.left);
        ObjectAnimator yAnim = ObjectAnimator.ofFloat(fromView, "y", startBounds.top, finalBounds.top);
        ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(fromView, View.SCALE_X, 1f, scaleX);
        ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(fromView, View.SCALE_Y,1f, scaleY);
        
        set.play(alphaAnim).with(xAnim).with(yAnim).with(scaleXAnim).with(scaleYAnim);
        
        set.setStartDelay(mStartDelay);
        set.setDuration(mAnimTime);
        set.setInterpolator(mInterpolator);
        set.addListener(new AnimListener(fromView,null));

        set.start();
    }

如果你操作对象的该属性方法里面,比如上例的setRotationX如果内部没有调用view的重绘,则你需要自己按照下面方式手动调用。

anim.addUpdateListener(new AnimatorUpdateListener()
        {
            @Override
            public void onAnimationUpdate(ValueAnimator animation)
            {
//                view.postInvalidate();
//                view.invalidate();
            }
        });

 

用xml文件来创建属性动画

大家肯定都清楚,View Animator 、Drawable Animator都可以在anim文件夹下创建动画,然后在程序中使用,甚至在Theme中设置为属性值。当然了,属性动画其实也可以在文件中声明。

 

首先在res下建立animator文件夹,然后建立res/animator/scalex.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="scaleX"
    android:valueFrom="1.0"
    android:valueTo="2.0"
    android:valueType="floatType" >
</objectAnimator>

然后通过动画工具类就可以加载xml中的动画文件了

 AnimatorInflater.loadAnimator(this, R.anim.anim);

public void scaleX(View view)
    {
        // 加载动画
        Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scalex);
        anim.setTarget(mMv);
        anim.start();
    }

 

纵向与横向同时缩放

<?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(表示一个接一个执行)】

 

六、通过AnimatorSet来控制多个动画

AnimatorSet和AnimationSet类似,可以操作多个动画属性。AnimationSet提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放,顺序播放等。

例子01:

AnimatorSet bouncer = new AnimatorSet();
bouncer.play(anim1).before(anim2);
bouncer.play(anim2).with(anim3);
bouncer.play(anim2).with(anim4)
bouncer.play(anim5).after(amin2);
animatorSet.start();
  1. 播放anim1;
  2. 同时播放anim2,anim3,anim4;
  3. 播放anim5。

例子02:

animSet.playTogether(anim1, anim2);  
animSet.start();  

使用playTogether两个动画同时执行,当然还有playSequentially依次执行

例子03:

     /** 
         * anim1,anim2,anim3同时执行 
         * anim4接着执行 
         */  
        AnimatorSet animSet = new AnimatorSet();  
        animSet.play(anim1).with(anim2);  
        animSet.play(anim2).with(anim3);  
        animSet.play(anim4).after(anim3);  
        animSet.setDuration(1000);  
        animSet.start(); 

如果我们有一堆动画,如何使用代码控制顺序,比如1,2同时;3在2后面;4在1之前

注意animSet.play().with();也是支持链式编程的,但是不要想太多。

比如:animSet.play(anim1).with(anim2).before(anim3).before(anim5);

这样是不行的,系统不会根据你写的这一长串来决定先后的顺序,所以麻烦你按照上面例子的写法,多写几行

 

七、ViewPropertyAnimator

 如果需要对一个View的多个属性进行动画可以用ViewPropertyAnimator类,该类对多属性动画进行了优化,会合并一些invalidate()来减少刷新视图,该类在3.1中引入。

例子01:

以下两段代码实现同样的效果

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
myView.animate().x(50f).y(100f);

例子02:

在SDK12的时候,给View添加了animate方法,更加方便的实现动画效果。

 btn.animate().x(200).y(200).alpha(0f); 就可以实现动画效果了~

例子03:

简单的使用mBlueBall.animate().alpha(0).y(mScreenHeight / 2).setDuration(1000).start()就能实现动画~~不过需要SDK11,此后在SDK12,SDK16又分别添加了withStartAction和withEndAction用于在动画前,和动画后执行一些操作。当然也可以.setListener(listener)等操作。


package com.example.zhy_property_animation;

import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

public class ViewAnimateActivity extends Activity
{
    protected static final String TAG = "ViewAnimateActivity";

    private ImageView mBlueBall;
    private float mScreenHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.view_animator);

        DisplayMetrics outMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
        mScreenHeight = outMetrics.heightPixels;
        mBlueBall = (ImageView) findViewById(R.id.id_ball);

    }

    public void viewAnim(View view)
    {
        // need API12
        mBlueBall.animate()//
                .alpha(0)//
                .y(mScreenHeight / 2).setDuration(1000)
                // need API 12
                .withStartAction(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        Log.e(TAG, "START");
                    }
                    // need API 16
                }).withEndAction(new Runnable()
                {

                    @Override
                    public void run()
                    {
                        Log.e(TAG, "END");
                        runOnUiThread(new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                mBlueBall.setY(0);
                                mBlueBall.setAlpha(1.0f);
                            }
                        });
                    }
                }).start();
    }                                                                                                                                                  }

 

八、TypeEvalutors<T>

根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值,Android提供了以下几个evalutor:

  • IntEvaluator:属性的值类型为int;
  • FloatEvaluator:属性的值类型为float;
  • ArgbEvaluator:属性的值类型为十六进制颜色值;
  • TypeEvaluator:一个接口,可以通过实现该接口自定义Evaluator。

  自定义TypeEvalutor很简单,只需要实现一个方法,如FloatEvalutor的定义:

public class FloatEvaluator implements TypeEvaluator {
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
    }
}

根据动画执行的时间跟应用的Interplator,会计算出一个0~1之间的因子,即evalute函数中的fraction参数,通过上述FloatEvaluator应该很好看出其意思。

 

九、当Layout改变时应用动画

ViewGroup中的子元素可以通过setVisibility使其Visible、Invisible或Gone,当有子元素可见性改变时,可以向其应用动画。

通过LayoutTransition类的常量(第一个参数)可以区分动画的类型,第二个参数为Animator。

  • APPEARING        当一个元素变为Visible时对其应用的动画
  • CHANGE_APPEARING   当一个元素变为Visible时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画
  • DISAPPEARING      当一个元素变为InVisible时对其应用的动画
  • CHANGE_DISAPPEARING 当一个元素变为Gone时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用的动画 disappearing from the container.
mTransition.setAnimator(LayoutTransition.DISAPPEARING, customDisappearingAnim);

更多的解释

  • 过渡的类型一共有四种:
  • 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。

详细的例子,请参考:http://blog.csdn.net/lmj623565791/article/details/38092093

 

顺便说下:Android 3.0已经原生支持了Animating Layout,当Layout变化的时候,系统会根据Layout变化前后,自动显示动画。

只要给layout添加:android:animateLayoutChanges="true"即可,而且动画还可以通过setLayoutTransition()自己定义。

 

参考博文如下:

http://www.open-open.com/lib/view/open1329994048671.html

http://www.tuicool.com/articles/yeM3my

http://blog.csdn.net/singwhatiwanna/article/details/17841165

http://blog.csdn.net/lmj623565791/article/details/38067475

http://blog.csdn.net/lmj623565791/article/details/38092093

posted @ 2014-12-10 21:05  developer_Kale  阅读(2528)  评论(0编辑  收藏  举报
网站流量统计工具