基础动画-(2)-属性动画

一.基本介绍以及相关api

(1).背景

是在Android3.0(API11)开始出现的,如果要在API11之前使用,要用动画库nineoldandroids来兼容以前的版本(在API11以前的版本内部是通过代理View动画来实现的,因此在Android低版本上,本质还是View动画,虽然看起来是属性动画)

(2).相关API

ObjectAnimator    动画的执行类,后面详细介绍

ValueAnimator    动画的执行类,后面详细介绍 

AnimatorSet      用于控制一组动画的执行:线性,一起,每个动画的先后执行等。

AnimatorInflater  用户加载属性动画的xml文件

TypeEvaluator     类型估值算法(作用:根据当前属性改变的百分比来计算改变后的属性值,系统预置的有IntEvaluator(针对整型),floatEvaluator(针对浮点),argbEvaluator(针对color属性),属性动画中插值器(Interpolator)和估值器(TypeEvalator)很重要)注意:自定义插值器需要实现Interpolator或者TimeInterpolator,自定义的算法素要实现TypeEvaluator(如果要对其他类型,非int,float,color做动画,那么必须要自定义类型的估值算法)

TimeInterpolator   时间插值(作用:根据时间流逝的百分比来计算出当前属性值改变的百分比)

Interceptor        插值器


android:propertyName —— 表示属性动画的作用对象的属性名称(只有ObJectAnimator有,ValueAnimator没有,注意这个属性是这个object有set和get方法的,不是随便乱来的)

android:duration—— 表示动画的时长
android:valueTo—— 表示属性的结束值
android:startOffset—— 表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正的播放动画
android:repeatCount—— 表示动画的重复次数(默认0-1代表无限循环)
android:repeatMode—— 表示动画的重复模式(repeat,reverse
android:valueType—— 表示android:propertyName所指定的属性类型,有intTypefloatType两个可选项,分别表示属性的类型为整形和浮点型,另外如果android:propertyName 所指定的属性去表示的是颜色,那么不需要指定,android:valueType,系统会自动对颜色类型的属性做处理

android:valueFrom—— 表示属性的起始值

Animator sets: 动画集合,你可以定义一组动画,一起执行或者顺序执行。(里面有个属性Android:ordering默认值是"together",表示同时播放,也可以自己改成"sequentially",表示按照前后顺序依次播放)

Frame refresh delay:帧刷新延迟,对于你的动画,多久刷新一次帧;默认为10ms,但最终依赖系统的当前状态;基本不用管

<set>  AnimatorSet (包含android:ordering(together,sequentially))

<animator>  valueAnimator   (相比objectAnimator只是少了一个Android:propertyName,其他都一样的)

<objectAnimator>    objectAnimator


总结:

属性动画就是,动画的执行类来设置动画操作的对象的属性、持续时间,开始和结束的属性值,时间差值等,然后系统会根据设置的参数动态的变化对象的属性。

二.基本的属性动画

MainActivity.java(复杂版)

public class MainActivity extends Activity {
    private ImageView iv;
    private ObjectAnimator alaph;
    private ObjectAnimator tran;
    private ObjectAnimator rotate;
    private ObjectAnimator scale;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv = (ImageView) findViewById(R.id.iv);
    }
    /**
     * 透明度
     * @param view
     */
    public void alpha(View view){
//		iv.setAlpha(alpha);
//		iv.getAlpha()
        alaph = ObjectAnimator.ofFloat(iv, "alpha", 0.0f,0.2f,0.4f,0.6f,0.8f,1.0f);
        alaph.setDuration(4000);
        alaph.setRepeatMode(ObjectAnimator.REVERSE);
        alaph.setRepeatCount(ObjectAnimator.INFINITE);
        alaph.start();
    }
    /**
     * 旋转动画
     * @param view
     */
    public void rotate(View view){
        //iv.setRotationY(rotationY)
        //rotaitionY 就是以y为轴转,rotationX就是以X为轴转
         rotate = ObjectAnimator.ofFloat(iv, "rotationX", 0.0f,30f,60.0f,90f);
        rotate.setDuration(2000);
        rotate.setRepeatMode(ObjectAnimator.REVERSE);
        rotate.setRepeatCount(ObjectAnimator.INFINITE);
        rotate.start();
    }
    /**
     * 缩放
     * @param view
     */
    public void scale(View view){
        //iv.setScaleX()
        scale = ObjectAnimator.ofFloat(iv, "scaleY", 0.0f,0.2f,0.5f,2.0f);
        scale.setDuration(2000);
        scale.setRepeatMode(ObjectAnimator.REVERSE);
        scale.setRepeatCount(ObjectAnimator.INFINITE);
        scale.start();
    }

    /**
     * 位移
     * @param view
     */
    public void trans(View view){
        //iv.setScaleX()
        //iv.setTranslationX();
        /**
         * public void end ()
         Added in API level 11
         Ends the animation.
         This causes the animation to assign the end value of the property being animated,
         then calling the onAnimationEnd(Animator) method on its listeners.
         This method must be called on the thread that is running the animation.
         *就是直接到最后
         * */
        //oa.end();
        /**
         * public void cancel ()
         Added in API level 11
         Cancels the animation. Unlike end(), cancel() causes the animation to stop in its tracks, s
         ending an onAnimationCancel(Animator) to its listeners, followed by an onAnimationEnd(Animator) message.
         This method must be called on the thread that is running the animation.
         * 播放哪停到那,就停在那个地方
         * */
        tran = ObjectAnimator.ofFloat(iv, "translationX", 0.0f, 30f, 60f, 200f);
        tran.setDuration(2000);
        tran.setRepeatMode(ObjectAnimator.REVERSE);
        tran.setRepeatCount(ObjectAnimator.INFINITE);
        tran.start();
    }
    public void stop(View view){
        cancelObjectAnimator();
    }

    public void set(View view){
        System.out.println("---");
        //动画的集合
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "rotation", 0.0f,30f,60.0f,90f);
        oa.setDuration(4000);
        ObjectAnimator oa2 = ObjectAnimator.ofFloat(iv, "translationX", 0.0f,10f,20f,310f);
        oa2.setDuration(2000);
        //分步发生
        set.playSequentially(oa,oa2); //先旋转再起飞
        //一起发生
        //set.playTogether(oa,oa2);			//边转边飞
        set.start();
    }

    public void cancelObjectAnimator(){
//        if(oa !=null){
//            oa.cancel();
//            oa = null;
//        }
    }

    public void endObjectAnimator(){
//        if(oa !=null){
//            oa.end();
//            oa = null;
//        }
    }
}

简单版(用布局文件写)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:ordering = "together">
    <objectAnimator
        android:propertyName = "x"
        android:duration = "300"
        android:valueTo = "200"
        android:valueType = "intType"
        />
    <objectAnimator
        android:propertyName = "y"
        android:duration = "300"
        android:valueTo = "300"
        android:valueType = "intType"
        />
</set>

但是实际开发中建议用代码实现属性动画

三.动画监听

(1)AnimatorListener

Public static interface AnimatorListener{

Void onAnimationStart(Animator animator);

Void onAnimationEnd(Animator animator);

Void onAnimationCancel(Animator animator);

Void onAnimationRepeat(Animator animator);

}

anim.addListener(new AnimatorListener()  
        {  
  
            @Override  
            public void onAnimationStart(Animator animation)  
           {  
               Log.e(TAG, "onAnimationStart");  
           }  
 
           @Override  
           public void onAnimationRepeat(Animator animation)  
           {  
               // TODO Auto-generated method stub  
               Log.e(TAG, "onAnimationRepeat");  
           }  
 
           @Override  
           public void onAnimationEnd(Animator animation)  
           {  
               Log.e(TAG, "onAnimationEnd");  
           }  
 
           @Override  
           public void onAnimationCancel(Animator animation)  
           {  
               // TODO Auto-generated method stub  
               Log.e(TAG, "onAnimationCancel");  
           }  
       });  
       anim.start();  
   }  


它可以监听动画的开始,结束(可以用end,cancel),取消以及重复,

系统还提供了AnimatorListnerAdapter这个,他是AnimatorListner的适配器类,这样就可以有选择的实现上面的4个方法

anim.addListener(new AnimatorListenerAdapter()  
{  
    @Override  
    public void onAnimationEnd(Animator animation)  
    {  
        Log.e(TAG, "onAnimationEnd");  
    }  
});  

AnimatorListenerAdapter继承了AnimatorListener接口,然后空实现了所有的方法

(2)AnimatorUpdataeListener

AnimatorUpdataeListener比较特殊,他会监听整个动画过程,动画师有许多帧组成的,每播放一帧,onAnimationUpdate就会调用一次(每播放一帧默认是10ms

Public static interface AnimatorUpdateListener{

Void onAnimationUpdateListner(ValueAnimator animation)

}


四.属性动画的原理

属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说随着时间推移,所传递的值越来越接近最终值。(言简意赅的说就是在规定的一段时间里,不断地调用某个属性的set方法,最后达到要求的最终值

所以动画生效的条件:

(1)object必须要提供setAbc方法,如果动画开始的时候没有传递初始值,那么还要提供getAbc方法,因为系统要去取abc属性的初始值(如果不满足,crash

(2)object的setAbc对属性abc所做的改变必须能够通过某种方法反映出来,比如会带来UI的改变之类的(如不满足,动画无效果,但是不会crash


例子:对Button的width设置属性没有效果?因为虽然内部提供了getWidth和setWidth,但是并不是改变视图的大小,他是TextView新添加的方法,View是没有这个方法的,

Button中的setWidth对应的是android:width是设置最大宽度和最小宽度,不是android:layout_width

解决方法如下:

1.给你的对象加上get和set,前提是得有权限(基本不能用,除非是自定义的)

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

3.采用ValueAnimator,监听动画过程,自己实现属性的改变(一般都用)。

//下面代码把2,3两个方法都写出来了

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button bt_main_one;
    private Button bt_main_two;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bt_main_one = (Button) findViewById(R.id.bt_main_one);
        bt_main_two = (Button) findViewById(R.id.bt_main_two);
        bt_main_one.setOnClickListener(this);
        bt_main_two.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_main_one:
//第一种方法,让button的width变大

           ViewWrapper wrapper = new ViewWrapper(bt_main_one);
                ObjectAnimator.ofInt(wrapper,"width",500).setDuration(1000).start();
                break;
            case R.id.bt_main_two:
                Intent intent = new Intent(this, SecondActivity.class);
                startActivity(intent);
                break;
        }

    }

    private static  class ViewWrapper{
        private View mTarget;

        public ViewWrapper(View mTarget) {
            this.mTarget = mTarget;
        }
        public int getWidth(){
            return mTarget.getLayoutParams().width;

        }
        public void setWidth(int width){
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }
}
SecondActivity.java
public class SecondActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mButton;
    //持有一个IntEvalute对象,等会下面估值要用
    private IntEvaluator mEvaluator = new IntEvaluator();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        mButton = (Button) findViewById(R.id.bt_second);
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(v == mButton){
//第二种改变button的width的方法
         performAnimate(mButton,mButton.getWidth(),500);
        }
    }

    public void performAnimate(final View target ,final int start ,final int end){
        final ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获得当前动画的进度值  ,整型,1-100之间
                int currentValue  = (int) valueAnimator.getAnimatedValue();

                //获得当前进度占整个动画过程的比例,浮点型,0-1之间
                float fraction = animation.getAnimatedFraction();
                //直接调用整型估值器,通过比例计算出宽度,然后再设给Button

//估值器的作用其实就是封装了数据随着时间按照一定比例变化的计算过程

                mButton.getLayoutParams().width  = mEvaluator.evaluate(fraction,start,end);
                mButton.requestLayout();
            }
        });
        valueAnimator.setDuration(2000).start();
    }
}

源码在这里


五.注意事项

(1).OOM问题

当图片数量较多且较大时就极易出现OOM,尽量避免使用帧动画

(2).内存泄露

属性动画中有一种无线循环的动画(infinite,这类动画需要在Activity退出时及时停止,否则将导致Activity无法释放从而造成内存泄露,但是View动画(也就是补间动画)并不会出现这个问题

(3).注意属性动画要在API11,否则要导包nineload...

(4)View动画 有时候播放完成后用setVisibilty(View.gone)失效了,这个时候只要调用view.clearAnimation()清除view动画可解决这个问题

(5).3.0以前的系统,view动画,属性动画,新的位置都无法触发单击事件(因为3.0以前属性动画实际还是View动画),但是老位置可以3.0以后的系统,属性动画在新的位置也可以触发单击事件,但是view动画不行

(6)建议开启硬件加速







你可能感兴趣的:(基础动画-(2)-属性动画)